CircleCI のジョブで OpenID Connect(OIDC) の ID トークンが利用可能になりました! このトークンを使うと、AWS、Google Cloud Platform、Vault など、OpenID Connect がサポートされているクラウドプロバイダーで CircleCI ジョブを認証できます。 このブログ記事では、OpenID Connect(OIDC) の概要と CI/CD システムにおける有用性について説明します。また、静的な認証情報ではなく OpenID Connect の ID トークンを使用して AWS の認証を行い、CircleCI ジョブをセキュアに AWS アカウントと接続する方法についても紹介します。

OpenID Connect (OIDC) とは?

OpenID Connect (OIDC) とは、クラウドサービスでエンドユーザーのアイデンティティを確認するための認証プロトコルです。 クラウドリソースにシングルサインオン (SSO) アクセスを提供する認可プロトコルの OAuth2.0 に、アイデンティティのレイヤーを追加します。 OpenID Connect は、2014 年に発表されて以来、保護対象リソースへのセキュアなアクセスを提供するための標準的技術として多くのクラウドプロバイダーに採用されています。

OpenID Connect Foundation によれば、OpenID を利用することで「クライアントは認可サーバーによる認証に基づいてエンドユーザーのアイデンティティを確認」できるとされています。 CircleCI の OpenID Connect トークンを使用する場合、クライアントに当たるのは AWS などのクラウドプロバイダーであり、 エンドユーザーは CircleCI で実行されるジョブ、 認可サーバーは CircleCI 自体です。

一連の流れとしては、AWS などのクラウド プロバイダーが CircleCI ジョブのアイデンティティを確認し、認証済みユーザーとしての処理の実行を許可することになります。 すなわち、CircleCI ジョブと AWS をセキュアに接続できるのです。

CI/CD で OpenID Connect が便利な理由

Physical key vs OpenID connect トークン

CI/CD ワークフローでは、バイナリやテストアーティファクト、ログなどをクラウドストレージにアップロードすることがあります。 たとえば、パッケージをアーティファクトリポジトリに送信したり、 本番環境にデプロイしたりする場合です。 そのようなクラウドリソースにセキュアにアクセスするには、クラウドプロバイダーと認証するための認証情報をジョブに提供する必要があります。 CircleCI のコンテキスト機能を使用すると、認証情報を安全に格納して組織全体のジョブで使用できます。

OIDC トークンの概要

しかし、格納された認証情報は静的です。 物理的な鍵が錠前の変更後は使えなくなるように、静的な認証情報が有効なのはその認証情報を入れ替えるまでの期間だけです。しかも、認証情報の入れ替えはセキュリティのベストプラクティスとして推奨されています。 キーの入れ替え自体は、CircleCI の Web UI、CircleCI CLI、またはコンテキスト用の CircleCI APIで行えます。

しかし、キーの入れ替えには時間がかかり、結果として開発にかけられる力が減ってしまいます。 このデメリットを回避できるのが OpenID Connect の ID トークンです。 CircleCI ジョブの OpenID Connect トークンを信頼するようにクラウドプロバイダーを設定すれば、クラウドプロバイダー側で CircleCI の署名が認識され、ジョブのリクエストが本物として扱われるようになります。 これによりジョブとクラウドプロバイダーをセキュアに接続し、ジョブでクラウドストレージへのアップロードや本番環境の状態の変更など、許可した処理を自由に行えます。

OIDC トークン ドキュメント

CircleCI ジョブを開始すると、CircleCI により OpenID Connect トークンに署名が行われ、トークンがジョブで使用できるようになります。 クラウドプロバイダーはジョブからトークンを提示されるとアイデンティティ確認を行い、一時的な認証情報を提供して所定の処理を許可します。

CircleCI の OIDC トークンの構造について詳しくは、こちらのドキュメントを参照してください。

OpenID Connect トークンを使用して CircleCI ジョブと AWS を接続する

それでは、サンプルを使って、CircleCI の OpenID Connect トークンを使用して AWS と接続する方法を確認しましょう。 最初に、CircleCI の OpenID Connect トークンを信頼するように AWS アカウントを設定します。この設定は 1 回行えば、次回以降は必要ありません。 その後、このトークンを使用して AWS と接続するジョブを実行し、イメージを ECR にアップロードします。

AWS のセットアップ

まず行う手順は、AWS での IAM ID プロバイダーと IAM ロールの作成です。 この手順により、AWS アカウントで CircleCI の OpenID Connect トークンが信頼されるようになります。 この設定は 1 回行うだけで構いません。

