echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【AWS CDK】GitHub ActionsでOIDCを使用してAWSと認証する方法

本日のお題

  • CD/CDなどでGitHub Actionからcdk deployコマンドを実施する際に、OIDCを使用して(IAM Userのアクセスキー&シークレットアクセスキーではなく)IAM RoleでAWSと認証を行う方法
  • 上記をAWS CDKで実装する方法

上記を実施する際に必要な手順

  1. IAM OIDC IDプロバイダーを作成する
  2. 認証用のIAM Roleを作成する
  3. 上記IAM Roleに必要なポリシーをアタッチする
  4. GitHub Actionsにてconfigure-aws-credentialsを使用してAWSと認証する

AWS CDKのコード

今回は、いきなりAWS CDKのコードを掲載します。(多分その方がわかりやすいので)

import * as cdk from 'aws-cdk-lib';
import { aws_iam } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class OIDCSampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);
  
    // cdk deployAWSを実施するAWSアカウントとリージョン  
    const accountId = this.account;
    const region = this.region;
  
    const user = '<対象のGitHubユーザー名>';
    const repo = '<対象のリポジトリ名>';
    const branch = '<対象のブランチ名>';
  
    // 1. IAM OIDC IDプロバイダーを作成する  
    const oidcProvider = new aws_iam.OpenIdConnectProvider(
      this,
      'GitHubOIDCProvider',
      {
        url: 'https://token.actions.githubusercontent.com',
        clientIds: ['sts.amazonaws.com'],
      }
    );
  
    // 2. 認証用のIAM Roleを作成する
    const oidcDeployRole = new aws_iam.Role(this, 'GitHubOidcRole', {
      roleName: 'github-oidc-role',
      assumedBy: new aws_iam.FederatedPrincipal(
        oidcProvider.openIdConnectProviderArn,
        {
          StringLike: {
            'token.actions.githubusercontent.com:sub': `repo:${user}/${repo}:ref:refs/heads/${branch}`,
          },
        },
        'sts:AssumeRoleWithWebIdentity' //これを忘れるとStatementのActionが'sts:AssumeRole'となりOIDCでのAssumeRoleで使えなくなる。
      ),
    });
  
    // 3. 上記IAM Roleに必要なポリシーをアタッチする 
    const deployPolicy = new aws_iam.Policy(this, 'deployPolicy', {
      policyName: 'deployPolicy',
      statements: [
        new aws_iam.PolicyStatement({
          effect: aws_iam.Effect.ALLOW,
          actions: ['sts:AssumeRole'],
          resources: [
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-deploy-role-${accountId}-${region}`,
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-file-publishing-role-${accountId}-${region}`,
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-image-publishing-role-${accountId}-${region}`,
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-lookup-role-${accountId}-${region}`,
          ],
        }),
      ],
    });

    oidcDeployRole.attachInlinePolicy(deployPolicy);
  }
}

1. IAM OIDC IDプロバイダーを作成する

IAM OIDC IDプロバイダーを作成し、そのURLに https://token.actions.githubusercontent.com (このホスト名は固定)、クライアントIDにAWS Security Token Service (AWS STS)を指定します。

2. 認証用のIAM Roleを作成する

IAM OIDC IDプロバイダーに対し、GitHub Actionsからのフェデレーション認証の場合にAssume Roleをを許可するIAM Roleを作成します。
そしてそのURLに https://token.actions.githubusercontent.com、クライアントIDにAWS Security Token Service (AWS STS)を指定します。

またその際にGitHubのユーザー名、リポジトリ名、ブランチ名を指定し、対象のブランチでのみ認証を行うようにします。

なおこの時assumeRoleAction引数に「sts:AssumeRoleWithWebIdentity」を指定しないとOIDCでのAssumeRoleが出来ないので注意です。(「sts:AssumeRole」ではダメ)

3. 上記IAM Roleに必要なポリシーをアタッチする

上記IAM ポリシーを作成し、上記IAM Roleにアタッチします。

今回はcdk deployを実施する用のポリシーをインラインポリシーとしてアタッチしています。

