このページでは、署名付きの IAP ヘッダーを使用してアプリを保護する方法について説明します。Identity-Aware Proxy(IAP)を構成すると、JSON Web Token(JWT)によってアプリに対するリクエストが承認されます。アプリは次のようなリスクから保護されます。
- IAP が誤って無効化される
- ファイアウォールの設定が誤っている
- プロジェクト内からのアクセス
アプリを適切に保護するには、すべてのアプリの種類に対して署名付きヘッダーを使用する必要があります。
また、App Engine スタンダード環境アプリを使用している場合は、Users API を使用できます。
Compute Engine と GKE のヘルスチェックには JWT ヘッダーが含まれていません。このため、IAP はヘルスチェックを処理しません。ヘルスチェックでアクセスエラーが返された場合は、ヘルスチェックが正しく構成されていることを Google Cloud コンソールで確認してください。また、JWT ヘッダーの検証でヘルスチェックのパスが登録されていることも確認する必要があります。詳細については、ヘルスチェックの例外の作成をご覧ください。
始める前に
署名済みヘッダーを使用してアプリを保護するには、次のものが必要になります。
- ユーザーが接続するアプリケーション。
ES256
アルゴリズムをサポートする、使用言語に対応するサードパーティの JWT ライブラリ。
IAP ヘッダーによるアプリの保護
IAP JWT でアプリを保護するには、JWT のヘッダー、ペイロード、署名を確認します。JWT は、HTTP リクエスト ヘッダー x-goog-iap-jwt-assertion
にあります。IAP をバイパスすると、IAP の署名がない ID ヘッダー x-goog-authenticated-user-{email,id}
の偽造が可能になります。IAP JWT はより安全な代替手段を提供します。
IAP がバイパスされた場合に備えて、署名付きヘッダーを使用することで補助的なセキュリティを提供します。IAP が有効になっている場合は、リクエストが IAP の処理インフラストラクチャを通過するときに、クライアントが設定した x-goog-*
ヘッダーが削除されます。
JWT ヘッダーの検証
JWT ヘッダーが次の制約に従っていることを確認します。
JWT ヘッダー クレーム | ||
---|---|---|
alg |
アルゴリズム | ES256 |
kid |
キー ID | IAP キーファイルにある公開鍵のいずれかに対応する必要があります。https://www.gstatic.com/iap/verify/public_key または https://www.gstatic.com/iap/verify/public_key-jwk のいずれかの形式で使用できます。 |
JWT が、トークンの kid
クレームに対応する秘密鍵で署名されていることを確認します。まず、次のいずれかから公開鍵を取得します。
https://www.gstatic.com/iap/verify/public_key
: この URL には、kid
クレームを公開鍵の値にマッピングする JSON 辞書が含まれています。https://www.gstatic.com/iap/verify/public_key-jwk
: この URL には、JWK 形式の IAP 公開鍵が含まれています。
公開鍵を取得したら、JWT ライブラリを使用して署名を検証します。
JWT ペイロードの検証
JWT ペイロードが次の制約に従っていることを確認します。
JWT ペイロード クレーム | ||
---|---|---|
exp |
有効期限 | 将来の時点にする必要があります。この時間は、UNIX エポック時刻からの秒数です。スキューには 30 秒かかります。トークンの最大有効期間は、10 分 + 2 * スキューです。 |
iat |
発行時 | 過去の時点にする必要があります。この時間は、UNIX エポック時刻からの秒数です。スキューには 30 秒かかります。 |
aud |
対象 | 次の値を持つ文字列にする必要があります。
|
iss |
発行元 | https://cloud.google.com/iap にする必要があります。 |
hd |
アカウント ドメイン | アカウントがホスト型ドメインに属する場合、アカウントが関連付けられているドメインを区別するために hd クレームが設定されます。 |
google |
Google クレーム |
リクエストにアクセスレベルが 1 つ以上適用されている場合、その名前は google クレームの JSON オブジェクトの access_levels キーの下に、文字列配列として格納されます。
デバイス ポリシーを指定していて、組織がデバイスデータにアクセスできる場合は、 |
上記の aud
文字列の値を取得するには、Google Cloud コンソールまたは gcloud コマンドライン ツールを使用します。
Google Cloud コンソールから aud
文字列値を取得するには、プロジェクトの Identity-Aware Proxy の設定に移動し、ロードバランサ リソースの横にある [さらに表示] をクリックして、[署名済みヘッダー JWT のユーザー] を選択します。[署名済みヘッダー JWT] ダイアログが開き、選択したリソースの aud
クレームが表示されます。
gcloud CLI の gcloud コマンドライン ツールを使用して aud
文字列の値を取得する場合は、プロジェクト ID を知っている必要があります。Google Cloud コンソール またはプロジェクト情報カードでプロジェクト ID を確認し、その値ごとに以下のコマンドを実行します。
プロジェクト番号
gcloud コマンドライン ツールを使用してプロジェクト番号を取得するには、次のコマンドを実行します。
gcloud projects describe PROJECT_ID
コマンドから次の出力が返されます。
createTime: '2016-10-13T16:44:28.170Z' lifecycleState: ACTIVE name: project_name parent: id: '433637338589' type: organization projectId: PROJECT_ID projectNumber: 'PROJECT_NUMBER'
サービス ID
gcloud コマンドライン ツールを使用してサービス ID を取得するには、次のコマンドを実行します。
gcloud compute backend-services describe SERVICE_NAME --project=PROJECT_ID --global
コマンドから次の出力が返されます。
affinityCookieTtlSec: 0 backends: - balancingMode: UTILIZATION capacityScaler: 1.0 group: https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/instanceGroups/my-group connectionDraining: drainingTimeoutSec: 0 creationTimestamp: '2017-04-03T14:01:35.687-07:00' description: '' enableCDN: false fingerprint: zaOnO4k56Cw= healthChecks: - https://www.googleapis.com/compute/v1/projects/project_name/global/httpsHealthChecks/my-hc id: 'SERVICE_ID' kind: compute#backendService loadBalancingScheme: EXTERNAL name: my-service port: 8443 portName: https protocol: HTTPS selfLink: https://www.googleapis.com/compute/v1/projects/project_name/global/backendServices/my-service sessionAffinity: NONE timeoutSec: 3610
ユーザー ID の取得
上記の検証がすべて成功したら、ユーザー ID を取得します。ID トークンのペイロードには次のユーザー情報が含まれています。
ID トークン ペイロードのユーザー ID | ||
---|---|---|
sub |
件名 |
ユーザー固有の安定した識別子。この値は、x-goog-authenticated-user-id ヘッダーの代わりに使用します。 |
email |
ユーザーのメール | ユーザーのメールアドレス。
|
次のサンプルコードでは、署名付き IAP ヘッダーを使用してアプリを保護しています。
C#
Go
Java
Node.js
PHP
Python
Ruby
検証コードのテスト
secure_token_test
クエリ パラメータを使用してアプリにアクセスすると、IAP に無効な JWT が設定されます。これを使用して、JWT 検証ロジックがさまざまな失敗事例に対応し、無効な JWT を受信したときのアプリの動作を確認します。
ヘルスチェックの例外の作成
前述のように、Compute Engine と GKE のヘルスチェックには JWT ヘッダーが含まれていません。このため、IAP はヘルスチェックを処理しません。ヘルスチェックへのアクセスを許可するように、ヘルスチェックとアプリを構成する必要があります。
ヘルスチェックの構成
ヘルスチェックのパスが未設定の場合は、Google Cloud コンソールでヘルスチェックの非機密パスを設定します。このパスは他のリソースと共有しないでください。
- Google Cloud コンソールの [ヘルスチェック] ページに移動します。
[ヘルスチェック] ページに移動 - アプリに使用しているヘルスチェックをクリックしてから、[編集] をクリックします。
- [リクエストパス] で、機密でないパス名を追加します。ヘルスチェック リクエストの送信時に Google Cloud が使用する URL パスを指定します。省略すると、ヘルスチェック リクエストは
/
に送信されます。 - [保存] をクリックします。
JWT 検証の構成
JWT 検証ルーチンを呼び出すコードに、ヘルスチェック パスに対して HTTP ステータス 200 を返す条件を追加します。例:
if HttpRequest.path_info = '/HEALTH_CHECK_REQUEST_PATH' return HttpResponse(status=200) else VALIDATION_FUNCTION
外部 ID 用の JWT
Google Identity と同様に、外部 ID を使用して IAP を使用すると、IAP は認証済みのリクエストごとに署名付き JWT を発行します。相違点もいくつかあります。
プロバイダ情報
外部 ID を使用する場合、JWT ペイロードには gcip
という名前のクレームが含まれます。このクレームには、メールや写真の URL など、ユーザーに関する情報とプロバイダ固有の属性が含まれます。
次の例は、Facebook でログインしたユーザーの JWT を示します。
"gcip": '{
"auth_time": 1553219869,
"email": "facebook_user@gmail.com",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"facebook_user@gmail.com"
],
"facebook.com": [
"1234567890"
]
},
"sign_in_provider": "facebook.com",
},
"name": "Facebook User",
"picture: "https://graph.facebook.com/1234567890/picture",
"sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',
email
と sub
フィールド
ユーザーが Identity Platform で認証されると、JWT の email
フィールドと sub
フィールドの接頭辞として Identity Platform トークン発行者または使用中のテナント ID(存在する場合)が追加されます。例:
"email": "securetoken.google.com/PROJECT-ID/TENANT-ID:demo_user@gmail.com", "sub": "securetoken.google.com/PROJECT-ID/TENANT-ID:gZG0yELPypZElTmAT9I55prjHg63"
sign_in_attributes
によるアクセスの制御
IAM は外部 ID と一緒に使用できません。ただし、sign_in_attributes
フィールドに埋め込まれたフィールドを使用することは可能です。たとえば、SAML プロバイダを使用してユーザーがログインしたとします。
{
"aud": "/projects/project_number/apps/my_project_id",
"gcip": '{
"auth_time": 1553219869,
"email": "demo_user@gmail.com",
"email_verified": true,
"firebase": {
"identities": {
"email": [
"demo_user@gmail.com"
],
"saml.myProvider": [
"demo_user@gmail.com"
]
},
"sign_in_attributes": {
"firstname": "John",
"group": "test group",
"role": "admin",
"lastname": "Doe"
},
"sign_in_provider": "saml.myProvider",
"tenant": "my_tenant_id"
},
"sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',
"email": "securetoken.google.com/my_project_id/my_tenant_id:demo_user@gmail.com",
"exp": 1553220470,
"iat": 1553219870,
"iss": "https://cloud.google.com/iap",
"sub": "securetoken.google.com/my_project_id/my_tenant_id:gZG0yELPypZElTmAT9I55prjHg63"
}
アプリケーションに次のようなロジックを追加し、有効な役割を持つユーザーによるアクセスを制限します。
const gcipClaims = JSON.parse(decodedIapJwtClaims.gcip);
if (gcipClaims &&
gcipClaims.firebase &&
gcipClaims.firebase.sign_in_attributes &&
gcipClaims.firebase.sign_in_attribute.role === 'admin') {
// Allow access to admin restricted resource.
} else {
// Block access.
}
Identity Platform SAML および OIDC プロバイダからの追加のユーザー属性は、gcipClaims.gcip.firebase.sign_in_attributes
のネストクレームを使用してアクセスできます。
IdP クレームによるサイズ制限
ユーザーが Identity Platform でログインすると、追加のユーザー属性がステートレス Identity Platform ID トークン ペイロードに反映され、IAP に安全に渡されます。IAP は独自の不透明なステートレス Cookie を発行し、この Cookie にも同じクレームが含まれます。IAP は、Cookie の内容に基づいて署名付き JWT ヘッダーを生成します。
そのため、多数のクレームを使用してセッションが開始されると、Cookie の最大許容サイズ(ほとんどのブラウザでは通常 4 KB)を超えることがあります。これにより、ログイン操作が失敗します。
IdP SAML 属性または OIDC 属性に必要なクレームのみが伝播されることを確認する必要があります。もう 1 つの方法は、ブロッキング関数を使用して、承認チェックに不要なクレームを除外することです。
const gcipCloudFunctions = require('gcip-cloud-functions');
const authFunctions = new gcipCloudFunctions.Auth().functions();
// This function runs before any sign-in operation.
exports.beforeSignIn = authFunctions.beforeSignInHandler((user, context) => {
if (context.credential &&
context.credential.providerId === 'saml.my-provider') {
// Get the original claims.
const claims = context.credential.claims;
// Define this function to filter out the unnecessary claims.
claims.groups = keepNeededClaims(claims.groups);
// Return only the needed claims. The claims will be propagated to the token
// payload.
return {
sessionClaims: claims,
};
}
});