こんにちは。 エキサイトの宮西です。
Advent Calendarの季節が今年もやってきました。
昨年同様、エキサイトホールディングス Advent Calendarで毎日投稿される予定です。お楽しみにー。
第一回目は、AWSのIAMロール(AssumeRole)のTipsを書いていこうと思います。
IAMユーザを使わずに外部からAWSリソースへアクセス
昨年からOIDCによる認証が使えるようになって、IAMユーザを作成せずとも外部からAWSリソースを操作できるようになりました。
クレデンシャルを発行してそれを保持しておく必要もないので、よりセキュアにもなったわけです。
私の所属するチームではCI/CDでGitHub Actionsを活用しており、その恩恵を存分に受けています。
そんなあるとき、GitHub Actionsのワークフロー内でDockerランタイムにクレデンシャルを渡す、という処理が必要になりまして。
実現するための解決策はいくつかあるのですが、今回はAssumeRoleを利用する例を紹介します。
IAMロールの準備
用意するIAMロールは以下の2つです。
- arn:aws:iam::111111111111:role/github-oidc-role
- ワークフロー内でAWSリソースを操作する(ECRへのPushなどデプロイ関連のアクション)ためのロール
- arn:aws:iam::111111111111:role/upload-to-s3-role
- 特定のS3バケットへのPutObjectを許可する、Dockerランタイムに渡したいロール
処理内容は、github-oidc-role
で upload-to-s3-role
をAssumeRoleする、です。
なるほど?
AssumeRoleといえば界隈では仮面や帽子などで例えられることで有名ですが、ざっくり言うと「指定したIAMロールを引き受けるアクション」です。
もう少し具体的に言えば、AssumeRoleするとそのIAMロールの一時的なクレデンシャルを返してくれる、といったところでしょうか。
イマイチよくわかりませんが、一時的なクレデンシャルを発行する・権限を引き受ける、ということはわかったので早速やってみようと思います。
upload-to-s3-roleの信頼されたエンティティ
upload-to-s3-role
の許可ポリシーは単純なので省略(S3のPutObjectを許可するだけ)しますが、重要なのは 信頼されたエンティティ
。
以下のように、Principalを github-oidc-role
にしてAssumeRoleできるように設定します。
{ "Version": "2008-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111111111111:role/github-oidc-role" }, "Action": "sts:AssumeRole" } ] }
github-oidc-roleの許可ポリシー
github-oidc-role
側は upload-to-s3-role
をAssumeRoleできるように許可ポリシーを追加しておきます。
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" ], "Resource": [ "arn:aws:iam::111111111111:role/upload-to-s3-role" ], "Effect": "Allow" } ] }
これで、github-oidc-role
が upload-to-s3-role
をAssumeRoleして権限を引き受けることができるようになりました。
ワークフロー
処理の順序としては、
- OIDCで認証して、
github-oidc-role
をAssumeRoleする - 1のクレデンシャルを使って、ECRにログインする
- 1のクレデンシャルを使って、
upload-to-s3-role
をAssumeRoleする - 3のクレデンシャルをDockerランタイムに渡して、イメージをビルドする
- 1のクレデンシャルを使って、ビルドしたイメージをECRにプッシュする
といった感じです。実際のワークフローに書き起こすと↓のようになるかと思います。
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: arn:aws:iam::111111111111:role/github-oidc-role aws-region: ap-northeast-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Get credentials run: | TEMP_ROLE=$(aws sts assume-role --role-arn arn:aws:iam::111111111111:role/upload-to-s3-role --role-session-name for-build) echo "${TEMP_ROLE}" | jq -r '.Credentials' > credentials.json - name: Build, tag, and push image to Amazon ECR uses: docker/build-push-action@v3 with: push: true tags: | ${{ steps.login-ecr.outputs.registry }}/image-name:latest secret-files: credentials=./credentials.json
なお、--build-arg
でDockerランタイムにクレデンシャルを渡してしまうと docker history
で内容が丸見えになってしまいます。
一時的なクレデンシャルと言えども、ベストプラクティス通りに --secret
で渡すようにしましょう。
--secret
で渡した場合、DockerfileではRUN命令文にオプションを付けてファイルをマウントするようにします。
FROM node:18-alpine WORKDIR /app COPY . . RUN --mount=type=secret,id=credentials npm ci && node index.js (以下略)
node index.js
で実行されるコードは↓のような感じ。
デフォルトでは /run/secrets/
配下にファイルがマウントされているので、それを読み込んで後続処理を進めていきます。
const data = fs.readFileSync('/run/secrets/credentials.json') const credentials = JSON.parse(data) const s3 = new AWS.S3({ apiVersion: '2006-03-01', accessKeyId: credentials.AccessKeyId, secretAccessKey: credentials.SecretAccessKey, sessionToken: credentials.SessionToken, region: 'ap-northeast-1', }) (以下略)
まとめ
「いやいや、そんな面倒臭いことしなくても、OIDC用のロール1つで全部処理しちゃえばいいじゃん」という声が聞こえてきそう。
要するに、ワークフローの aws-actions/configure-aws-credentials
で発行されたクレデンシャルをDockerランタイムに渡す、ということです。
クレデンシャルは環境変数にセットされているので、それを使うことができるわけです。
そうなのですが、ユースケースによってはOIDC用のロールにポリシーを追加できない、ということもあるかと思います。
また、IAMのベストプラクティスには以下のような記述もあったりします。
IAM ポリシーでアクセス許可を設定するときは、タスクの実行に必要なアクセス許可のみを付与します。これを行うには、特定の条件下で特定のリソースに対して実行できるアクションを定義します。これは、最小特権アクセス許可とも呼ばれています。 https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/best-practices.html
目的が異なるものを一緒くたにしてしまうのは思わぬ事故を招いてしまいます。「最小権限の原則」を意識したIAM管理を心掛けていきたいです。
AssumeRoleを使いこなしてセキュアなシステムを構築していきましょう。
最後に、検証に付き合っていただいた師匠のN氏、ありがとうございましたー。