ようへいの日々精進XP

よかろうもん

GitHub Actions や CircleCI で AWS リソースをいじる際に Assume Role を利用する

この記事は

YAMAP エンジニア Advent Calendar 2020 の四日目になる予定です。

qiita.com

tl;dr

Github Actions (CircleCI でもほぼ同じアプローチでイケると思います) で AWS リソースを操作する際、強い権限を付与したアクセスキー、シークレットアクセスキーを使い続けたくないので、Assume Role を設定して、AWS リソースを操作する時だけ (デプロイする時だけ) 強い権限を払い出すようにしてみたのでメモします。

Assume Role とは

Assume Role

Assume Role とは、直訳すると、「Assume Role (役割を引き受ける)」となります。ある Role に紐付いているポリシー (権限) をユーザーやグループ、他の AWS アカウントのユーザーやグループ、別の Role に委譲する機能です。

以下は、Assume Role のイメージです。

f:id:inokara:20201201235359p:plain

How to Use a Single IAM User to Easily Access All Your Accounts by Using the AWS CLI より引用

Assume Role を利用するメリット

Assume Role を利用することのメリットとしては、以下のようなことが挙げられます。

  • Assume Role されるユーザーに払い出す権限はデプロイ用ロールに対する sts:AssumeRole だけで良いので、シンプルに保つことが出来る
  • Assume Role される権限の、アクセスキー、シークレットアクセスキーは一時的に払い出されるもので、期限が切れると全くの無力である為、安心安全

登場人物

Assume Role でググると、複数の AWS 間での権限委譲の実装例を良く見ますが、本記事で紹介する実装例では、とてもシンプルな一つの AWS アカウント内のお話となります。

そして、本記事内で登場する登場人物は以下の通りです。

  • Assume Role されるユーザー (以後、デプロイ用ユーザーと記載)
  • Assume Role されるユーザーに付与するポリシー (以後、デプロイ用ユーザーポリシー)
  • Assume Role するロール (AWS リソースを操作する為に必要なポリシーが付与されている、以後、デプロイ用ロールと記載)

実装例

リポジトリ

このサンプルですが、Github Actions を使って、S3 バケットのオブジェクト一覧を取得したり、オブジェクトを削除したり、オブジェクトを追加します。

準備

以下のリソースを Terraform を使って作成します。

  • Assume Role されるユーザー (以後、デプロイ用ユーザーと記載)
  • Assume Role されるユーザーに付与するポリシー (以後、デプロイ用ユーザーポリシー)
  • Assume Role するロール (AWS リソースを操作する為に必要なポリシーが付与されている、以後、デプロイ用ロールと記載)
  • S3 バケット

以下のように実行します。

$ git clone git@github.com:inokappa/deploy-sample-for-assume-role.git
$ cd deploy-sample-for-assume-role
$ cd setup
$ make plan
$ make apply

これらのリソースを作成する際には、それなりの権限が付与された IAM ユーザーで作成しましょう。

以下のように、出力されますので、それぞれの値を控えておきましょう。

...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

id = AKxxxxxxxxxxxxxxxxxxxxxxx
role = arn:aws:iam::012345678901:role/deploy-user-role
secret = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Github リポジトリの設定

上記で出力された、idsecret 及び secret 等の情報を、下図のように Github リポジトリSettings > Secrets に設定します。

f:id:inokara:20201201235602p:plain

Github Actions の YAML

以下のように設定します。

name: sandbox

on:
  push:
    branches:
      - 'master'
      - 'main'

jobs:
  sandbox:
    runs-on: ubuntu-latest
    env: 
      S3_TARGET_BUCKET: ${{ secrets.S3_TARGET_BUCKET }}

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        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 }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
          role-duration-seconds: 900

      - name: List up Objects
        run: |
          aws s3 ls s3://${S3_TARGET_BUCKET}/

      - name: Put Objects
        run: |
          aws s3 cp test.txt s3://${S3_TARGET_BUCKET}/test.txt

      - name: Remove Objects
        run: |
          aws s3 rm s3://${S3_TARGET_BUCKET}/test.txt

肝心なのは、以下の部分で、AWS から Configure AWS Credentials が提供されているので、これを利用することで、とても簡単に設定することが出来ました。

    steps:
... 略 ...
      - name: Configure AWS credentials
        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 }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
          role-duration-seconds: 900
... 略 ...

以下の内容を設定します。

  • aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} には、デプロイ用ユーザーのアクセスキーを設定します
  • aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} には、デプロイ用ユーザーのシークレットアクセスキーを設定します
  • role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }} には、デプロイ用ロール名を設定します

