目的
AWS IAM User のアクセスキーの代わりに OpenID Connect を用いて GitHub Actions から AWS にリソースをデプロイする手順とサンプル実装を紹介します。
全体の流れ
OpenID Connect (以下 OIDC) を用いて GitHub Actions から AWS のリソースをデプロイする全体の流れを図を示します。
以下で (1) ~ (5) のポイントを紹介します。
(1) デプロイの実行承認を得る
AWS の本番環境へのデプロイは、ブランチを制限したり、デプロイ前に実行承認を得るなどして慎重に行うべきです。GitHub には デプロイに環境の使用 という仕組みが用意されているので、デプロイ保護ルールをプライベートリポジトリに設けました。なお、プライベートリポジトリにこの保護ルールを設けるためには GitHub のプラン のうち Enterprise プランにアップグレードする必要があります。
Deployment protection rules with GitHub Actions for private or internal repositories
今回は次のように deploy-aws
という指定した個人のレビューを必須とするデプロイ保護ルールを定義しました。
上記のデプロイ保護ルールが設定されたワークフローが実行されるたびに デプロイメントのレビュー で承認または拒否が得られるまでジョブは Pending 状態で待機となります。
(2) OIDC トークンを取得する
AWS での OpenID Connect の構成 で紹介されている aws-actions/configure-aws-credentials アクションを使うことで GitHub の OIDC プロバイダから JWT (JSON Web トークン) が発行されます。以下に GitHub Actions のワークフローの実装例を紹介します。
on:
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
environment: deploy-aws
permissions:
contents: read
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/gh-oidc-deploy-aws
role-session-name: gh-oidc-${{ github.run_id }}-${{ github.run_attempt }}
aws-region: ap-northeast-1
ポイントを抜粋します。
- OIDC トークンを発行するために
permissions
にid-token:write
を明示的に追加しています - 誰もが承認を得ることなく本番環境にデプロイできないように前述の
environment: deploy-aws
でレビュー必須としています -
role-to-assume
で後述のロールを指定して一時的なアクセスキーの発行をリクエストします
(3) OIDC トークンで認証をする
前述の aws-actions/configure-aws-credentials アクション内で AWS の OIDC プロバイダに対して認証が行われます。このプロバイダは対象 AWS アカウントに対して 1 つ必要で AWS への ID プロバイダーの追加 を参考に利用者が作成します。このプロバイダを Terraform で設定する場合は次のように定義します。
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [
"sts.amazonaws.com",
]
thumbprint_list = [
"6938fd4d98bab03faadb97b34396831e3780aea1",
"1c58a3a8518e8759bf075b76b750d4f2df264fcd"
]
}
この thumbprint_list
は GitHub の OIDC プロバイダの CA 証明書のハッシュ値で OpenID Connect ID プロバイダーのサムプリントを取得する で説明されています。具体的なサムプリントは GitHub Blog に紹介されていますが、こちらのコメント を読む限り 2023 年 7 月からは GitHub と AWS との認証に証明書のサムプリントは使われなくなったので、適当な値でも aws-actions/configure-aws-credentials は動いています。以下の記事が大変参考になります。
(4) 一時的なアクセストークンを得る
前述の aws-actions/configure-aws-credentials アクション内で OIDC プロバイダへの認証が成功すると role-to-assume
で指定したロールへの一時的なアクセストークンが発行され、実行環境の環境変数にセットされます。ここで重要なのは指定したロールの信頼ポリシーの定義です。
Important
If you do not limit the condition key token.actions.githubusercontent.com:sub to a specific organization or repository, then GitHub Actions from organizations or repositories outside of your control are able to assume roles associated with the GitHub IAM IdP in your AWS account.
とあるように、信頼ポリシーの条件に必ず GitHub のリポジトリ名やその他の情報で制限をかける必要があります。条件については こちら から記法を学べますが、この条件を誤って定義してしまうと自分たちが管理していない Organization の Actions からも AWS にアクセスされる恐れがあります。上記のリンクに幾つか制限の例がありますが、例えば GitHubOrg/GitHubRepo
リポジトリの main
ブランチを起点とした GitHub Action しか許容しない場合は次のように信頼ポリシーを定義します。
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:GitHubOrg/GitHubRepo:ref:refs/heads/main"
}
}
今回はブランチではなく前述の Environements deploy-aws
で制限をかけたいので次のようにしました。
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:GitHubOrg/GitHubRepo:environment:deploy-aws"
}
}
このロールの定義はセキュリティリスクに直結する重要な設定なので OIDC プロバイダ同様、以下のように Terraform で IaC 管理し、継続的に意図しない変更がないことを自動チェックしています。
resource "aws_iam_role" "gh-oidc-deploy-aws" {
name = "gh-oidc-deploy-aws"
path = "/"
assume_role_policy = jsonencode(
{
Statement = [
{
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub" = "repo:${var.orgname}/${var.reponame}:environment:deploy-aws"
}
}
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
}
],
Version = "2012-10-17"
}
)
}
この (4) のステップが成功すると、ついに AWS への一時的なアクセスキーがワークフローの実行環境に環境変数として追加されます。
+ AWS_ACCESS_KEY_ID=***
+ AWS_DEFAULT_REGION=ap-northeast-1
+ AWS_REGION=ap-northeast-1
+ AWS_SECRET_ACCESS_KEY=***
+ AWS_SESSION_TOKEN=***
オプション によると、発行された一時的なアクセスキーの有効期限はデフォルト 1 時間と短命であることがわかります。
The assumed role duration in seconds, if assuming a role. Defaults to 1 hour.
(5) Terraform でリソースのデプロイを実行する
これまでのステップで AWS への一時的なアクセスキーが実行環境の環境変数に設定されました。あとは AWS CLI や Terraform で AWS のリソースを操作することができます。例えば route53_record リソースで CNAME レコードを管理することができます。
resource "aws_route53_zone" "this" {
name = local.hosted_zone
}
resource "aws_route53_record" "cname" {
name = aws_route53_zone.this.name
zone_id = aws_route53_zone.this.zone_id
type = "CNAME"
ttl = 86400
records = var.records
}
まとめ
この仕組みを導入する以前は複数の AWS アカウントに対して権限別にマシンアカウントを作成し、それぞれのアクセスキーを GitHub Secrets で管理して、定期的にローテーションしていました。この作業は常にオペレーションミスのリスクが伴い、各 CI/CD ジョブに影響が出ないように慎重な作業が求められましたが IAM でのセキュリティのベストプラクティス によると、アクセスキーなどの長期的な認証情報を作成すること自体がそもそも推奨されていませんでした。そこで今回の OIDC を用いた手法によって、長期的な認証情報の管理そのものを廃止し、管理工数削減と生成した短命なトークンを使うことによるセキュリティ面でのメリットが得られました。また、ロールの信頼ポリシーの定義はセキュリティ上重要な役割を持っているので、チームレビューをした後に、これもまた GitHub Actions からデプロイをし、継続的にドリフト検出を行うことで安心して利用できています。