Guide

Workload Identity Federation

GitHub Actions・Google Cloud Run・AWS Lambda などの外部ワークロードが発行する短命トークンを使って、 静的なアクセストークンなしで CRISIS API にアクセスする仕組みです。 シークレットをリポジトリや環境変数に保存する必要がなくなります。

概要

従来の認証では、静的なアクセストークンを Secret Manager などのシークレット管理サービスに保存し、CI/CD から取得して利用していました。 静的トークンは一度漏洩すると無期限に悪用される可能性があります。

従来の認証フロー

CI/CD ワークロード
  ─── 静的トークン(無期限)を Secret Manager 等から取得
  ──────────────────────────▶ CRISIS API

WIF による認証フロー

CI/CD ワークロード
  ─── IdP から短命トークンを自動取得
  ─── Token Exchange で CRISIS トークンを取得
  ──────────────────────────▶ CRISIS API
  • シークレット不要 — 静的トークンをリポジトリや環境変数に保存する必要がありません
  • 短命トークン — 発行されるアクセストークンの有効期限は最大 1 時間
  • 漏洩リスクの低減 — トークンが漏洩しても、被害範囲が時間的に限定されます
  • マルチプロバイダー対応 — OIDC 対応プロバイダー(GitHub Actions, Google Cloud, Azure AD, AWS 等)をサポート

仕組み

CRISIS は受け取った外部トークンの内容を Federated Credential の設定値と照合し、 すべての条件を満たした場合に対応するサービスアカウントのアクセストークンを発行します。

検証フロー

CRISIS は受け取った OIDC JWT に対して、以下の条件をすべて検証します。

issuer_url — JWT の iss クレームと一致すること
audience — JWT の aud クレームと一致すること
subject_filter — JWT の sub クレームと一致すること
attribute_condition(設定した場合) — 指定した式が true を返すこと

条件を満たさない場合に返るエラーの詳細はエラーレスポンスを参照してください。

セットアップ

1. サービスアカウントの作成

CRISIS の管理画面または API でサービスアカウントを作成します。 WIF を使うサービスアカウントは、従来の静的アクセストークンとの共存も可能です。 すでに適切なサービスアカウントがある場合は、この手順をスキップできます。

2. Federated Credential の作成

サービスアカウントに Federated Credential を登録します。 これにより、指定した外部 IdP が認証するワークロードが、そのサービスアカウントとして認証できるようになります。

GitHub Actions の場合

POST /organizations/{organizationId}/service_accounts/{serviceAccountId}/federated_credentials

{
  "display_name": "GitHub Actions (my-org/my-repo)",
  "issuer_url": "https://token.actions.githubusercontent.com",
  "subject_filter": "repo:my-org/my-repo:ref:refs/heads/main",
  "audience": "https://crisis.jp",
  "status": "ACTIVE"
}

AWS の場合

AWS IAM Outbound Identity Federation(GetWebIdentityToken API)を使用します。 AWS STS がワークロードの IAM ID をアサートする OIDC JWT を発行します。

POST /organizations/{organizationId}/service_accounts/{serviceAccountId}/federated_credentials

{
  "display_name": "AWS Lambda (crisis-integration)",
  "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
  "subject_filter": "arn:aws:iam::123456789012:role/crisis-integration-role",
  "audience": "https://crisis.jp",
  "status": "ACTIVE"
}

issuer_url には AWS アカウント固有の STS 発行者 URL を指定します。 IAM コンソールの「アカウント設定」または CLI で確認できます(詳細は使用例: AWS Lambdaを参照)。 subject_filter にはワークロードの IAM ロール ARN(GetWebIdentityToken が発行する JWT の sub クレーム)を指定します。

フィールド