デプロイ用ユーザーのアクセスキーとシークレットアクセスキーだけでは、S3 バケットにアクセスことは出来ません。あくまでも、Assume Role でロールを引き受けるだけの権限が付与されている状態です。

尚、role-duration-seconds では、Assume Role された権限の猶予時間を指定します。上記の例では、900 秒 (15 分) を設定しています。

git push

適当に test.txt を修正して、git push します。

$ echo 'foo' > test.txt
$ git add && git commig -m 'Add foo' && git push

すると、下図のように正常に S3 バケットにアクセスしてオブジェクトの操作が行えていることが判ります。

f:id:inokara:20201201235632p:plain

試しに、YAML ファイルを以下のように修正して push してみると...

diff --git a/.github/workflows/sandbox.yml b/.github/workflows/sandbox.yml
index b946edd..2fae2d7 100644
--- a/.github/workflows/sandbox.yml
+++ b/.github/workflows/sandbox.yml
@@ -22,8 +22,8 @@ jobs:
           aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
           aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
           aws-region: ap-northeast-1
-          role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
-          role-duration-seconds: 900
+            # role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
+            # role-duration-seconds: 900

       - name: List up Objects
         run: |

Assume Role 出来ておらず、権限が付与されていないので、下図のようにエラーとなりました。

f:id:inokara:20201201235653p:plain

いい感じです!

以上

正直、思ったよりも簡単に Assume Role を使って、Github Action から AWS リソースを操作することが出来たので、まだまだ、俺も捨てたもんじゃないなって自画自賛しています :sweat_smile:

Assume Role を利用することで、Github Actions や CircleCI 等の外部サービス用の IAM ユーザーに対して、個別に細かい権限を付与する必要がなく、また、時限的に権限が付与される為、アクセスキー、シークレットアクセスキーの漏洩に対して、安心安全に運用出来そうです。

ということで、素敵な Assume Role ライフをお過ごし下さい。

ちょっと待った、CircleCI だとどうする?

いくつか Assume Role の為の orbs が提供されていましたが、どれを使ったら良いのか判断出来なかったので、泥臭くシェルスクリプトで書いてみました。

# reference: https://dev.classmethod.jp/articles/circleci-use-aws-cli-v2/
version: 2.1

executors:
  my-executor:
    docker:
      - image: cimg/base:2020.11
    working_directory: ~/work
    environment:
      PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$HOME/work/aws-cli/v2/current/bin

commands:
  restore:
    steps:
      - restore_cache:
          key: work-v1-{{ .Revision }}

  save:
    steps:
      - save_cache:
          paths:
            - "aws-cli"
          key: work-v1-{{ .Revision }}

  install:
    steps:
      - run:
          name: install
          command: |
            if [[ ! -d aws-cli ]]; then
              curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
              unzip awscliv2.zip
              sudo ./aws/install --install-dir ~/work/aws-cli
            fi

  assume_role:
    steps:
      - run:
          name: Assume Role
          command: |
            AWS_STS_CREDENTIALS="$(aws sts assume-role \
              --role-arn=${AWS_ASSUME_ROLE_ARN} \
              --role-session-name "deply-user-session" \
              --external-id 00001 \
              --duration-seconds 900 \
              --query "Credentials" \
              --output "json")"
            echo "export AWS_ACCESS_KEY_ID=$(echo ${AWS_STS_CREDENTIALS} | jq -r '.AccessKeyId')" >> ${BASH_ENV}
            echo "export AWS_SECRET_ACCESS_KEY=$(echo ${AWS_STS_CREDENTIALS} | jq -r '.SecretAccessKey')" >> ${BASH_ENV}
            echo "export AWS_SESSION_TOKEN=$(echo ${AWS_STS_CREDENTIALS} | jq -r '.SessionToken')"  >> ${BASH_ENV}

  access:
    steps:
      - run:
          name: List up Objects
          command: |
            aws s3 ls s3://$S3_TARGET_BUCKET/
      - run:
           name: Put Object
           command: |
             aws s3 cp test.txt s3://$S3_TARGET_BUCKET/test.txt
      - run:
           name: Remove Object
           command: |
             aws s3 rm s3://$S3_TARGET_BUCKET/test.txt

jobs:
  sandbox:
    executor: my-executor
    steps:
      - checkout
      - restore
      - install
      - save
      - assume_role
      - access

workflows:
  sandbox:
    jobs:
      - sandbox

以下のように意図した通りに AWS リソースの操作が出来ています。

f:id:inokara:20201201235708p:plain

参考

docs.aws.amazon.com

aws.amazon.com

dev.classmethod.jp