What I'm trying to do is generate a JWT for calling the HTTP trigger on a GCP Cloud Function I've deployed. I've already deployed my function with 'allUsers' and verified it works, but I want it to be more secure, so I need to attach a JWT to my HTTP request. I'm following this code and the following snippets are mostly from that. I think I am close, but not quite there yet. In all of the samples etc below I've changed my project name to PROJECT_NAME.
First I created a service account named testharness-sa and downloaded its key file. I have an env var GOOGLE_APPLICATION_CREDENTIALS pointing to that file when I run my tests. Then I ran the following command:
gcloud functions add-iam-policy-binding gopubsub \
--member='serviceAccount:testharness-sa@PROJECT_NAME.iam.gserviceaccount.com' \
--role='roles/cloudfunctions.invoker'
This gave me a confirmation by listing all the current bindings on my cloud function, including testharness-sa.
The core of my code is this:
private String getSignedJwt() {
GoogleCredentials credentials = GoogleCredentials
.getApplicationDefault()
.createScoped(Collections.singleton(IAM_SCOPE));
long now = System.currentTimeMillis();
RSAPrivateKey privateKey = (RSAPrivateKey) credentials.getPrivateKey();
Algorithm algorithm = Algorithm.RSA256(null, privateKey);
return JWT.create()
.withKeyId(credentials.getPrivateKeyId())
.withIssuer(credentials.getClientEmail())
.withSubject(credentials.getClientEmail())
.withAudience(OAUTH_TOKEN_AUDIENCE)
.withIssuedAt(new Date(now))
.withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
.withClaim("target_audience", clientId)
.sign(algorithm);
}
This gives me a signed JWT. As I understand things, this is used to call GCP to get me a final JWT I can use to call my cloud function.
Once I generate the signed JWT I use it like this:
String jwt = getSignedJwt();
final GenericData tokenRequest = new GenericData()
.set("grant_type", JWT_BEARER_TOKEN_GRANT_TYPE)
.set("assertion", jwt);
final UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
final HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
final HttpRequest request = requestFactory
.buildPostRequest(new GenericUrl(OAUTH_TOKEN_URI), content)
.setParser(new JsonObjectParser(JacksonFactory.getDefaultInstance()));
HttpResponse response = request.execute();
...
So it gets the signed JWT and makes up a request to get the final JWT and then... fails. The error is "Invalid JWT: Failed audience check" It looks like I have a bad parameter, so let's look at my parameters (they are actually constants, not parameters):
private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";
//"https://www.googleapis.com/auth/cloud-platform";
private static final String OAUTH_TOKEN_URI = "https://oauth2.googleapis.com/token";
//"https://www.googleapis.com/oauth2/v4/token";
private static final String OAUTH_TOKEN_AUDIENCE = "https://us-central1-PROJECT_NAME.cloudfunctions.net/gopubsub";
//"https://www.googleapis.com/token";
private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;
I've added other variants as comments against the constants.
Based on the error message it looks like the audience value I'm using is wrong. This page suggests the audience should be the service account email, and I think I've seen elsewhere that it ought to be the URL of the cloud function, but neither of those work for me.
I do know this can work because I can issue this command:
gcloud auth print-identity-token
which gives me the final JWT (as long as I have GOOGLE_APPLICATION_CREDENTIALS pointing at my json file). I can paste that JWT into a curl command and invoke the HTTP trigger successfully, and if I leave the JWT out it fails, so I know the JWT is being checked. But so far I don't know how to do the equivalent of gcloud auth print-identity-token from my Java code. Anyone know?