ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

OIDCを用いたCircleCIのAWS認証

インフラ基盤グループの丹羽です。この記事では、最近取り組んできたCircleCIのAWS認証のOIDC化の過程とその結果についてご紹介したいと思います。

きっかけ

きっかけとなったのは、まだ記憶に新しい今年(2023年)1月に発生したCircleCIのセキュリティインシデントです。弊社でもCircleCI側に保存していたシークレット類を総入れ替えする必要に迫られました。この際、AWSとの連携にアクセスキーを用いているプロジェクトが思いのほか多く、したがって入れ替えの手間もかかったため、今後のインシデント再発対策やセキュリティ向上の観点から本腰を入れて取り組むことになりました。

OIDCとは

ここで、OIDCとは何かについて確認しておきたいと思います。正式名称はOpenID Connectといい、OAuth 2.0にエンドユーザのアイデンティティ検証を追加した認証プロトコルです。OAuthはサービス内のリソースアクセスを許可する認可プロトコルですが、これに認証のレイヤーを加えることでサービス間で安全なやり取りを実現しています。

CircleCIとAWSの連携で説明すると、あらかじめCircleCIのOrganization(組織)とAWSアカウントの間で信頼関係を築いておいて、CircleCIのワークフローからAWSリソースを利用したい場合はAWSにIAMロールに紐付いた一時クレデンシャルを発行してもらい、これを使って各リソースにアクセスします。一時クレデンシャルには期限があるため、万が一セキュリティインシデントが再発して悪意ある第三者に窃取されても、アクセスキーに比べれば危険性は格段に低くなるというわけです。

導入作業

ここからは公式ドキュメントに沿って、実際の導入作業を見ていきます。

まず、AWSコンソールでOIDC IDプロバイダーを作成します。先に説明したCircleCIとAWS間の信頼関係の構築に該当します。

次に、IAMロールを作成して、CircleCIワークフロー内で必要となるAWSリソースの権限を追加します。IAMロールを作成する際に信頼されたエンティティとしてウェブアイデンティティを選択し、一つ前の手順で作成したIDプロバイダーを指定します。ロールの信頼ポリシーに条件を追加して、プロジェクトごとに使用できるIAMロールを分けることもできます。

CircleCIコンソールに移り、Organization SettingsContextsからコンテキストを作成します。ここにAWSから発行された一時クレデンシャルなどを格納します。名前は自由に設定できますが、ここでは便宜的にaws-oidcと名付けます。

最後に、ワークフローを修正します。まずは.circleci/config.ymlのバージョンを2.1にして、aws-cli Orbを追加しておきます。

version: 2.1

orbs:
  aws-cli: circleci/aws-cli@3.1.5

続いて、ジョブ内でAWSリソースを追加するステップの前に、一時クレデンシャルを発行するステップを追加します。

jobs:
  build:
    docker:
      - image: cimg/aws:2022.06
    environment:
      AWS_DEFAULT_REGION: ap-northeast-1
      AWS_ROLE_ARN: arn:aws:iam::123456789012:role/OIDC-ROLE
    steps:
      - checkout
      - aws-cli/setup:  # このステップを追加
          role_arn: ${AWS_ROLE_ARN}
          region: AWS_DEFAULT_REGION
          role_session_name: "example-session"
          session_duration: "3600"
      - run: 
          # AWSリソースを使用するステップ

そして最後に、ワークフローのジョブ実行で先ほど作成したコンテキストを指定します。

workflows:
  version: 2
  build:
    jobs:
      - build:
          context:
            - aws-oidc

困った点

ここまではスムーズに進行しましたが、ここで問題が発生しました。弊社ではCircleCIのワークフロー実行にECR上の自前のコンテナイメージを使用する場合が多いため、ECRのアクセス権限も必要になってくるのですが、上記の方法ではこれが実現できません。実行イメージの指定はジョブのステップの外側で行われているためです。