display_name (必須) — 管理用のラベル
issuer_url (必須) — IdP の発行者 URL(https:// 必須、作成時に OIDC Discovery で検証)
subject_filter (必須) — 信頼する sub クレームの値(完全一致またはワイルドカード * 接尾)
audience — 期待する aud クレーム値。省略時は https://crisis.jp
attribute_condition — 追加の属性条件式(後述
statusACTIVE または INACTIVE。省略時は ACTIVE

注意: issuer_urlsubject_filteraudience は作成後は変更できません。 変更が必要な場合は削除して再作成してください。

attribute_condition について

sub 以外のクレームで追加の制約をかけたい場合に使用します。 構文は expr-lang に準拠した式言語をサポートしています。

利用可能な演算子
==, != — 等価・非等価
startsWith, endsWith, contains — 文字列マッチ
in — 配列に含まれるか: account in ["123", "456"]
&&, || — 論理 AND / OR
!() — 否定
// 単一条件: issuer が一致すること
"attribute_condition": "iss == \"https://token.actions.githubusercontent.com\""

// 複合条件: issuer と ref ブランチの両方を検証
"attribute_condition": "iss == \"https://token.actions.githubusercontent.com\" && ref == \"refs/heads/main\""

// 部分一致: email がドメインで終わること
"attribute_condition": "email endsWith \"@example.com\""
例(AWS STS トークン)

AWS STS GetWebIdentityToken が発行する JWT には、標準クレーム(sub = IAM ロール ARN)に加えて、 https://sts.amazonaws.com/ ネームスペースのクレームが含まれます。 これらを attribute_condition で参照できます:

sub — IAM ロール ARN(例: "arn:aws:iam::123456789012:role/my-role"
["https://sts.amazonaws.com/"]["aws_account"] — AWS アカウント ID(例: "123456789012"
["https://sts.amazonaws.com/"]["principal_id"] — プリンシパル ID(例: "AROAEXAMPLE"
// 特定の AWS アカウントのみ許可
"attribute_condition": "[\"https://sts.amazonaws.com/\"][\"aws_account\"] == \"123456789012\""

// 複数アカウントを許可
"attribute_condition": "[\"https://sts.amazonaws.com/\"][\"aws_account\"] in [\"123456789012\", \"987654321098\"]"

// IAM ロール ARN のプレフィックスで制約
"attribute_condition": "sub startsWith \"arn:aws:iam::123456789012:role/\""

利用できるクレーム名は IdP ごとに異なります。GitHub Actions の場合は OIDC トークンの仕様 を参照してください。

作成レスポンスに含まれる idcredentialId です。 トークン交換時のエンドポイント URL に使用します。

トークン交換

外部 IdP から取得した OIDC JWT を提示し、CRISIS アクセストークンと交換します。 エンドポイントは RFC 8693(OAuth 2.0 Token Exchange)に準拠しています。

エンドポイント

POST https://auth.crisis.jp/workload-identity/{credentialId}/token
Content-Type: application/x-www-form-urlencoded

リクエストパラメータ(form-urlencoded)

grant_typeurn:ietf:params:oauth:grant-type:token-exchange(固定値)
subject_token — 外部 IdP が発行した署名済み OIDC JWT
subject_token_typeurn:ietf:params:oauth:token-type:jwt(固定値)

レスポンス

{
  "access_token": "<CRISIS アクセストークン>",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600
}

取得した access_token を Bearer トークンとして通常どおり CRISIS API に渡します。 有効期限は 1 時間(3600 秒) で、トークン交換エンドポイントは認証不要・レート制限の対象です。

使用例: GitHub Actions

GitHub Actions の OIDC トークンを使って CRISIS API を呼び出す例です。 リポジトリにシークレットを一切設定する必要がありません。

Federated Credential の設定

{
  "display_name": "GitHub Actions (my-org/my-repo main branch)",
  "issuer_url": "https://token.actions.githubusercontent.com",
  "subject_filter": "repo:my-org/my-repo:ref:refs/heads/main",
  "audience": "https://crisis.jp"
}

subject_filter の値は GitHub の OIDC トークン仕様 に基づきます。

repo:my-org/my-repo:ref:refs/heads/mainmain ブランチのプッシュ
repo:my-org/my-repo:ref:refs/tags/v* — タグプッシュ
repo:my-org/my-repo:environment:production — Environment production のジョブ
repo:my-org/my-repo:pull_request — プルリクエスト

attribute_condition の推奨設定

subject_filter に加えて、repository_owner クレームで組織を制約することを推奨します。 これにより、万が一 subject_filter のワイルドカードが広すぎる場合でも、 信頼する組織外のリポジトリからのトークン交換を防止できます。

// リポジトリオーナー(組織)を制約
"attribute_condition": "repository_owner == \"my-org\""

// 組織 + 特定の環境に制約
"attribute_condition": "repository_owner == \"my-org\" && environment == \"production\""

ワークフロー例

name: Deploy

on:
  push:
    branches: [main]

permissions:
  id-token: write   # OIDC トークンの取得に必要
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Get OIDC token and exchange for CRISIS token
        run: |
          # GitHub Actions から OIDC JWT を取得
          OIDC_JWT=$(curl -sSfL \
            -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            -H "Accept: application/json; api-version=2.0" \
            "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=https://crisis.jp" \
            | jq -r '.value')

          # CRISIS Token Exchange
          CRISIS_TOKEN=$(curl -sSf \
            -X POST \
            -H "Content-Type: application/x-www-form-urlencoded" \
            --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
            --data-urlencode "subject_token=${OIDC_JWT}" \
            --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
            "https://auth.crisis.jp/workload-identity/${{ vars.CRISIS_CREDENTIAL_ID }}/token" \
            | jq -r '.access_token')
          echo "::add-mask::${CRISIS_TOKEN}"

          # 同一ステップ内で API を呼び出し、トークンを外部に渡さない
          curl -sSf \
            -H "Authorization: Bearer ${CRISIS_TOKEN}" \
            https://api.crisis.jp/v1/organizations/${{ vars.CRISIS_ORG_ID }}/incidents

セキュリティ上のヒント: 取得したアクセストークンは可能な限り同一ステップ内で使い切るのが安全です。 他ステップへ渡す必要がある場合は $GITHUB_OUTPUT ではなく $GITHUB_ENV(同一 Job 内に限定)を使用し、artifact として保存しないでください。

設定すべき Variables

CRISIS_CREDENTIAL_ID — 作成した FederatedCredential の id
CRISIS_ORG_ID — 組織 ID

シークレットは一切不要です。Variables(非機密設定値)のみで動作します。

使用例: Google Cloud

Google Cloud のサービスアカウントが発行する ID Token を使う例です。 Cloud Run などのマネージド環境で、Metadata Server からトークンを取得できます。

Federated Credential の設定

{
  "display_name": "Google Cloud Run (my-service)",
  "issuer_url": "https://accounts.google.com",
  "subject_filter": "123456789012345678901",
  "audience": "https://crisis.jp"
}

subject_filter には Google Cloud サービスアカウントの Unique ID(21 桁前後の数字)を指定します。 Google Cloud Console の「IAM と管理 → サービスアカウント」で対象サービスアカウントの詳細画面を開き、 「一意の ID」欄に表示される値です。

attribute_condition の推奨設定

サービスアカウントが削除・再作成された場合、sub(Unique ID)は変わりますが、 同じメールアドレスが再利用される可能性があります。 逆に sub のみでの制約では、意図したサービスアカウントであることの可読性が低くなります。 email クレームを attribute_condition で併用することで、 より明確かつ安全な制約が可能です。

// email クレームでサービスアカウントを明示的に検証
"attribute_condition": "email == \"my-service@my-project.iam.gserviceaccount.com\""

// email ドメインでプロジェクト内の全 SA を許可
"attribute_condition": "email endsWith \"@my-project.iam.gserviceaccount.com\""

注意: email クレームを取得するには、Metadata Server へのリクエストに format=full パラメータを付与する必要があります(下記コード例参照)。 format=full を付けない場合、トークンに email クレームは含まれません。

アクセストークンの取得

# Cloud Run 上のサービスから実行する場合
# CRISIS_CREDENTIAL_ID は環境変数として事前に設定しておく
# format=full を付けると email クレームがトークンに含まれる
GOOGLE_TOKEN=$(curl -sSf \
  -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://crisis.jp&format=full")

CRISIS_TOKEN=$(curl -sSf \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token=${GOOGLE_TOKEN}" \
  --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
  "https://auth.crisis.jp/workload-identity/${CRISIS_CREDENTIAL_ID}/token" \
  | jq -r '.access_token')

使用例: Azure

Azure のマネージド ID(Managed Identity)が発行する OIDC トークンを使って CRISIS API を呼び出す例です。 CRISIS のシークレットを Azure に保存する必要はありません。

Federated Credential の設定

{
  "display_name": "Azure Functions (crisis-integration)",
  "issuer_url": "https://login.microsoftonline.com/{tenant-id}/v2.0",
  "subject_filter": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "audience": "api://crisis-wif"
}

issuer_url には Azure AD テナントの OIDC 発行者 URL を指定します。 subject_filter には Managed Identity の Object ID(Principal ID) を指定します。 Azure Portal の「マネージド ID」画面で確認できます。

issuer_url の形式について:

  • https://login.microsoftonline.com/{tenant-id}/v2.0 — v2.0 エンドポイント(推奨)
  • https://sts.windows.net/{tenant-id}/ — v1.0 エンドポイント(レガシー)

発行されるトークンの iss クレームと一致する方を指定してください。 Azure Functions / App Service / AKS の新規構成では通常 v2.0 です。

attribute_condition の例

// Object ID(sub)で制約する場合は subject_filter で十分だが、
// テナント ID でも二重に検証したい場合:
"attribute_condition": "tid == \"your-tenant-id\""

// 複数のマネージド ID を許可する場合:
"attribute_condition": "oid in [\"object-id-1\", \"object-id-2\"]"

Azure Functions の例(Python)

import json
import os
import urllib.request
import urllib.parse

import azure.functions as func
from azure.identity import ManagedIdentityCredential

CREDENTIAL_ID = os.environ["CRISIS_CREDENTIAL_ID"]
AUDIENCE = os.environ.get("CRISIS_AUDIENCE", "api://crisis-wif")


def main(req: func.HttpRequest) -> func.HttpResponse:
    # Step 1: Managed Identity から OIDC トークンを取得
    credential = ManagedIdentityCredential()
    azure_token = credential.get_token(AUDIENCE).token

    # Step 2: CRISIS Token Exchange
    crisis_token = exchange_token(CREDENTIAL_ID, azure_token)

    # Step 3: CRISIS API を呼び出す
    api_req = urllib.request.Request(
        "https://api.crisis.jp/v1/organizations/YOUR_ORG_ID/incidents",
        headers={"Authorization": f"Bearer {crisis_token}"},
    )
    with urllib.request.urlopen(api_req) as resp:
        data = json.loads(resp.read())

    return func.HttpResponse(json.dumps(data), mimetype="application/json")


def exchange_token(credential_id: str, subject_token: str) -> str:
    """CRISIS Token Exchange エンドポイントを呼び出す"""
    data = urllib.parse.urlencode({
        "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
        "subject_token": subject_token,
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
    }).encode()

    req = urllib.request.Request(
        f"https://auth.crisis.jp/workload-identity/{credential_id}/token",
        data=data,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["access_token"]

Azure Functions の例(Node.js)

import { app } from "@azure/functions";
import { ManagedIdentityCredential } from "@azure/identity";

const CREDENTIAL_ID = process.env.CRISIS_CREDENTIAL_ID;
const AUDIENCE = process.env.CRISIS_AUDIENCE || "api://crisis-wif";

app.http("crisisProxy", {
  methods: ["GET"],
  handler: async (request, context) => {
    // Step 1: Managed Identity から OIDC トークンを取得
    const credential = new ManagedIdentityCredential();
    const { token: azureToken } = await credential.getToken(AUDIENCE);

    // Step 2: CRISIS Token Exchange
    const body = new URLSearchParams({
      grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
      subject_token: azureToken,
      subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
    });

    const tokenRes = await fetch(
      `https://auth.crisis.jp/workload-identity/${CREDENTIAL_ID}/token`,
      { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }
    );
    const { access_token } = await tokenRes.json();

    // Step 3: CRISIS API を呼び出す
    const apiRes = await fetch(
      "https://api.crisis.jp/v1/organizations/YOUR_ORG_ID/incidents",
      { headers: { Authorization: `Bearer ${access_token}` } }
    );
    return { jsonBody: await apiRes.json() };
  },
});

AKS (Azure Kubernetes Service) の例

AKS Workload Identity を使う場合、Pod に自動マウントされる Projected Service Account Token を利用します。 Azure Workload Identity webhook がトークンファイルのパスを環境変数で注入します。

# AKS Pod 内のシェルスクリプト例

# Azure Workload Identity が注入する環境変数:
#   AZURE_FEDERATED_TOKEN_FILE — トークンファイルのパス
#   AZURE_AUTHORITY_HOST — https://login.microsoftonline.com/
#   AZURE_TENANT_ID — テナント ID
#   AZURE_CLIENT_ID — マネージド ID のクライアント ID

# Projected SA Token を読み取り(audience は Azure AD 向け)、
# Azure AD から ID Token を取得し、CRISIS に交換する

# azure-identity SDK を使う場合は自動的にファイルから読み取る
# 手動で行う場合:
AZURE_TOKEN=$(cat "$AZURE_FEDERATED_TOKEN_FILE")

# Azure AD にトークン交換して ID Token を取得
AZURE_ID_TOKEN=$(curl -sSf -X POST \
  "${AZURE_AUTHORITY_HOST}${AZURE_TENANT_ID}/oauth2/v2.0/token" \
  -d "client_id=${AZURE_CLIENT_ID}" \
  -d "scope=api://crisis-wif/.default" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${AZURE_TOKEN}" \
  -d "grant_type=client_credentials" \
  | jq -r '.access_token')

# CRISIS Token Exchange
CRISIS_TOKEN=$(curl -sSf -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token=${AZURE_ID_TOKEN}" \
  --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
  "https://auth.crisis.jp/workload-identity/${CRISIS_CREDENTIAL_ID}/token" \
  | jq -r '.access_token')

curl -H "Authorization: Bearer ${CRISIS_TOKEN}" \
  https://api.crisis.jp/v1/organizations/${CRISIS_ORG_ID}/incidents

必要なアプリケーション設定

CRISIS_CREDENTIAL_ID — 作成した FederatedCredential の id
CRISIS_AUDIENCE — Federated Credential に設定した audience(デフォルト: api://crisis-wif

Azure 側の前提条件

System-assigned または User-assigned Managed Identity が Azure Functions / App Service / AKS に割り当てられていること
App Registration(AKS Workload Identity の場合)api://crisis-wif を Audience として受け付ける App Registration が Azure AD に登録されていること

シークレット不要: CRISIS のアクセストークンや API キーを Azure Key Vault やアプリケーション設定に保存する必要は一切ありません。 Azure のマネージド ID 自体が CRISIS への認証手段となります。

使用例: AWS Lambda

AWS Lambda 関数から CRISIS API を呼び出す例です。 AWS IAM Outbound Identity Federation(GetWebIdentityToken API)で OIDC JWT を取得し、 他のプロバイダーと同じトークン交換フローで CRISIS アクセストークンを取得します。 CRISIS のシークレットを AWS に保存する必要はありません。

AWS 側の前提条件

Outbound Web Identity Federation の有効化 — AWS アカウント単位で有効にする必要があります。IAM コンソールの「アカウント設定」で有効化するか、CLI で実行します。 手順の詳細は アウトバウンド ID フェデレーションの開始方法(AWS IAM ユーザーガイド) および AWS IAM アウトバウンド ID フェデレーションを使用して、外部サービスへのアクセスを簡素化(AWS 公式ブログ) を参照してください:
aws iam enable-outbound-web-identity-federation
IAM ロールへの権限付与 — ワークロードの IAM ロールに sts:GetWebIdentityToken の呼び出し権限を付与します:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["sts:GetWebIdentityToken"],
      "Resource": "*"
    }
  ]
}
STS 発行者 URL の確認 — Outbound WIF を有効化すると、アカウント固有の STS 発行者 URL(https://<uuid>.tokens.sts.global.api.aws)が割り当てられます。 IAM コンソールの「アカウント設定」の「Get Token Issuer URL」欄、または CLI で確認できます:
aws iam get-outbound-web-identity-federation-info \
  --query 'IssuerUrl' --output text

Federated Credential の設定

{
  "display_name": "AWS Lambda (crisis-integration)",
  "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
  "subject_filter": "arn:aws:iam::123456789012:role/crisis-integration-role",
  "audience": "https://crisis.jp"
}

issuer_url には上記で確認した AWS アカウント固有の STS 発行者 URL を指定します。 subject_filter には Lambda 実行ロールの IAM ロール ARN を指定します (GetWebIdentityToken が発行する JWT の sub クレーム)。

arn:aws:iam::123456789012:role/my-role — 特定ロールに制限(推奨)
arn:aws:iam::123456789012:role/* — アカウント内の全ロールを許可(非推奨)

Lambda 関数の例(Python)

import json
import os
import urllib.request
import urllib.parse

import boto3

CREDENTIAL_ID = os.environ["CRISIS_CREDENTIAL_ID"]
REGION = os.environ.get("AWS_REGION", "us-east-1")


def lambda_handler(event, context):
    # Step 1: STS から OIDC JWT を取得
    sts = boto3.client("sts", region_name=REGION)
    resp = sts.get_web_identity_token(
        Audience=["https://crisis.jp"],
        SigningAlgorithm="RS256",
        DurationSeconds=900,
    )
    subject_token = resp["WebIdentityToken"]

    # Step 2: CRISIS Token Exchange
    crisis_token = exchange_token(CREDENTIAL_ID, subject_token)

    # Step 3: CRISIS API を呼び出す
    req = urllib.request.Request(
        "https://api.crisis.jp/v1/organizations/YOUR_ORG_ID/incidents",
        headers={"Authorization": f"Bearer {crisis_token}"},
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())


def exchange_token(credential_id: str, subject_token: str) -> str:
    """CRISIS Token Exchange エンドポイントを呼び出す"""
    data = urllib.parse.urlencode({
        "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
        "subject_token": subject_token,
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
    }).encode()

    req = urllib.request.Request(
        f"https://auth.crisis.jp/workload-identity/{credential_id}/token",
        data=data,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["access_token"]

Lambda 関数の例(Node.js)

import { STSClient, GetWebIdentityTokenCommand } from "@aws-sdk/client-sts";

const CREDENTIAL_ID = process.env.CRISIS_CREDENTIAL_ID;
const REGION = process.env.AWS_REGION || "us-east-1";

export async function handler(event) {
  // Step 1: STS から OIDC JWT を取得
  const sts = new STSClient({ region: REGION });
  const { WebIdentityToken } = await sts.send(
    new GetWebIdentityTokenCommand({
      Audience: ["https://crisis.jp"],
      SigningAlgorithm: "RS256",
      DurationSeconds: 900,
    })
  );

  // Step 2: CRISIS Token Exchange
  const body = new URLSearchParams({
    grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
    subject_token: WebIdentityToken,
    subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
  });

  const tokenRes = await fetch(
    `https://auth.crisis.jp/workload-identity/${CREDENTIAL_ID}/token`,
    { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }
  );
  const { access_token } = await tokenRes.json();

  // Step 3: CRISIS API を呼び出す
  const apiRes = await fetch(
    "https://api.crisis.jp/v1/organizations/YOUR_ORG_ID/incidents",
    { headers: { Authorization: `Bearer ${access_token}` } }
  );
  return await apiRes.json();
}

必要な Lambda 環境変数

CRISIS_CREDENTIAL_ID — 作成した FederatedCredential の id

IAM ロールのポリシー

Lambda の実行ロールには sts:GetWebIdentityToken の呼び出し権限が必要です。 Audience 条件キーで許可する audience を制限することを推奨します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["sts:GetWebIdentityToken"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "sts:Audience": "https://crisis.jp"
        }
      }
    }
  ]
}

注意: GetWebIdentityToken はリージョナル STS エンドポイントでのみ利用可能です。 STS クライアントのリージョンを明示的に指定してください(例: us-east-1)。

シークレット不要: CRISIS のアクセストークンや API キーを AWS Secrets Manager や Lambda の環境変数に保存する必要は一切ありません。 Lambda の実行ロール自体が CRISIS への認証手段となります。

エラーレスポンス

トークン交換が失敗した場合、RFC 6749 §5.2 に準拠した JSON 形式でエラーが返ります。 error フィールドの値で失敗理由を判別できます。

{
  "error": "invalid_token",
  "error_description": "Token verification failed"
}
400 invalid_request — リクエストパラメータ不正(grant_type 間違い、subject_token_type 未対応など)
401 invalid_token — JWT の署名検証失敗・有効期限切れ・iss/aud 不一致
403 access_deniedsubsubject_filter に不一致、 attribute_condition 不満足

セキュリティのベストプラクティス

  • 最小権限のサービスアカウントを使う — WIF 用のサービスアカウントには、ワークロードが必要とする最小限のロールのみを付与してください。 発行されるアクセストークンはそのサービスアカウントの権限範囲に限定されます
  • 短命トークンを活かす — 発行されるアクセストークンの有効期限は最大 1 時間です。 トークンをファイルやデータベースに永続化せず、必要なタイミングで都度交換してください
  • subject_filter を可能な限り限定する — ワイルドカード(*)の使用範囲を最小にし、特定のワークロードのみがトークン交換を行えるように制約してください
  • attribute_condition で追加の制約をかけるsubject_filter だけでは不十分な場合、環境名・ブランチ・アカウント ID などの追加条件を設定できます
  • AWS: Outbound Identity Federation は必要なアカウントのみで有効化する — Outbound WIF はアカウント単位の設定です。 必要なアカウントでのみ有効化し、不要なアカウントでは無効のままにしてください
  • AWS: IAM ポリシーで Audience を制限するsts:GetWebIdentityToken の IAM ポリシーで sts:Audience 条件キーを使用し、 許可する audience を制限することで、意図しないトークン発行を防止できます
  • 不要になった Credential は削除する — 使われなくなった Federated Credential は速やかに削除し、認証経路を最小限に保ってください

次のステップ