【AWS】セキュアにGitHub ActionsでECR/ECSへのCI/CD
概要
本記事は、
・OpenID Connectの概要
・GitHub ActionsでのECR/ECSへのCI/CDを行う方法
をまとめた記事になる。
OpenID Connectとは?
OAuth2.0をベースにして、(認可だけでなく)認証も行えるようにした拡張仕様のこと。
認証処理をOpenID Providerにお任せして、認証結果を一時的なOIDCトークンとして発行。
そのIDトークンをクライアントが受け取って、それを使用して他のサービスへ認証を行う感じ。
従来のやり方
Github ActionsからAWSへアクセスする際、従来はIAMユーザーのCredentialを渡すのが一般的だった。
~~~
- name: Configure AWS Credentials # AWSアクセス権限設定
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
~~~
しかしこのやり方は、GitHubに長時間有効なクラウドプロバイダのシークレットを保存する事になるので
・定期的にキーのローテーションが必要
・漏洩した場合の悪用されるリスクも上がる
という問題があった。
OpenID Connectが使えるようになり、よりセキュアにAWSへのアクセスが可能に
ところが2021/10に、Github ActionsがOpenID Connectをサポートした。
これにより、GitHubのプロバイダがOIDCトークンを発行出来るようになったので
・AWS側で、GitHubのプロバイダを信頼するプロバイダを作成
・AWS側で、GitHub側にAssumeさせたいロールを作成
を行えば、一時的なアクセストークンをAWS → GitHubに渡して、IAMユーザーなしでECR/ECSにアクセスが出来るようになる。
処理の流れ
①AWS側のプロバイダーで、GitHub側のプロバイダを信頼するプロバイダを作成
②ジョブが実行される度に、GitHub側のプロバイダがOIDCトークンを自動生成する。
③JWTにして、Assumeさせたいロール付きでAWS側のプロバイダに送る。
④AWS側のプロバイダーがそのトークンの内容を検証すると、そのジョブが実行されている間だけ利用できる短期間のアクセストークンを発行する。以降の操作は、このアクセストークンを使い回す。
参考記事↓
実装方法
AWS側のプロバイダ、ロールを作成
Terraformで作成。
より安全にするために、ロールは
・mainブランチで実行されたワークフローのみ許可する
という風に厳しくするのも可能なので、今回はそのように書いてる。
(ロールはもうちょい最小限に絞って良いかも)
# GitHub側のプロバイダを信頼するプロバイダを作成
resource "aws_iam_openid_connect_provider" "github_actions" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"]
}
# IAMロールを作成
resource "aws_iam_role" "github_actions" {
name = "GitHubActions-OIDC"
assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Federated" : aws_iam_openid_connect_provider.github_actions.arn
},
"Action" : "sts:AssumeRoleWithWebIdentity",
"Condition" : {
"StringEquals" : {
# main ブランチで実行された GitHub Actions ワークフローでのみ、このロールの利用を許可する設定を行う
"token.actions.githubusercontent.com:sub" : "repo:#{GitHubのリポジトリ名}:ref:refs/heads/main"
},
}
}
]
})
}
# ECR/ECSへの捜査を許可するために、ロールにポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "ecr" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess"
}
resource "aws_iam_role_policy_attachment" "ecs" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
GitHub Actionsの設定
secrets.xx の箇所は、GitHub Actions Secretsからの参照にしてる。
name: Build and Push
# mainブランチへのpushで発火
on:
push:
branches:
- main
# id-token: write はGitHubのOIDC プロバイダから発行されるトークンを利用するために必要になる
# permissions を書くと他の contents: read のようなデフォルトで有効になってる権限も無効になってしまうため明示的に書く必要あり
permissions:
id-token: write
contents: read
jobs:
build-and-deploy:
runs-on: ubuntu-latest
timeout-minutes: 300
steps:
- uses: actions/checkout@v1
# aws-actions/configure-aws-credentials: AWS 公式のアクションで、他に認証情報を渡さなければ自動で OIDC トークンを使用してくれる。
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ap-northeast-1
- name: Login to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push image to ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:latest .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:latest"
- name: Render Amazon ECS task definition
id: render-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: aws/task-definition.json
container-name: {対象のコンテナ名}
image: ${{ steps.build-image.outputs.image }}
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-container.outputs.task-definition }}
service: {対象のECSサービス名}
cluster: {対象のECSクラスタ名}
完成
成功するとこんな感じ。
まとめ
・GitHub Actions側でAWSにアクセスする際に、もうIAMユーザーを発行する必要ない
・設定もめちゃくちゃ楽。IAMユーザーを使用してるプロジェクトは、是非置き換えしてみてほしい
Discussion