The Zurich release has arrived! Interested in new features and functionalities? Click here for more

Xander H
ServiceNow Employee
ServiceNow Employee

Introduction

Google Cloud Platform, like AWS and Azure, is a significant cloud infrastructure service. We recently integrated GCP's BigQuery product as part of an implementation for user provisioning.

 

When using Service Accounts (the recommended server-to-server authentication method for these type of integrations), the provided client libraries by Google allow you to easily integrate and authenticate. However, within the Now Platform, it is not possible to use these libraries.

 

OAuth2 is used for GCP server-to-server authentication, but when using service accounts, it is not possible to use the clientside OAuth redirection flow. There is no client secret, only a certificate and a private key (which is not the same thing). Therefore, we can't use the built-in Now Platform OAuth2 authentication functionality. 

 

As a result, we integrated GCP by calling the REST APIs, which requires signed JWT tokens. This article walks us through that process and gives an end-to-end description of how to accomplish the integration of GCP without the use of Google's client libraries.

 

XanderH__0-1679325716097.png

 

Image credit: Google Developer Documentation

 

Obtaining an access token

In order to make REST calls to Google BigQuery or other GCP platforms, we need an access token. This token is retrieved from server-to-server OAuth using a service account, as described in this documentation: https://developers.google.com/identity/protocols/oauth2/service-account

 

Assuming that the service account file has been provided to you (obtaining it is out of scope for this article), we need to implement the steps that are described in this section, under the HTTP/REST tab. 

 

The idea is that we will be able to create JWT tokens that are signed with the certificates embedded in the service account file. Fortunately, the platform provides part of this functionality out of the box, through X509 certificates, JWT keystores and JWT providers. We just need to set them up.

 

Creating the certificates, keystores and providers

First, follow the steps in this document in order to create a Java KeyStore certificate based on the security certificate sent by Google: Create a Java KeyStore certificate. After following this documentation, you should have passwords which you used to sign, an X509 cert .pem file and a .jks keystore.

 

Next, we create a new entry in the X.509 Certificates table (sys_certificate):

- Type = Java Keystore

- Keystore password = as created

- Add the generated .jks file as an attachment

 

Afterwards, we create a JWT Keys entry in the jwt_keystore_aliases table:

- Signing keystore = the record created in the previous step

- Signing algorithm = RSA 256

- Signing key = as created

 

Finally, we create a new JWT Provider:

- Signing Configuration = previously created JWT Keys record

- Standard claims:

-- Name = aud, Value = https://oauth2.googleapis.com/token

-- Name = iss, Value = service account "email address"  ending in .gserviceaccount.com

 

Obtaining a token

At this point, we are able to sign our own JWT tokens that can use to call the OAuth token endpoint:

		var scopes = [
			'https://www.googleapis.com/auth/bigquery', 
			'https://www.googleapis.com/auth/cloud-platform'
		];

		var jwtAPI = new sn_auth.GlideJWTAPI();

		var header = JSON.stringify({ });
		var payload = JSON.stringify({ scope: scopes.join(" ") });

		var jwtProviderSysId = gs.getProperty('gcp.jwt.provider.sysid');
		var jwt = jwtAPI.generateJWT(jwtProviderSysId, header, payload);

		gs.info(jwt);

I have decided to store the JWT Provider record's sys ID in a system property for easy reconfiguration. 

 

Now we are able to create our signed JWTs, we just need to call the token endpoint on GCP and get a Bearer token. For that, I have created a new Outbound REST Message with endpoint = https://oauth2.googleapis.com/token called "Google Cloud Platform OAuth token".

 

I added a POST method call with No authentication, and 2 HTTP Query Parameters:

- Name = grant_type, Value = urn:ietf:params:oauth:grant-type:jwt-bearer

- Name = assertion, Value = ${jwt}

 

XanderH__0-1679324563933.png

 

XanderH__1-1679324579967.png

 

And finally we can make another function calling this REST message based on a jwt:

 

			var jwt = createJwt(); // output of above snippet
			var r = new sn_ws.RESTMessageV2('Google Cloud Platform OAuth token', 'Get token'); // name of your REST Message and request here
			r.setStringParameterNoEscape('jwt', jwt);

			var response = r.execute();
			var responseBody = response.getBody();

			var access_token = JSON.parse(responseBody).access_token; 
			gs.info(access_token);

 

We have obtained a successful Bearer token to be used in calls to GCP endpoints!

 

Querying Google BigQuery

At this time we can call any GCP endpoint using our access token. In our case, we will query the /bigquery/v2/projects/${projectId}/queries endpoint.

 

Thus, we make a new REST Message, calling "Google BigQuery query" with endpoint https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/queries.

 

Once again we set No authentication (we will authenticate through the header), and we create a new POST method call called "Query".  Here, we set the HTTP request as follows:

 

Headers:

