OIDC についてまとめています。

OIDC の利点は長期認証情報の排除です(デフォルトで一時認証情報の有効期限は 1 時間です)。
さらに使用元も制限できます(例えば CircleCI の特定のプロジェクトからの実行のみ許可)。

本記事では接続元を CircleCI として進めます。

bot ユーザー + アクセスキーの問題点

問題 内容
長期認証情報 アクセスキーは手動で削除するまで有効。漏洩しても気づきにくい
漏洩リスク CircleCI の環境変数に保存 → CircleCI が侵害されたら即アウト
ローテーション負担 定期的に手動で交換 + CircleCI の環境変数も更新が必要
使用元の制限が弱い IAM ポリシーで IP 制限などはできるが、CI のプロジェクト単位では制限できない。IP 制限などを設定していなければ、漏洩したキーは世界中どこからでも使える

OIDC が解決する点

OIDC では CircleCI がジョブ実行ごとに JWT(JSON Web Token)トークン(一時的な身元証明書)を発行し、AWS STS ( AWS Security Token Service )がそれと引き換えに 有効期限付きの一時認証情報 を返します。

CircleCI Job
    |
    | (1) JWT トークン発行( CircleCI が署名)
    |
    ▼
AWS STS ( AssumeRoleWithWebIdentity )
    |
    | (2) JWT を検証 → 一時認証情報を発行(有効期限: 1 時間等)
    ▼
AWS リソース操作
    |
    | (3) 有効期限切れで自動失効
    |
    ▼
(終了)
比較 bot ユーザー + アクセスキー OIDC
認証情報の寿命 永続(手動削除まで) 短期(自動失効)
CircleCI に保存するシークレット あり(アクセスキー) なし
漏洩時の影響 失効させるまで悪用され続ける すぐ期限切れ
使用元の制限 IP 等では可能だが、CircleCI の project 単位では不可 可(特定の org / project のみ許可)
ローテーション 手動で必要 不要

条件の絞り込み例(trust policy の Condition):

"Condition": {
  "StringEquals": {
    "oidc.circleci.com/org/xxx:aud": "xxx"
  },
  "StringLike": {
    "oidc.circleci.com/org/xxx:sub": "org/xxx/project/yyy/user/*"
  }
}
  • xxx:CircleCI の Organization ID
  • yyy:CircleCI の Project ID

NOTE
ブランチ単位の制限はできません
CircleCI の suborg/<Organization ID>/project/<Project ID>/user/<User ID> の形式で、ブランチ名は含まれません。そのため AWS の trust policy で「特定のブランチからのみ許可」とすることはできず、絞り込めるのは project 単位までです(GitHub Actions の sub には ref:refs/heads/... が含まれるためブランチ単位で制限できます)。

Appendix: JWT トークン

JWT とは

JWT(JSON Web Token)は、2 者間で安全に通信するための 署名付き JSON です。OIDC では接続元(CircleCI)の身元を証明するために使います。

構造

JWT は . で区切られた 3 つの部分から構成されます。

eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL29pZGMuY2lyY2xlY2kuY29tL29yZy94eHgifQ.signature
      ▲                          ▲                                                      ▲
   Header                     Payload                                              Signature
部分 内容
Header 署名アルゴリズム(例:RS256)とトークン種別
Payload クレーム(誰が・どこから・いつまで有効か)
Signature Header + Payload を秘密鍵で署名したもの

各部分は Base64 URL エンコードされているだけなので、デコードすれば中身を読めます(ただし改ざんは署名で検出できます)。

Payload のクレーム例(CircleCI)

以下は主要なクレームの抜粋です(実際のトークンにはこのほかのクレームも含まれます)。

{
  "iss": "https://oidc.circleci.com/org/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "sub": "org/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/project/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/user/zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
  "aud": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "exp": 1713800000,
  "iat": 1713796400
}
クレーム 意味
iss(Issuer) トークンの発行者(CircleCI の OIDC エンドポイント。Organization ごとに URL が異なる)
sub(Subject) トークンの主体(どの org / project / user か)
aud(Audience) トークンの受取先。CircleCI では Organization ID(AWS の OIDC プロバイダーに設定する Audience と一致させる)
exp(Expiration) 有効期限(Unix タイムスタンプ)
iat(Issued At) 発行日時

AWS が JWT を信頼できる理由

AWS は、IAM に登録した OIDC プロバイダー(OIDC IdP:OpenID Connect Identity Provider)の URL(https://oidc.circleci.com/org/<Organization ID>)を元に、/.well-known/openid-configuration から辿れる JWKS(公開鍵のセット)を取得して Signature を検証します。ユーザーが公開鍵を登録するわけではありません。秘密鍵は CircleCI だけが持っているため、署名が正しければ CircleCI が発行したトークンと確認できます。

CircleCI(秘密鍵で署名)→ JWT → AWS STS(OIDC IdP の JWKS から取得した公開鍵で検証)→ OK → 一時認証情報を発行

trust policy の Condition は、この Payload のクレーム値(aud / sub)を使って「どの CircleCI project からの実行か」を絞り込んでいます。