初めに、IAM コンソールで OpenID Connect ID プロバイダーを作成します。 組織 ID が必要になるので、CircleCI の組織設定に移動し Contexts (コンテキスト) ページで Organization ID (組織 ID) をコピーします。

[プロバイダの URL] には https://oidc.circleci.com/org/<organization-id> と指定します。<organization-id> は CircleCI の組織 ID に置き替えてください。

対象者 にも同じ組織 ID を指定します。

次に、IAM コンソールで ロールを作成します。 信頼されたエンティティとして、ウェブ ID を選択した後、先ほど作成した ID プロバイダーを選択します。 Audience (対象者) では、現状唯一の選択肢である Add permissions (アクセス許可の追加) を選択します。 これで、CircleCI ジョブに許可する処理と許可しない処理を指定できます。 AWS のベストプラクティスに従い、ジョブに必要なアクセス許可だけを選択するようにしてください。

注: ニーズに応じて、独自のポリシーを作成することもお勧めします。

CircleCI ジョブと AWS を接続する

AWS のセットアップが完了したので、AWS と接続する CircleCI ジョブを作成しましょう。

ワークフローに CircleCI ジョブのうち、OpenID Connect トークンを付与するものを選択します。 トークンを付与するジョブでは、必ずコンテキストを使用してください。 OpenID Connect トークンを使用できるのは、少なくとも 1 つのコンテキストを使用するジョブだけだからです。 この場合のコンテキストは、環境変数を含まないものでかまいません。 CircleCI の OIDC トークンについて詳しくは、こちらの OIDC トークンに関するドキュメントを参照してください。

ジョブで OpenID Connect トークンを使用し、AWS STS の AssumeRoleWithWebIdentity を利用します。 このためには、下記の情報が必要です。

  • 処理を行う AWS リージョン
  • 先ほど作成した IAM ロールの ARN

以下に、AWS CLI の assume-role-with-web-identity サブコマンド を使用して AWS と認証する設定ファイルの例を示します。 この例では、認証が成功したかどうか確かめるために、認証後に AWS で簡単な処理 (aws sts get-caller-identity) を行います。 この処理の部分は、S3 バケットへのアップロード、ECR へのプッシュ、EKS の操作など、お好きな処理に置き換えてください。

jobs:
  deploy:
    docker:
      - image: amazon/aws-cli
    environment:
      AWS_DEFAULT_REGION: <your AWS region here>
      AWS_ROLE_ARN: <your role ARN here>
    steps:
      - run:
        name: 認証と接続
        command: |
          # OpenID Connect トークンを使用して AWS 認証情報を取得
          read -r AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \<<< \
            $(aws sts assume-role-with-web-identity \
             --role-arn ${AWS_ROLE_ARN} \
             --role-session-name "CircleCI-${CIRCLE_WORKFLOW_ID}-${CIRCLE_JOB}" \
             --web-identity-token $CIRCLE_OIDC_TOKEN \
             --duration-seconds 3600 \
             --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
             --output text)
          export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
          # AWS で処理を実行
          aws sts get-caller-identity

高度な使用方法

CircleCI の OIDC トークン要求形式を利用すると、AWS で CircleCI ジョブに許可する処理を制限できます。 たとえば、特定の AWS リソースにアクセス可能なプロジェクトを限定する場合は、目的のプロジェクトに含まれる CircleCI ジョブのみがそのロールを引き受けるように IAM ロールを制限します。

そのためには、IAM ロールの信頼ポリシーを編集し、目的のプロジェクトの OIDC トークンのみがロールを引き受けるように設定します。 つまり、信頼ポリシーでロールの引き受け条件を規定します。

まず、[Project Settings (プロジェクト設定)] > [Overview (概要)] でプロジェクト ID を確認します。

プロジェクト設定の概要

次に、目的のプロジェクトに含まれるジョブだけがそのロールを引き受けるように、ロールの信頼ポリシーに以下の条件を追加します。

"StringLike": {
  "oidc.circleci.com/org/<your organization ID>:sub": "org/<your organization ID>/project/<your project ID>/user/*"
}

上記の条件で、StringLike を使用してプロジェクトに含まれる CircleCI の OIDC トークンのサブ要求を照合しています。 これで、他のプロジェクトのジョブは、このロールを引き受けられないようになりました。

おわりに

おつかれさまでした! これで、コンテキストの代わりに CircleCI の OpenID Connect トークンを使うようにジョブを設定できました。 OpenID Connect トークンを利用することでキーの入れ替えの手間をなくし、ジョブとクラウドプロバイダーをセキュアに接続できますので、ぜひお試しください。