IAPのリクエストヘッダから取得したemailを使って細かな権限管理をする
Google Cloudで管理画面(管理サイト)を動かすときには、Cloud IAP(Identity-Aware Proxy)がとても便利です。
例えば、Cloud Runにデプロイされた管理サイトをIAP越しに動かすことで「特定のGoogleアカウントに対してのみアクセスを許可する」といったことが簡単に実現できます。
この記事は、IAPを導入したうえでより細かな権限管理をする方法についてメモしたものです。IAPの詳細はドキュメントを確認してください。
IAP越しに動くアプリケーションでより細かな権限管理をしたい
IAP越しに動かしているアプリケーションで、より細かな権限管理をしたくなることもあると思います。例えば以下のようなケースです。
- 個人情報にアクセスできるのは管理者の中でも一部の人に限定したい
- 編集・削除といった操作ができるのは管理者の中でも一部の人に限定したい
リクエストヘッダから取り出せるメールアドレスが使えそう
IAPを経由したリクエストでは、リクエストヘッダからメールアドレスを信頼できる形で取り出すことができます。具体的には、"X-Goog-IAP-JWT-Assertion"
という署名付きのヘッダから取得したペイロードの中にはemail
という値が含まれています。
このあたりについてはドキュメントにも記載されていますが、参考までにNode.js + TypeScriptを使ったサンプルを載せておきます。
# 事前に必要なパッケージをインストール
$ npm i google-auth-library gcp-metadata
gcp-metadataは稼働中のGoogle Cloudのプロジェクトのメタデータの取得に使います。
import { OAuth2Client } from 'google-auth-library';
import * as metadata from 'gcp-metadata';
// リクエストのたびにプロジェクトの情報を取得するのはコストが高いためキャッシュする
// 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("プロジェクトのメタデータが取得できませんでした")
}
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
}
以下のような感じでリクエストユーザーのメールアドレスを取得できます。
const iapJwt = req.header('X-Goog-IAP-JWT-Assertion');
const userEmail = await getIapUserEmail(iapJwt)
メールアドレスをもとに権限管理をする
あとはメールアドレスに対応する権限の一覧をデータベースなどに持たせておいて、アプリケーションの中でチェックするだけです。アクセスするのが数人に限られるのであれば、権限一覧をJSONで書いてしまっても良いかもしれません(Gitで権限を管理できるというメリットもあります)。
ここではJSONを使った簡単なサンプルコードを載せておきます。
// ユーザーの権限一覧
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)
// リクエストユーザーの権限
const useRole = userRoles.find(({ email }) => email === userEmail).role
取得した権限をもとに処理を振り分ければOKです。
if (useRole !== "admin") {
return res.status(403).json({ message: "この操作は許可されていません" })
}
より良い方法をご存知の方は、ぜひコメントなどで教えてください 👏
Discussion
サンプルコード一寸ミスってそう。
getIapUserEmail
関数内、変数assertion
→iapJwt
じゃないかとミスってました!ありがとうございます!!!!