- Name = Accept, Value = application/json

- Name = Authorization, Value = Bearer ${accessToken}

- Name = Content-Type, Value = application/json

 

And finally, our code:

			var token = createAuthToken(); // output from the previous call
			var projectId = 'YOUR-GBQ-PROJECT-ID';

			var bodyObj = { "query": query };
			
			var r = new sn_ws.RESTMessageV2('Google BigQuery query', 'Query');
			
			r.setStringParameterNoEscape('accessToken', token);
			r.setStringParameterNoEscape('projectId', projectId);
			r.setRequestBody(JSON.stringify(bodyObj));

			var response = r.execute();
			var responseBody = response.getBody();
			
			gs.info(responseBody);

Conclusion

In conclusion, we were able to connect to Google Cloud Platform without the Google client libraries by manually constructing and encrypting a JWT token and sending that to the OAuth endpoint, to retrieve a Bearer token.

 

We can then use the Bearer token to make any endpoint call within GCP, such as to GBQ's queries endpoint. However, this token can be used to authorize any other endpoint call, if the service account's scopes allow.

Comments
gandalf
Tera Expert

Hi folks. I've followed all steps and I successful retrieve both JWT and Token. Unfortunately when I use the token to connect my specific end-point on GPC, I see in the logs:

 

Integration crypto failure protocol=HTTPS javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated callchain=[{http '2023-08-30T08:43:21.849866Z' '9'}, {sys_ui_action '48c17ed207131000dada43c0d1021e83' 'global' '48c17ed207131000dada43c0d1021e83' '2023-08-30T08:43:21.840825Z' '8' '8'}]

 

and using the token directly on the REST UI: Request not sent to uri= https://XX.YY.it/ : org.apache.commons.httpclient.HttpException: Session contains no certificates - Untrusted

gandalf
Tera Expert

I think the error is due to this article:

https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1112530

 

The KB contains an invalid link: so in order to enable roles about KMF you have to see here: https://docs.servicenow.com/bundle/sandiego-platform-security/page/administer/key-management-framewo...

 

I've found "Module Access Policy" giving to my admin on my istance both KMF admin and KMF cryptographic manager roles..... First the KMF admin + logout, after KMF cryptographic manager + logut ... and finally the module was there!

gandalf
Tera Expert

Hi folks,

unfortunately also enabling the KMF on my personal istance the connection with my GCP endpoint still fails and into logs I see:

 

Integration crypto failure protocol=HTTPS javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated callchain=[{http '2023-08-30T13:56:48.385475Z' '11'}, {scoped_script_evaluation 'e37ada1c474531109289b616536d43b2' 'script' '2023-08-30T13:56:48.296148Z' '8'}]

Iv'e also opened a specific case to understand where is the point (CS6859348) and I've also tried to follow this KB: https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1509991

Again I find that the steps given are unclear ... So I wait for exiting from this "dead-end road"

Mohan raj
Mega Sage

Hi @Xander H,

 

I tried to implement the same integration with a single script include. It worked fine. Please let me know if there are any mistakes or best practices I need to follow.

 

Please review my code in GitHub repo and below is link to my GitHub 

 

https://github.com/thangarajmohan/GCP-BigQuery-to-ServiceNow/tree/main

 

 

 

Xander H
ServiceNow Employee
ServiceNow Employee

Hi @gandalf, looks like the issues you are seeing are related to a local (on-premise) installation and SSL configuration. Afraid that's beyond the area of my expertise and hope Now Support will be able to help you!

 

@Mohan raj indeed, your single script include looks like it contains all the functionality. From a scripting best practice, it's recommended to break up the function `getData` in some smaller function units (for reusability, maintainability, testability, etc).

I would also store the REST Message templates in a record (less code, easier to manage) as per the article, and it's generally not a great idea to hardcode sys IDs (like that of the JWT provider), they should be stored in system properties.

Finally, I don't think you want to include an "exp" key in the payload for creating the JWT - it means "expiration" and will be dynamically generated. I think it is being overridden in your case so there's no real harm in having it per se, just extraneous and possibly confusing moving forward (in a few months time you will think - why was it there?).

 

With all that said, these are all best practices - your core functionality seems to work fine, good work!

gandalf
Tera Expert

Hi gents

I share the end of story.

Really useful to test the chain autority this link, reported on the above link --> https://www.digicert.com/help/

Ali42
Tera Contributor

Hi, 

 

I have a question regarding the CMDB class used to identify Bigquery data. Do you have any idea ?

 

Thank you

Ali S.

Xander H
ServiceNow Employee
ServiceNow Employee

Hi Ali, 

 

I'm not entirely sure what your question concretely is. However we did not integrate this data with CMDB as our BigQuery data was used for sys_user creation and nothing relating to CMDB.

Version history
Last update:
‎03-20-2023 08:40 AM
Updated by:
Contributors