GitHub Actions OpenID Connect (OIDC) で多段 AssumeRole する

# はじめに

GitHub ActionsでOpenID Connectがサポートされましたね。

GitHub ActionsからAWSを操作するにあたって、MFA＋外部IDでAssumeRoleを頑張ってましたが、
IAMユーザーの認証情報を持たせる必要がなくなるのでこれに切り替えていこうと思います。

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

## 設計方針

- GitHub OIDCプロバイダーはAWSアカウント毎に1つだけ作成する必要がある
- GitHub OIDCは１つであり、複数のIAMロールを引き受けることができるがGitHub Actionsで直接引き受けるIAMロールは１つとする
- GitHub ActionsでAWSを操作を行うためには、多段AssumeRoleさせAWSの操作権限を与える
- GitHub Actionsで引き受けるIAMロールはAWSを操作する権限を与えない(踏み台IAMロール)。
- 踏み台IAMロールで引き受けるAWS操作権限を持つIAMロールを作成する(操作用IAMロール)
- 操作用IAMロールには外部IDを設定する
- 踏み台IAMロール一時認証情報＋外部IDを渡し操作用IAMロールを引き受ける
- 操作用IAMロール一時認証情報でAWS APIにリクエストする

図にするとこんな感じです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228453/717e8dff-fcf8-0176-f8d0-28e2d76d5315.png)

## CloudFormation テンプレート

以下のサンプルを元に設計方針に沿って書き直しました。

https://github.com/aws-actions/configure-aws-credentials#sample-iam-role-cloudformation-template

```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: "GitHub Actions OIDC"
Parameters:
  SystemPrefix:
    Type: String
    Default: "github-actions-oidc"
  Env:
    Type: String
    Default: "stg"
    AllowedValues:
      - dev
      - stg
      - prod
  Group:
    Type: String
    Default: "group-a"
    AllowedValues:
      - "group-a"
      - "group-b"
  GitHubOrgName:
    Type: String
    Default: org
  OIDCProviderArn:
    Description: Arn for the GitHub OIDC Provider.
    Default: ""
    Type: String
  ExternalId:
    Type: String
    Description: "External ID"
    Default: "example1234"
Conditions:
  IsNotExistsOIDCProvider: !Equals 
    - !Ref OIDCProviderArn
    - ""
Resources:
  GitHubOIDC:
    Type: AWS::IAM::OIDCProvider
    Condition: IsNotExistsOIDCProvider
    Properties:
      ClientIdList: 
        - sts.amazonaws.com
      Tags:
        - Key: "Name"
          Value: !Sub "${SystemPrefix}-${Env}-oidc"
        - Key: "Env"
          Value: !Ref Env
        - Key: "Group"
          Value: !Ref Group
      ThumbprintList:
        - a031c46782e6e6c662c2c87c76da9aa62ccabd8e
      Url: https://token.actions.githubusercontent.com
  BastionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Principal:
              Federated: !If 
                - IsNotExistsOIDCProvider
                - !Ref GitHubOIDC
                - !Ref OIDCProviderArn
            Condition:
              StringEquals:
                # audience
                token.actions.githubusercontent.com:aud: sts.amazonaws.com
              StringLike:
                # repository
                token.actions.githubusercontent.com:sub:
                  - !Sub repo:${GitHubOrgName}/example-github-actions:*
                  - !Sub repo:${GitHubOrgName}/example-github-actions-sub:*
      Description: ""
      # ManagedPolicyArns:
      #   - String
      MaxSessionDuration: 3600
      Path: "/github-actions/"
      # PermissionsBoundary: String
      Policies:
        - PolicyName: assume-role-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: Allow
                Action:
                  - "sts:GetCallerIdentity"
                Resource:
                  - "*"
              -
                Sid: "GitHubActionsAssumeRole"
                Effect: Allow
                Action:
                  - "sts:AssumeRole"
                  - "sts:TagSession"
                Resource:
                  - !Sub "arn:aws:iam::${AWS::AccountId}:role/${SystemPrefix}-${Env}-deploy-role"
      RoleName: !Sub ${SystemPrefix}-${Env}-bastion-role
      Tags:
        - Key: "Name"
          Value: !Sub "${SystemPrefix}-${Env}-bastion-role"
        - Key: "Env"
          Value: !Ref Env
        - Key: "Group"
          Value: !Ref Group
  GitHubActionsDeployRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Sid: "AllowAssumeRole"
            Effect: "Allow"
            Action:
              - "sts:AssumeRole"
            Principal:
              AWS: !GetAtt BastionRole.Arn
            Condition:
              StringEquals:
                sts:ExternalId: !Ref ExternalId
          -
            Sid: "AllowPassSessionTags"
            Effect: "Allow"
            Action:
              - "sts:TagSession"
            Principal:
              AWS: !GetAtt BastionRole.Arn
      Description: "Allows Protected Execute AWS Access via GitHub Actions Access."
      # ManagedPolicyArns:
      #   - arn:aws:iam::aws:policy/AdministratorAccess
      MaxSessionDuration: 3600
      Path: "/github-actions/"
      # PermissionsBoundary: String
      # Policies:
      #   - Policy
      RoleName: !Sub "${SystemPrefix}-${Env}-deploy-role"
      Tags:
        - Key: "Name"
          Value: !Sub "${SystemPrefix}-${Env}-deploy-role"
        - Key: "Env"
          Value: !Ref Env
        - Key: "Group"
          Value: !Ref Group
Outputs:
  BastionRoleArn:
    # Description: Information about the value
    Value: !GetAtt BastionRole.Arn
    Export:
      Name: !Sub "${SystemPrefix}-${Env}-bastion-role-arn"
  GitHubActionsDeployRoleArn:
    # Description: Information about the value
    Value: !GetAtt GitHubActionsDeployRole.Arn
    Export:
      Name: !Sub "${SystemPrefix}-${Env}-deploy-role-arn"
```

## GitHub Actions Workflow サンプル

```yaml
name: GitHub Actions
on:
  workflow_dispatch:
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@master
        with:
          aws-region: ap-northeast-1
          role-to-assume: arn:aws:iam::{AccountID}:role/github-actions/github-actions-oidc-dev-bastion-role
      - run: aws sts get-caller-identity
      - name: Configure AWS Credentials2
        uses: aws-actions/configure-aws-credentials@master
        with:
          aws-region: ap-northeast-1
          aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
          aws-session-token: ${{ env.AWS_SESSION_TOKEN }}
          role-external-id: ${{ secrets.EXTERNAL_ID }}
          role-to-assume: arn:aws:iam::{AccountID}:role/github-actions/github-actions-oidc-dev-deploy-role
          role-duration-seconds: 900
      - run: aws sts get-caller-identity
```

## 結果

Workflow dispatchで動作確認します。
それぞれの結果でAWSアカウント情報が取得できることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228453/4da2a9fb-d7fa-656c-f199-6940e0ad0553.png)

## まとめ

GitHub Actionsで直接ロールを引き受けることなく、多段でAssumeRoleしてAWS APIにリクエストが確認できました。
MFA＋外部IDでAssumeRoleを頑張ってましたが切り替えていこうかと思います。