Get hands-on experience with 20+ free Google Cloud products and $300 in free credit for new customers.

Accessing a Google Cloud Platform based service using JWT and a service account

We have a GCP-based microservice (built within our company) which we are attempting to access via one of our proxies. We set up a service account within GCP which should have access to this resource. We downloaded the credentials file associated with service account (edited for security):

{ 
  "type": "service_account",
  "project_id": "cluster",
  "private_key_id": "93158289b2734d823aaeba3b1e4a48a15aaac",
  "client_email": "apigee-orderservices-dev@ourcluster.iam.gserviceaccount.com",
  "client_id": "1167082158558367844",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/apigee-orderservices-dev%40ourcluster.iam.gserviceaccount.com",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQE...8K5WjX\n-----END PRIVATE KEY-----\n"
} 

We are attempting to use a GenerateJWT policy, storing the private_key value in an encrypted kvm so that we can get an authentication token using this JWT request, but I am currently getting "cannot instantiate private key". I'm pretty sure that I'm missing something basic here, but any assistance would be greatly appreciated.

Solved Solved
1 27 5,220
1 ACCEPTED SOLUTION

It works for me. I tried 2 different ways.

In either case, the goal was to create a JWT that is signed by an RSA key, obtained from the google service account credentials file. The payload of that JWT should look like this:

{
  "iss" : ServiceAccount client_email,
  "scope" : scope,
  "aud": ServiceAccount token_uri, 
  "iat": nowInSeconds,
  "exp": nowInSeconds + (3 * 60)
}

In either case, the first thing I did was: create a service account and download the .json file.

Then, Option 1:

  1. I extracted the private_key string from the json file,
  2. Using a text editor, I replaced all \n characters with "newline" . The result looks like this:

    -----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsGyx52HRj4z2D
    oMdmMcdVSU1nbZ6m/r4QOsiFL/KKcdnw6lMkUxwzaIKR27IK1Hngn8SROM5UAp3p
    WuGwyF2vlIfzOdQXFrjScCT0XJu5wjCjqZe0eUhEWMtBbiKTNSb2K536EG3iy9oY
    sUZ4RjNvHaRqGn8HAr6mGM5Q8fILIqSYQwNO+htvcso5TKHcR6b79Nz9TcqC6ger
    WG5pioXNmXSuuMHkTexqbLjdP0MIub/ViiqPIiWkGtv8wAZu+3NuIatuz1VFIq+v
    ...
    -----END PRIVATE KEY-----
    	
  3. added THAT ^^ to the Encrypted KVM via the Admin UI, stored under the client_id.
  4. Added a KVM-Get policy that looks like this:

    <KeyValueMapOperations name="KVM-Get-1" mapIdentifier="secrets">
        <ExclusiveCache>false</ExclusiveCache>
        <ExpiryTimeInSecs>300</ExpiryTimeInSecs>
        <Get assignTo="private.rsakey">
            <Key>
                <Parameter>104855500587360709513</Parameter>
            </Key>
        </Get>
        <Scope>environment</Scope>
    </KeyValueMapOperations>
    	

    You can see I am storing the extracted value to a variable that begins with "private." This is necessary to satisfy the validation for the GenerateJWT policy.

  5. Added a Generate-JWT policy like this

    <GenerateJWT name="Generate-JWT-1">
        <Algorithm>RS256</Algorithm>
        <PrivateKey>
            <Value ref="private.rsakey"/>
        </PrivateKey>
        <Issuer>dinoch-trial-171023@appspot.gserviceaccount.com</Issuer>
        <Audience>https://accounts.google.com/o/oauth2/token</Audience>
        <ExpiresIn>300s</ExpiresIn>
        <AdditionalClaims>
            <Claim name="scope" type="string">https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore</Claim>;
        </AdditionalClaims>
        <OutputVariable>output_jwt</OutputVariable>
    </GenerateJWT>
    	

And that worked. I was able to get an RSA-signed JWT with that policy.

The next thing I tried: rather than manually replacing the \n with newlines, and storing ONLY the private key into the encrypted KVM, I did this:

  1. Stored the entire, unmodified credentials.json file into the encrypted KVM

  2. Extracted that into a context variable with a KVM Get like this:

    <KeyValueMapOperations name="KVM-Get-2" mapIdentifier="secrets">
        <ExclusiveCache>false</ExclusiveCache>
        <ExpiryTimeInSecs>300</ExpiryTimeInSecs>
        <Get assignTo="private.credentialsjson">
            <Key>
                <Parameter>dinoch-trial-171023-bdb91206c515.json</Parameter>
            </Key>
        </Get>
        <Scope>environment</Scope>
    </KeyValueMapOperations>
    	
  3. Ripped the .json into context variables with a JavaScript policy like this:

    <Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="JavaScript-1">
        <DisplayName>JavaScript-1</DisplayName>
        <Properties/>
        <Source>
            var c = context.getVariable('private.credentialsjson');
            c = JSON.parse(c);
            for (var prop in c) { 
              context.setVariable('private.' + prop, c[prop]);
            }
        </Source>
    </Javascript>
    	
  4. Then generated a JWT with a GenerateJWT policy like this:

    <GenerateJWT name="Generate-JWT-2">
        <Algorithm>RS256</Algorithm>
        <PrivateKey>
            <Value ref="private.private_key"/>
        </PrivateKey>
        <Issuer ref="private.client_email"/>
        <Audience ref="private.token_uri"/>
        <ExpiresIn>300s</ExpiresIn>
        <AdditionalClaims>
            <Claim name="scope" type="string">https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore</Claim>;
        </AdditionalClaims>
        <OutputVariable>output_jwt</OutputVariable>
    </GenerateJWT>
    

Both ways worked for me. I suspect that it's not working for you because of the \n vs newlines, or some other problem transcribing the private key.

View solution in original post

27 REPLIES 27