🔐

【AWS】セキュアにGitHub ActionsでECR/ECSへのCI/CD

2022/07/16に公開

概要

本記事は、

・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をサポートした。

https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/

これにより、GitHubのプロバイダがOIDCトークンを発行出来るようになったので

・AWS側で、GitHubのプロバイダを信頼するプロバイダを作成
・AWS側で、GitHub側にAssumeさせたいロールを作成

を行えば、一時的なアクセストークンをAWS → GitHubに渡して、IAMユーザーなしでECR/ECSにアクセスが出来るようになる。

処理の流れ

①AWS側のプロバイダーで、GitHub側のプロバイダを信頼するプロバイダを作成

②ジョブが実行される度に、GitHub側のプロバイダがOIDCトークンを自動生成する。

③JWTにして、Assumeさせたいロール付きでAWS側のプロバイダに送る。

④AWS側のプロバイダーがそのトークンの内容を検証すると、そのジョブが実行されている間だけ利用できる短期間のアクセストークンを発行する。以降の操作は、このアクセストークンを使い回す。

参考記事↓

https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect

実装方法

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