Reputation: 6806
I have a google cloud function providing a Google Chat App. My GCF receives calls from Google Chat when users type input. It doesn't call Chat; it exposes a URL and Chat calls it, it computes for a while, and it sends a response back to Chat. So the GCF does not need authentication to work with Chat.
I seem to need to configure my Google Cloud Function to accept network requests to "Allow All Traffic". Google Chat does not seem to be part of my Google Cloud Project, so I cannot configure the connection into the GCF to be "Allow Internal Traffic Only". So anyone learning the URL of my GCF could flood it with fake traffic.
Ideally, I would like the Google networking system to only permit access to my function from Google Chat, but I cannot find a way to do this using a service account in Google IAM. Can I do this somehow?
Looking at the headers of the access from Chat to my GCF, it does contain an authorization: 'Bearer eyJhbGciO...'
header. That turns out to be a JSON Web Token. That says
headers: {
"alg": "RS256",
"kid": "06ea3d3c9414b34d77d66407580cec7e10c0b7d3",
"typ": "JWT"
}
payload: {
"aud": "939021344830",
"exp": 1680195982,
"iat": 1680192382,
"iss": "[email protected]"
}
so it looks like that is a token generated by Google Chat and signed by Google to demonstrate that the source was indeed Google Chat. The kid
should identify the key I need to validate the JWT. How can I verify that this token is properly signed by Google?
I looked at Secure Google Cloud Functions http trigger with auth but the solutions there do not seem to be applicable because I do not have control over how Google Chat makes the call to my GCF.
Thanks!
Upvotes: 3
Views: 906
Reputation: 460
One good approach is to deploy in front of your GCF a Cloud Endpoints. This is a step-by-step https://cloud.google.com/endpoints/docs/openapi/set-up-cloud-functions-espv2
For the security part I recommended the following:
Restrict to "Allow Internal traffic only" because all the traffic will be routed by the new Cloud Endpoint
Disable the (unauthenticated) access, eliminating the AllUsers permission on the IAM, and set the Cloud Function Invoker role to the SA of the Cloud Endpoints only.
Use the security definition in the YAML to validate the JWT https://cloud.google.com/endpoints/docs/openapi/authenticating-users-custom#configuring_esp_to_support_client_authentication
securityDefinitions:your_custom_auth_id:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
# The value below should be unique
x-google-issuer: "[email protected]"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/[email protected]"
# Optional. Replace YOUR-CLIENT-ID with your client ID
x-google-audiences: "939021344830"
Note that is replace with the values of the JWT that you provide:
With this approach you don't need to implement the logic to validate the JWT and you could reuse this YAML definition to multiples Cloud Functions.
Upvotes: 0
Reputation: 6806
Here is my code to check the JWT, where token
is the authorisation header stripped of the bearer
prefix. This seems to be working.
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: 'https://www.googleapis.com/service_accounts/v1/metadata/jwk/chat%40system.gserviceaccount.com',
});
const promiseToVerify = (token) => {
return new Promise((resolve, reject) => {
const decoded = jwt.decode(token, {complete: true});
if (!decoded || !decoded.header || !decoded.header.kid) {
return reject(new Error('Invalid token'));
}
client.getSigningKey(decoded.header.kid, (err, key) => {
if (err) {
return reject(err);
}
const signingKey = key.publicKey || key.rsaPublicKey;
jwt.verify(token, signingKey, (err, decoded) => {
if (err) {
return reject(err);
}
resolve(decoded);
});
});
});
};
The aud
which appears in the JWT is the project ID of a folder called system-gsuite
which appears in our google cloud console project selector dropdown. I have not yet been able to find if that ID is unique to our code. aud
needs checking to make sure it is the right value, as well as validating the JWT signature. jwt.verify
above does check the validity time and complains if the JWT has expired.
Using this code, we can be confident that the JWT was signed by google, because the kid
has to be
that of a public key found at a google-controlled URL specific to Google Chat. And the aug
is the ID of a google project
known to our google cloud console. And the token has not yet expired.
Upvotes: 0