ちなみに上記ポリシーの内容&sts:AssumeRole元のリソースの内容については、下記をご参照ください。(事前に cdk bootstrap が実施済であることが前提です)

speakerdeck.com

GitHub Enterprise(GHE) の場合

もちろん今回の方法はGHEでも使用できますが、GHEの場合、GitHubと以下の点が異なります。

  • ユーザー名を指定していた箇所は、GHEではorganization名を指定する
  • ホスト名は token.actions.githubusercontent.com ではなく、 「<GHEのドメイン名>/_services/token」になる

例えばorganizationが「my_org」、GHEのドメイン名が「github.hogehoge.com」の場合、CDKのソースはこうなります。(差分だけ記載)

    const oidcProvider = new aws_iam.OpenIdConnectProvider(
      this,
      'GitHubOIDCProvider',
      {
        url: 'https://github.hogehoge.com/_services/token',
        clientIds: ['sts.amazonaws.com'],
      }
    );
  
    // 2. 認証用のIAM Roleを作成する
    const oidcDeployRole = new aws_iam.Role(this, 'GitHubOidcRole', {
      roleName: 'github-oidc-role',
      assumedBy: new aws_iam.FederatedPrincipal(
        oidcProvider.openIdConnectProviderArn,
        {
          StringLike: {
            'github.hogehoge.com/_services/token:sub': `repo:my_org/${repo}:ref:refs/heads/${branch}`,
          },
        },
        'sts:AssumeRoleWithWebIdentity' 
      ),
    });
  }
}

OIDC IDプロバイダーのサムプリントについて

今までIAM OIDC IDプロバイダーを使用する場合、OIDC IDプロバイダーの検証用にサムプリント(thumbprint)を設定する必要がありましたが、今年の7/6よりサムプリントが検証に必要なくなりました。

GitHub OIDC is now using library of trusted CAs · Issue #763 · aws-actions/configure-aws-credentials · GitHub

なおAWS CDKではサムプリントは任意項目で、CloudFormationでは必須項目ですが、適当な値を入れておけば良いみたいです。

GitHub Actions

# トリガ設定は適宜変更
# 最低限しか書いてないです(cache処理などは省略)
on: 
  push:
    branches:
      - 'develop'
      - 'main'

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      # 今回は直接記載しているが、必要に応じてsecretsに登録する
      AWS_ACCOUNT_ID: "<AWSアカウント番号>"
      AWS_REGION: "ap-northeast-1"
  
    # ここでpermissionを設定するのが大事
    permissions:
      id-token: write
      contents: read
  
    steps:  
      - name: Checkout
        uses: actions/checkout@v3
  
      - name: Install CDK Dependency
        run: npm ci
  
      # 4. GitHubにてconfigure-aws-credentialsを使用してAWSと認証する  
      - name: Assume Role
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: "arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/github-oidc-role"
          aws-region: ${{ env.AWS_REGION }}
  
      # npm scriptに登録しておいたcdk deployコマンドを実施する
      - name: Deploy
        run: npm run deploy 

ポイントは下記2点です

permissionsにid-token: write、contents: readを設定する

これを設定しないとOIDCを使用してcdk deployが実施できない

aws-actions/configure-aws-credentials@v2を使用して、「2. 認証用のIAM Roleを作成する」で作成したIAM Roleで認証を行う

role-to-assumeに該当IAM RoleのArnを指定する

ちなみにロール名だと「credential情報がない」みたいなエラーになった。(secretsにアクセスキー&シークレットアクセスキーを登録すればOKかも)

ただ、それをしたくないからOIDCを使用しているのに、それやったら意味がない...

まとめ

以上、AWS CDKでGitHubでOIDCを使用してAWSと認証する方法でした。

アクセスキー&シークレットアクセスキーはセキュリティ的な観点からも、発行しないで済むならそれに越したことはないので、今回のような手法を積極的に取り入れて、よりセキュアになればと思います。

なお、GitHub側で「特定のIPアドレスからしか接続を許可していない」ような設定をしている場合、この方法が使えないので、その場合は残念ながらこの方法は使えません。(GHEでありがち)

では、今回はこの辺で。