The system cannot find the path specified in Linux Server

Product/components used and version/fix level:

Java program in webMethods 10.7 for getting Google Authentication access token.

Detailed explanation of the problem:

We are using a firebase Authentication Java code to get the Authentication token. As per the code shared by google firebase APIs, we are supposed to pass a JSON file(Property Key file which has Google Firebase Credentials) and get the token. But while executing the below java code it throws error.

Is there any error in the path specified? We could see proper full permission is given to the file. Do we have to add hostname or any other details in file path specified or anything to be added in fileAccessControl.cnf file?

Java Code Snippet:
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(new FileInputStream(“/opt/softwareag107/IntegrationServer/instance/default/packages/Default/config/service-account.json”)).createScoped(Arrays.asList(SCOPES))

Error messages / full error message screenshot / log file:

File Path: /opt/softwareag107/IntegrationServer/instance/default/packages/Default/config/service-account.json
Error obtaining access token: \opt\softwareag107\IntegrationServer\instance\default\packages\Default\config\service-account.json (The system cannot find the path specified)

Question related to a free trial, or to a production (customer) instance?

Hi Dinesh,

is it really neccessary to have this under the Default package structure?
Usually it is recommended to leave the Default package untouched after installation.

Preferrably this can be located under the config folder of the instance in a custom folder, which will then be backed up to config/backup structure once the IS completes a clean restart.

Remember that all pathes inside the IS instance can be specified as relative pathes to the base instance directory “instances/<instance_name>”.

Regards,
Holger

Thanks for your reply. Just for some internal testing purpose it was added. For project specific it will be moved to Project specific folders.
Let me try with the relative path to base instance directory and see if it work. Thanks for your suggestion.

Does it requires a restart or reloading WmPublic package should be enough? As we tried reload of WmPublic package and its not working properly.

Hi Dinesh,

when it is located under Default package, you will have to reload the Default package, for project specific packages you will have to reload the affected package where it is located.
Hopefully you will have a Startup Service assigned to that package which reads the file during load or reload of the package.

Regards,
Holger

@Dinesh_Sitaraman2 ,

If you invoke the service pub.file:getFile and give it the same path, does it work? The error indicates to me that the path is perhaps invalid or the user under which the IS is running does not have access to it. I understand, however, you said the permissions are set properly.

One thing you can try for now is to pass the JSON into your service as a string. In fact, in my current project we have several integrations with Google Cloud Pub/Sub and BigQuery. We store the service account key JSON file in a secrets manager. During the token generation, we fetch the JSON first and then create a ByteArrayInputStream from it.

Here’s the method responsible for generating the token:

	private static final String getAccessToken(String clientId, String audience, long expireAfter, String expireAfterUnit) throws Exception {
		long now = System.currentTimeMillis();
		if(expireAfter == -1L) {
			// If expireAfter was not provided, default to the currently maximum allowed value of 1 hour
			expireAfter = 1;
			expireAfterUnit = TimeUnit.HOUR;
		}
		else if(expireAfterUnit == null) {
			expireAfterUnit = TimeUnit.DEFAULT;
		}
		String serviceAccountKey = getCredential(clientId);		// This fetches the JSON as a string from our secrets manager
		GoogleCredential credential = GoogleCredential
				.fromStream(new ByteArrayInputStream(serviceAccountKey.getBytes()));
		PrivateKey privateKey = credential.getServiceAccountPrivateKey();
		String privateKeyId = credential.getServiceAccountPrivateKeyId();
		String clientEmail = (new ObjectMapper()).readTree(serviceAccountKey).get("client_email").asText();
		Algorithm algorithm = Algorithm.RSA256(null, (RSAPrivateKey) privateKey);
		Builder tokenBuilder = JWT.create()
				.withKeyId(privateKeyId)
				.withIssuer(clientEmail)
				.withSubject(clientEmail)
				.withAudience(audience)
				.withIssuedAt(new Date(now));
		if(expireAfter > 0L) {
			tokenBuilder = tokenBuilder.withExpiresAt(new Date(now + convertToMillis(expireAfter, expireAfterUnit)));
		}
		String accessToken = tokenBuilder.sign(algorithm);
		return accessToken;
	}

Hope this helps but let me know if you have any more questions.

Percio

I am somewhat dismayed that this authentication mechanism requires a vendor-specific library and classes. Is there not a way to do authentication/authorization via HTTP calls? Using a library seems so “throwback.” :slight_smile: Surely Google offers something so that we don’t all need install their library and write Java code?

2 Likes

You should really avoid hard-coding paths (and most other things for that matter) like the plague. I know that I am a “bit” obsessed with this kind of configuration management issues. However, there is a reason for it (see this blogpost for details).

2 Likes

Hi @reamon ,

It is my understanding that when authenticating against Google Cloud with a service account, use of the Google libraries is recommended. In fact, here are a few blurbs from Google’s own documentation:

Although your application can complete these tasks by directly interacting with the OAuth 2.0 system using HTTP, the mechanics of server-to-server authentication interactions require applications to create and cryptographically sign JSON Web Tokens (JWTs), and it’s easy to make serious errors that can have a severe impact on the security of your application.

For this reason, we strongly encourage you to use libraries, such as the Google APIs client libraries, that abstract the cryptography away from your application code.

In other section, it also goes on to say:

With some Google APIs, you can make authorized API calls using a signed JWT directly as a bearer token, rather than an OAuth 2.0 access token. When this is possible, you can avoid having to make a network request to Google’s authorization server before making an API call.

More info here: Using OAuth 2.0 for Server to Server Applications  |  Authorization  |  Google for Developers

HTH,
Percio

Glad to see there is a direct option to use OAuth2 over HTTP.

As with everything, it is a trade-off of sometimes conflicting constraints.

Hopefully Google identifies what these are somewhere and not just assume “it’s too hard for you to do, so use ours.” :slight_smile:

As per the Google API documentation, they shared only Node.js, Python, Java code for getting the Authentication token. For actual sending notification(FCM) there are REST API calls available, where we can use this authentication token generated as bearer token and invoke via HTTP.

But for the Authentication part, still we have to get the corresponding libraries and classes to make it work.

Path added in fileAccessControl seems to be incorrect which was corrected and issue is solved. Thanks for your detailed explanation, its really helpful in proceeding with getting the Authentication token.
We were able to proceed with reading the serviceAccountKey.json file content and proceed with implementation. Whereas its failing in getAccessToken() method due to some Network level issue which is yet to be sorted.

Based upon the info that @Percio_Castro1 shared, that appears to not be the case.

@Dinesh_Sitaraman2

I’m glad the issue is resolved BUT fileAccessControl.cnf only comes into play when using the file-related WmPublic services. If you have a Java service of your own that creates a FileInputStream, fileAccessControl.cnf doesn’t come into the picture.

Percio

Agreed. The blurb that I find of more interest though is the second one. Being able to sign the JWT token without having to make a network request seemed worthwhile and it has been working well in our environment.

Take care,
Percio