CircleCIがOpenID ConnectをサポートしたのでAWSと連携させてJWTを使用したAssumeRoleを試してみた
こんにちは、CX事業本部 IoT事業部の若槻です。
このたび、CI/CDプラットフォームCircleCIで、OpenID Connect(OIDC)がいよいよサポートされたとのことです。待ちわびていた人も多いのではないでしょうか。
#OIDC リリースされました!
(OpenID Connect Tokens)
Twitterでも「1月にでるはずだったよね」という期待をずいぶん頂き、ご関心をひしひしと感じてしました。
- Changelog: https://t.co/oqM0zbE2OF
- Doc: https://t.co/fOYKoKfTNd— CircleCI Japan (@CircleCIJapan) March 26, 2022
CircleCIにおいては、OpenID Connectプロトコルの流れは次のようになるとのこと。 CircleCI の OpenID Connect サポート - Qiitaより引用
これにより永続的なアクセスキーなどの情報を使用せず、一時的なCredentialのみを使用した認証が可能となるため、CircleCIを利用する際のセキュリティリスクを大きく軽減することができるようになります。
今回は下記のドキュメントを参考にしつつ、CircleCIをOIDCでAWSと連携させて、OIDC Token(JWT)を使用したAssumeRoleを試してみました。
やってみた
Contextの作成
まず、CircleCIでContextを作成します。
CircleCIのコンソールで、[Organization Settings > Contexts]で[Create Context]をクリック。
Context名を適当に付けて作成。
するとContextが作成され、Organization IDが発行されるので控えます。
IDプロバイダの追加
AWSにIDプロバイダを作成し、OIDCによりCircleCIと連携できるようにします。
Create Identity ProviderでProvider typeとProvider URLを次のように指定し、[Get thumbprint]をクリックしてThumbprintを発行します。
- Provider type:
OpenID Connect
- Provider URL:
https://oidc.circleci.com/org/<Organization ID>
さらにAudienceを指定し、[Add provider]をクリック。
- Audience:
<Organization ID>
IDプロバイダが追加できました。
Assume用のIAMロールの作成
CircleCI上でAssume Roleする際に使用するIAMロールをAWSに作成します。
まず適当にIAMロールを作成します。
作成したIAMロールにポリシーをアタッチします。権限は必要に応じて絞ってください。今回は簡単のため*
を指定しています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "*", "Resource": "*" } ] }
またIAMロールの信頼ポリシーを次のように編集して、IDプロバイダを信頼するようにします。Federated
には先程作成したIDプロバイダのArnを指定します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "<IDプロバイダArn>" }, "Action": "sts:AssumeRoleWithWebIdentity" } ] }
ちなみにここでCondition句を使用して、このIAMロールを使用してAssumeRole可能なユーザーやProjectを制限することもできます。詳しくは下記をご覧ください。
Environment Variablesの設定
CircleCIのEnvironment Variablesに環境変数を設定します。設定先はProjectとContexts(先程作成したもの)のどちらでも良いです。適した方を選んでください。
設定する環境変数は次の2つです。
- AWS_DEFAULT_REGION:
<AWSリージョンID>
- AWS_ROLE_ARN:
<前節で作成したIAMロールのArn>
以前ならここにIAMユーザーのAWS_ACCESS_KEY_ID
やAWS_SECRET_ACCESS_KEY
も合わせて設定していましたが、OpenID Connectの利用により不要になります。
AssumeRole実行スクリプトの作成
GitHub Repository内に次のシェルスクリプトを作成します。
- sts:AssumeRoleWithWebIdentityAPIによりOIDC Tokenを使用したAssume Roleを行い、取得した一時Credentialの情報を環境変数に格納しています。(sts:AssumeRoleAPIではないことに注意)
DURATION_SECONDS
で一時Credentialの有効期限(15分)を指定しています。
#!/usr/bin/env bash set -xeuo pipefail DURATION_SECONDS=$((60*15)) aws_sts_credentials=`aws sts assume-role-with-web-identity \ --role-arn $1 \ --web-identity-token $2 \ --role-session-name "circle-ci-session" \ --duration-seconds ${DURATION_SECONDS} \ --query "Credentials" \ --output "json"` cat <<EOT > "$(dirname $0)/aws-envs.sh" export AWS_ACCESS_KEY_ID="$(echo $aws_sts_credentials | jq -r '.AccessKeyId')" export AWS_SECRET_ACCESS_KEY="$(echo $aws_sts_credentials | jq -r '.SecretAccessKey')" export AWS_SESSION_TOKEN="$(echo $aws_sts_credentials | jq -r '.SessionToken')" EOT
スクリプトの権限を変更して実行可能としておきます。
$ chmod +x assume-role-with-oidc.sh
Configファイルの作成
同じくGitHub Repository内に、次のConfigファイル.circleci/config.yml
を作成します。
workflows
で前節で作成したContext名を指定することにより、OIDCが利用可能となります。- OIDC Tokenは
CIRCLE_OIDC_TOKEN
に格納されます。AssumeRole時に使用できるようにAWS_ROLE_ARN
と合わせてスクリプトの引数に指定します。
version: 2.1 executors: my-executor: docker: - image: circleci/node # deprecated # See https://circleci.com/docs/ja/2.0/next-gen-migration-guide/ orbs: # See https://circleci.com/developer/ja/orbs/orb/circleci/aws-cli aws-cli: circleci/aws-cli@2.1.0 jobs: deploy: executor: my-executor steps: - checkout - aws-cli/install - run: | set -x ./assume-role-with-oidc.sh ${AWS_ROLE_ARN} ${CIRCLE_OIDC_TOKEN} - run: name: some deploy command: | source aws-envs.sh aws s3 ls workflows: version: 2 release: jobs: - deploy: context: aws-deploy # 前節で作成したContextを指定
ちなみにCircleCIのConfigを最近さっぱりいじっていなかったため勝手が全然分からなかったのですが、前節の実行スクリプト含め次のブログがとても参考になりました。感謝。
追記
AssumeRole部分を次のようにすればCongig内に直接記述できました。
jobs: deploy: executor: my-executor steps: - checkout - aws-cli/install - run: | aws_sts_credentials=$(aws sts assume-role-with-web-identity \ --role-arn ${AWS_ROLE_ARN} \ --web-identity-token ${CIRCLE_OIDC_TOKEN} \ --role-session-name "circleci-oidc" \ --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 source $BASH_ENV
動作確認
ここまでで作成したAssumeRole実行スクリプト、Configファイルを含んだRepositoryをGitHubにpushし、CI/CDを実行させます。
するとOIDC Tokenを使用したAssume Roleにより一時Credentialが取得でき、AWS CLIコマンドを実行することができました!
補足
OIDC Tokenの中身を見てみる
OIDC Tokenの中身はどんな感じなのか見てみます。
echo
で出力しようとしていますが、そのまま表示しようとするとコンソール上ではマスクされてしまうので、スライスして先頭一文字以降を表示するようにしています。
version: 2.1 executors: my_executor: docker: - image: circleci/node # deprecated # See https://circleci.com/docs/ja/2.0/next-gen-migration-guide/ jobs: get_token: executor: my_executor steps: - checkout - run: echo ${CIRCLE_OIDC_TOKEN:1}; # OIDC Tokenを取得 workflows: version: 2 release: jobs: - get_token: context: aws-deploy # 先程作成したContextを指定
RepositoryにブランチをpushしてCircleCIのWorkflowを実行すると、OIDC Tokenを表示できました。
jwt.ms(Microsoftのサービス)でトークンを復号すると、クレームの各種値がちゃんと確認できます。
トークンのフォーマットはドキュメントにまとまっています。
Contextを指定しない場合
Configファイルのworkflows
でContextを指定しない場合の動作を試してみます。
workflows: version: 2 release: jobs: - get_token: #context: aws-deploy
OIDC Tokenは設定されなくなりました。
ドキュメントにある通り設定は必須のようです。
Add a context to a job by adding the context key to the workflows section of your circleci/config.yml file:
おわりに
CircleCIがOpenID ConnectをサポートしたのでAWSと連携させてOIDC Token(JWT)を使用したAssumeRoleを試してみました。
GitHub ActionsのOIDCが昨年10月に正式リリースされ、同じCI/CDサービスであるCircleCIはいつなのかと待ちわびていましたが、いよいよサポートが開始されました。プロジェクトに応じて両サービスを使い分けしているので、いずれにおいても永続的なキー情報を使わずセキュアにCI/CDが行えるようになったのはとても嬉しいです。その他のAWSと連携するあらゆるサービスに追随して欲しいですね。
参考
- CircleCI API Developer's Guide - CircleCI
- 環境変数の使用 - CircleCI
- Configuring CircleCI - CircleCI
- GitHub + CircleCI + AWS CDK で自動デプロイをやりたい | なつねこメモ
- CircleCI OrbsでAWS CLIを超簡単に導入して使ってみる | DevelopersIO
以上