OIDCを使ってGithub ActionsからAWSにアクセスする仕組みについて理解を整理してみた
はじめに
Github Actions から AWS クラウドリソースを操作するためにACCESS_KEY、SECRET_ACCESS_KEYを使う方法はもう古い、OIDCを使うのが一般的といわれましたが理解していなかったため整理しました。
検証環境
今回取り上げる CI/CD ( ビルド ) パイプライン は Github Actions に限定しています。
クラウドプロバイダー に関しても、 AWS に限定しています。
ただしアーキテクチャはCI/CDパイプラインがCircleCIやCloudBuild、クラウドプロバイダーがGCPやAzureに置き換えられたとしても適用可能なものと考えられます。
パスワードやアクセストークンを使ってGithub Actionsからクラウドへ認証する方法の問題点
Github ActionsのようなCI/CDパイプラインから、クラウド環境 ( AWS、Azure、GCP... ) にアクセスするケースは多いはずです。
ビルドされたソフトウェアやコンテナイメージはいずれデプロイされますし、デプロイ環境としてクラウド環境 ( ex: AWS ) が利用されることが多いからです。
Github Actionsがクラウド上のリソースにアクセスするために、事前にパスワードやアクセストークンといった認証情報を取得しておく必要があります。
これらの認証情報はGithubの secrets に保存することが多いでしょう。
Github Actionsワークフローは実行のたびに secrets から認証情報を取り出し、これを使ってクラウド環境へのアクセスを可能にします。
しかし、このような secrets を使う方法には問題があります。
以下のような手間が発生します。
- クラウド上のIAMサービスで認証情報を生成する
- 生成した認証情報をGithubのsecretsに登録する
つまり、認証情報が二重管理されることになります 。
OIDC ( OpenID Connect ) を使うメリット
そこで登場するのがOIDC ( OpenID Connect ) です。
OIDCを使うと、Github Actionsワークフローがクラウドプロバイダーに対して要求するアクセストークンは、有効期限の短いものとなります。
もちろんOIDC認証による認証をサポートされているクラウドプロバイダーでしか利用できない方式ではありますが、現在はほとんどのクラウドプロバイダー ( AWS、Azune、GCP、HashiCorp Vault... ) がOIDCをサポートしています。
Github Acitonsワークフロー上でOIDCを使うと、以下のようなセキュリティに関するメリットが生まれます。
- クラウドプロバイダーの認証情報が不要になる- 「クラウドのIAMサービス」と「Github の secrets 」 の両方に認証情報が存在し、これを保存・管理し続ける必要がなくなります。
 
- 認証・認可の管理が容易になる- クラウドプロバイダー側で認証・認可について粒度の細かい設定ができます。
 
- 認証のローテーション- 生存期間の短いアクセストークンを発行できます。
- 多くの場合、Github Actions ワークフロー内のそれぞれジョブ実行ごとにアクセストークンを払い出します。
- ジョブ実行後に自動的に破棄されます。
 
Github ActionsからクラウドプロバイダーへのOIDC認証の流れ
以下の図は、 Github Actionsとクラウドプロバイダー間のOIDC認証の処理の流れを示しています。
     Cloud Provider                                    Github
    +---------------------------+                     +-----------------------------------+
    |                           |                     |                                   |
    |  +- ODIC Trust --------+  |                     |  +-----------------------------+  |
    |  | Filters             | <------ (2) JWT---------- | Github Actions Workflow Job |  |
    |  | Roles               | ------- (3) AccessToken-> |                             |  |
    |  | Resoueces           | <------ (4) ------------- |                             |  |
    |  |                     |  |                     |  |                             |  |
    |  | (Public Key)        |  |                     |  |                             |  |
    |  +---------------------+  |                     |  +-----------------------------+  |
    |       ^                   |                     |    (1) | ^                        |
    |       |                   |                     |        v | OIDC Token (JWT)       |
    |       |                   |                     |  +-----------------------------+  |
    |       |                   |                     |  | Github OIDC Provider        |  |
    |       |                   |                     |  |                             |  |
    |       |                   |                     |  | (Private Key)               |  |
    |       +-------------------+------------------------- (Public Key)                |  |
    |                           |                     |  +-----------------------------+  |
    +---------------------------+                     +-----------------------------------+- 事前に、クラウドプロバイダーで、 OIDC Trust を作成します。
- "OIDC Trust" が何かがはっきりつかめなかったのですが、以下の3つの組み合わせを内包するものと解釈しました。
- Fitlers ( Github の「誰」からの操作を許可するか、例えば Github Actions ワークフローが実行されるリポジトリ名など )
- Resources ( AWS クラウド上の「何」の操作を許可するか、例えばS3など )
- Roles ( AWS クラウド上の「どのような」の操作を許可するか、例えば書き込みなど )
 
 
- "OIDC Trust" が何かがはっきりつかめなかったのですが、以下の3つの組み合わせを内包するものと解釈しました。
- Github Actionsワークフロー内の各ジョブが実行されるたびに、Github OIDC Provider に対してOIDCトークンの生成を依頼します
- トークンの実態は JWT ( JSON Web Toke ) です。
 ★これ以降、OIDCトークンのことをJWTと記載することにします