これを解決するには、あるジョブで取得したECRパスワードを別のジョブに動的に設定してECRにアクセスする、といった処理が必要になってきます。しばらく悩みましたが、最終的にダイナミックコンフィグ機能を使うことで解決しました。

ダイナミックコンフィグはセットアップと本番の2回ワークフローを実行し、セットアップから本番に動的に値を渡すことができるというものです。これを使って.circleci/config.ymlを修正してみます。

まず、セットアップと本番を切り替えるsetupフラグを用意します。また、値の受け渡しに使うパラメータとcontinuation Orbを追加します。

version: 2.1

setup: << pipeline.parameters.is-setup >>

parameters:
  is-setup:
    type: boolean
    default: true
  ecr-password:
    type: string
    default: ""

orbs:
  aws-cli: circleci/aws-cli@3.1.4
  continuation: circleci/continuation@0.2.0

続いて、ECRパスワードを取得するジョブを追加します。

jobs:
  get-ecr-password:
    docker:
      - image: cimg/aws:2022.06
    environment:
      AWS_DEFAULT_REGION: ap-northeast-1
      AWS_ROLE_ARN: arn:aws:iam::123456789012:role/OIDC-ROLE
    steps:
      - checkout
      - aws-cli/setup:
          role-arn: ${AWS_ROLE_ARN}
          aws-region: AWS_DEFAULT_REGION
      - run:
          name: get-ecr-password
          command: |
            export ECR_PASSWORD=$(aws ecr get-login-password)
            echo '{"is-setup":false,"ecr-password":"'"${ECR_PASSWORD}"'"}' | tee -a .circleci/continuation-parameters.json
      - continuation/continue:
          configuration_path: .circleci/config.yml
          parameters: .circleci/continuation-parameters.json

取得したECRパスワードはcontinuation Orbの機能で本番のワークフローに引き継がれるので、そちらでECRの認証に使用します。

  build:
    docker:
      - image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ci:latest
        auth:
          username: AWS
          password: << pipeline.parameters.ecr-password >>

最後にワークフローを二段構えにします。最初はsetupフラグが真なのでECRパスワードを取得するセットアップワークフローが実行され、そこから渡された変数によってsetupフラグが偽となり本番ワークフローが実行されるというわけです。ちょっとトリッキーでややこしいですね。

workflows:
  get-ecr-password:
    when: << pipeline.parameters.is-setup >>
    jobs:
      - get-ecr-password:
          context:
            - aws-oidc
  build:
    when:
      not: << pipeline.parameters.is-setup >>
    jobs:
      - build:
          context:
            - aws-oidc

Orbの自作

こうしてOIDC対応を実現できましたが、上記のような処理を入れていくうちに.circleci/config.ymlの記述が冗長になってしまいました。社内でこの件を相談したところ、一連の処理をOrbにまとめてみてはどうかと意見をもらい、挑戦してみることにしました。

最終的にこちらのOrbをリリースすることができました。使いどころはだいぶ限定されますが、OIDC対応にあたって同じ悩みを抱えておられる方の助けになれば幸いです。

驚愕の事実

…と、ここまでOIDC認証でECRイメージを使用するテクニックについて長々と書いてきましたが、この記事を書くために最新の公式ドキュメントを参照したところ、なんとECRイメージを使用するのが格段に楽になっていました

これによると、ダイナミックコンフィグのような複雑な機構は必要なく、IAMロールのARNを指定するだけで良いようです。

  build:
    docker:
      - image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ci:latest
        aws_auth:
          oidc_role_arn: arn:aws:iam::123456789012:role/OIDC-ROLE

今までの苦労は…という気持ちもなきにしもあらずですが、CircleCIに関して色々勉強にもなりましたし、何より公式にOIDC対応を進めてくれたことはありがたいので、今後はこちらの書き方を採用していきたいと思います。

まとめ

AWS認証のOIDC対応を進める中で、社内の問題を解決するところから一歩踏み込んで、ささやかながら自作Orbをリリースすることができました。

くふうAIスタジオでは安全で効率的なインフラを構築し、業務を通じて共に成長していける仲間を募集中です!