iTranslated by AI
Fine-Grained Access Control Using Email from IAP Request Headers
When running an admin panel (admin site) on Google Cloud, Cloud IAP (Identity-Aware Proxy) is very useful.
For example, by running an admin site deployed to Cloud Run through IAP, you can easily achieve things like "allowing access only to specific Google accounts."
This article is a note on how to implement more fine-grained privilege management after introducing IAP. For details on IAP, please check the documentation.
Wanting fine-grained privilege management in applications running behind IAP
You might want to implement more detailed privilege management for applications running behind IAP. For example, in cases like the following:
- You want to restrict access to personal information to only some of the administrators.
- You want to restrict operations such as editing and deleting to only some of the administrators.
The email address obtainable from the request header seems useful
In requests that pass through IAP, you can reliably retrieve the email address from the request header. Specifically, the payload obtained from the signed header named "X-Goog-IAP-JWT-Assertion" contains a value called email.
This is also described in the documentation, but for reference, I'll provide a sample using Node.js + TypeScript.
# Install necessary packages beforehand
$ npm i google-auth-library gcp-metadata
gcp-metadata is used to retrieve metadata for the running Google Cloud project.
import { OAuth2Client } from 'google-auth-library';
import * as metadata from 'gcp-metadata';
// Cache the project info because it's expensive to fetch every request
// https://cloud.google.com/nodejs/getting-started/authenticate-users
let aud: string;
async function getAudience() {
if (aud) return aud;
if (!await metadata.isAvailable()) {
throw new Error("Could not retrieve project metadata")
}
const projectNumber = await metadata.project('numeric-project-id');
const projectId = await metadata.project('project-id');
aud = `/projects/${projectNumber}/apps/${projectId}`;
return aud;
}
async function getIapUserEmail(iapJwt: string): null | string {
const aud = await getAudience();
const { pubkeys } = await oAuth2Client.getIapPublicKeys();
const ticket = await oAuth2Client.verifySignedJwtWithCertsAsync(
iapJwt,
pubkeys,
aud,
['https://cloud.google.com/iap']
);
const payload = ticket.getPayload();
if (!payload) return null;
return payload.email
}
You can retrieve the requesting user's email address like this:
const iapJwt = req.header('X-Goog-IAP-JWT-Assertion');
const userEmail = await getIapUserEmail(iapJwt)
Privilege management based on email address
Now, all you need to do is keep a list of privileges associated with the email addresses in a database or similar, and check them within the application. If access is limited to a few people, you might even write the privilege list as JSON (which also has the advantage of allowing you to manage privileges via Git).
Here is a simple sample code using JSON.
// List of user privileges
const userRoles = [
{
email: "hanako@example.com",
role: "admin"
},
{
email: "takashi@example.com",
role: "member"
},
...
]
const iapJwt = req.header('X-Goog-IAP-JWT-Assertion');
const userEmail = await getIapUserEmail(iapJwt)
// Requesting user's privilege
const useRole = userRoles.find(({ email }) => email === userEmail).role
Simply distribute the logic based on the retrieved privilege.
if (useRole !== "admin") {
return res.status(403).json({ message: "This operation is not permitted" })
}
If you know of a better way, please let me know in the comments! 👏
Discussion
サンプルコード一寸ミスってそう。
getIapUserEmail関数内、変数assertion→iapJwtじゃないかとミスってました!ありがとうございます!!!!