CircleCIがOpenID ConnectをサポートしたのでAWSと連携させてJWTを使用したAssumeRoleを試してみた

2022.03.28

こんにちは、CX事業本部 IoT事業部の若槻です。

このたび、CI/CDプラットフォームCircleCIで、OpenID Connect(OIDC)がいよいよサポートされたとのことです。待ちわびていた人も多いのではないでしょうか。

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_IDAWS_SECRET_ACCESS_KEYも合わせて設定していましたが、OpenID Connectの利用により不要になります。

AssumeRole実行スクリプトの作成

GitHub Repository内に次のシェルスクリプトを作成します。

  • sts:AssumeRoleWithWebIdentityAPIによりOIDC Tokenを使用したAssume Roleを行い、取得した一時Credentialの情報を環境変数に格納しています。(sts:AssumeRoleAPIではないことに注意)
  • DURATION_SECONDSで一時Credentialの有効期限(15分)を指定しています。

assume-role-with-oidc.sh

#!/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と合わせてスクリプトの引数に指定します。

.circleci/config.yml

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内に直接記述できました。

.circleci/config.yml

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で出力しようとしていますが、そのまま表示しようとするとコンソール上ではマスクされてしまうので、スライスして先頭一文字以降を表示するようにしています。

.circleci/config.yml

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を指定しない場合の動作を試してみます。

.circleci/config.yml

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と連携するあらゆるサービスに追随して欲しいですね。

参考

以上