- JWT には 実行中のGithub Actions ワークフロー、ジョブ、ステップ ( Action ) を特定するためのIDを含んでいます。
 
- トークンの実態は JWT ( JSON Web Toke ) です。
- JWT を クラウドに渡します。
- クラウドは受け取ったJWTペイロード、署名をチェック ( Githubサイト上で公開されている公開鍵を使うが詳細は割愛 ) し問題なければアクセストークンを払い出します。
- このアクセストークンは実行中のジョブに限って利用することができます。
 
- AWS リソースにアクセスする際には、アクセストークンをセットで送信します。
OIDC Trust の設定方法
いくらJWTの署名チェックに問題がなかったとしても、 クラウドが無条件に応答してアクセストークンを返却してしまうとセキュリティ上問題があります。
許可されたGithubリポジトリ や 許可されたGithub Actionsワークフロー から要求された場合のみ、アクセストークンを返却するようにしたいところです。
これを実現するためには、クラウド上のOIDC Trustにある Filters を設定します。
Filters を設定することで、
クラウドプロバイダーはアクセストークンの応答時に受け取ったJWTペイロード(本体部分)の aud や sub フィールドの値が Filters の条件と一致しているかをチェックします。
設定方法はクラウドプロバイダーごとに異なるため、それぞれのクラウドプロバイダーのドキュメントを参照する必要があります 。
JWTペイロードに可能されているフィールドについて、もう少し深く見ていきましょう。
JWT ( OIDCトークン ) の構成
Github Actionsワークフロー内の各ジョブは、Github OIDC Provider に対してOIDCトークンを要求します。
Github ODIC Provider により自動的に生成されたJWTが返却されます。
ここで生成されたJWTは各ジョブごとにユニークなものとなります。
ジョブ実行中にクラウドプロバイダーの呼び出しが行われる際には、リクエストにJWTが一緒に送られます。
クラウドプロバイダーは受信したJWTの検証とともに、JWTペイロード内にある aud や sub フィールドの値が、事前にtrustに設定されている許可情報にマッチするかを検証します。aud や sub フィールドはアクセス元の判定のために使うことができるというわけです。
- audフィールド- Githubリポジトリの組織またはリポジトリの所有者のURLとなります。
 特定の組織にのみAWSリソースに対するアクセスを許可するといった際に利用できます。
 
- Githubリポジトリの組織またはリポジトリの所有者のURLとなります。
- subフィールド- Github の組織、リポジトリ、ブランチ、Github Workflowの実行中ジョブ環境名などを :区切りで連結した情報です。
- どのような値が参照できるかについての詳細は以下のページを参照
- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims
 
- Github の組織、リポジトリ、ブランチ、Github Workflowの実行中ジョブ環境名などを 
JWT のコードの具体例を掲載しておきましょう。
正式なJWTのフォーマットは3つのブロックから構成されますが、ここでは1つ目、2つ目のブロックのみ掲載 ( 3つ目の署名ブロックを割愛 ) します。
2つ目のブロックがJWTペイロード ( 本体部分 ) です。
sub の設定値は repo:octo-org/octo-repo:environment:prod となっており、以下のことがわかります。
- repo:octo-org/octo-repoの部分から対象のリポジトリ名が特定できます
- environemnt:prodの部分から、ジョブの実行環境名が特定できます
{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "example-thumbprint",
  "kid": "example-key-id"
}
{
  "jti": "example-id",
  "sub": "repo:octo-org/octo-repo:environment:prod",
  "environment": "prod",
  "aud": "https://github.com/octo-org",
  "ref": "refs/heads/main",
  "sha": "example-sha",
  "repository": "octo-org/octo-repo",
  "repository_owner": "octo-org",
  "actor_id": "12",
  "repository_visibility": "private",
  "repository_id": "74",
  "repository_owner_id": "65",
  "run_id": "example-run-id",
  "run_number": "10",
  "run_attempt": "2",
  "runner_environment": "github-hosted"
  "actor": "octocat",
  "workflow": "example-workflow",
  "head_ref": "",
  "base_ref": "",
  "event_name": "workflow_dispatch",
  "ref_type": "branch",
  "job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main",
  "iss": "https://token.actions.githubusercontent.com",
  "nbf": 1632492967,
  "exp": 1632493867,
  "iat": 1632493567
}ひとこと
ストーリーの登場人物を把握できました。
改めてサンプル実装に進んでみたいと思います。






 Go言語用ポストイット
Go言語用ポストイット
ディスカッション
コメント一覧
まだ、コメントがありません