👷

Bitbucket Pipelinesでもアクセスキー無しにOIDCでTerraformを実行する

2022/12/05に公開

この記事は、下記アドベントカレンダー5日目の記事です。


BitbucketでもOpenID Connectを使ってIAMアクセスキー無しにAWSへデプロイ出来ます

公式ドキュメント

上記公式を見たら終わる話なんですが、Terraform文脈でBitbucket PipelinesもOIDC対応していることに言及されている記事が少なかったので書いてみます。
世の中GitHub Actionsの記事ばっかりですがBitbucket派もいるんや!😭

なぜOIDCを使いたいかというと、TerraformでAWSの様々なリソースを作成することから AdministratorAccess など強力な権限を渡しがちです。そんな絶大な権限をもったアクセスキー/シークレットキーなど正直管理したくありません。ローテーションも面倒なのでしたくないです。OIDCを使うとアクセスキー無しに一時的な認証情報でAWSにアクセス出来るのでよりセキュア運用できます。Terraform Cloudも検討したのですがOIDCが使えないという点で断念しました。信用できないというよりは、管理出来る自信が無いという理由です。

1. Bitbucketリポジトリから情報を取得する

Repository settings -> OpenID Connect
Repository settings -> OpenID Connect

リポジトリ設定から「OpenID Connect」を選んで Identity provider URLAudience をメモします。

2. AWSのIAMにてIDプロバイダーを設定する

IAM -> IDプロバイダ
IAM -> ID プロバイダ

AWSマネジメントコンソールのIAMにて「ID プロバイダ」を選択し、「プロバイダを追加」を押します。「プロバイダのURL」欄にさきほどの Identity provider URL を貼り付けて「サムプリントを取得」を押します。

「対象者」欄にさきほどの Audience を貼り付けて「プロバイダを追加」を押します。

3. AWSのロールを割り当てる

作成されたIDプロバイダをクリックして詳細を開き、右上の「ロールの割り当て」を押して「ロールを作成」を押す。「ウェブアイデンティティ」を選び「アイデンティティプロバイダー」でさきほど設定したBitbucketを選択する。Audienceも同様に選択する。許可する権限は AdministratorAccess など適宜設定します。
作成されたロールのARNをメモします。

IAM -> ロール -> ロールを作成
IAM -> ロール -> ロールを作成

4. Bitbucket Pipelinesを設定する

Bitbucketリポジトリの設定「Repository variables」にてリポジトリ変数設定出来ますので、AWS IAMロール名の変数を作成します。

変数名(例) 値(例)
AWS_ROLE_NAME arn:aws:iam::1234567890:role/your-role-name

次にリポジトリルートに bitbucket-pipelines.yml を設定して挙動を設定します

bitbucket-pipelines.yml
# Terraform docs
# https://developer.hashicorp.com/terraform/docs
# Bitbucket Pipelines OpenID Connect guide
# https://support.atlassian.com/ja/bitbucket-cloud/docs/deploy-on-aws-using-bitbucket-pipelines-openid-connect/

image: hashicorp/terraform:1.3.4

pipelines:
  pull-requests:
    '**': #this runs as default for any branch not elsewhere defined
      - parallel:
          - step:
              name: Terraform Plan
              oidc: true
              script:
                - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
                - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
                - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
                - cd $BITBUCKET_CLONE_DIR/stg/exampleA
                - terraform init -input=false
                - terraform validate
                - terraform plan -input=false
          - step:
              name: tflint
              image: ghcr.io/terraform-linters/tflint-bundle:latest
              oidc: true
              script:
                - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
                - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
                - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
                - cd $BITBUCKET_CLONE_DIR/stg/exampleA
                - tflint --init --config .tflint.hcl
                - tflint
          - step:
              name: tfsec
              image: aquasec/tfsec-ci:latest
              oidc: true
              script:
                - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
                - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
                - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
                - cd $BITBUCKET_CLONE_DIR/stg/exampleA
                - tfsec
  custom:
    staging:
      - step:
          name: Terraform Plan
          oidc: true
          script:
            - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
            - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
            - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
            - cd $BITBUCKET_CLONE_DIR/stg/exampleA
            - terraform init -input=false
            - terraform validate
            - terraform plan -input=false
      - step:
          name: Deploy to Staging
          oidc: true
          deployment: staging
          trigger: manual
          script:
            - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
            - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
            - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
            - cd $BITBUCKET_CLONE_DIR/stg/exampleA
            - terraform init -input=false
            - terraform apply -input=false --auto-approve

    production:
      - step:
          name: Terraform Plan
          oidc: true
          script:
            - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
            - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
            - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
            - cd $BITBUCKET_CLONE_DIR/prod/exampleA
            - terraform init -input=false
            - terraform validate
            - terraform plan -input=false
      - step:
          name: Deploy to Production
          oidc: true
          deployment: production
          trigger: manual
          script:
            - export AWS_ROLE_ARN=$OIDC_ROLE_ARN
            - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
            - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
            - cd $BITBUCKET_CLONE_DIR/prod/exampleA
            - terraform init -input=false
            - terraform apply -input=false --auto-approve

上記はTerraformのモジュールを使ってステージング環境とプロダクション環境にapply(デプロイ)する時の設定です。
OIDC: true でOIDC有効にします。

- export AWS_ROLE_ARN=$OIDC_ROLE_ARN
- export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
- echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token

の3行ではさきほどの変数も使いAWSロールを引き受けワンタイムな認証情報をセットします。

あとはお好みですが、プルリクエスト時にはterraform planやtflint/tfsecなどツールを使いチェックを行います。Mainブランチにコミットしたら自動で本番applyする設定をする場合もありますが、ここでは custom セクションを使って手動でパイプラインを動かすようにしています。

5. Pipelinesを動かしてTerraformが動くことを確認する

プルリクエストを作ったり、手動でステージング環境へapplyして確認します。

TerraformというかIaCの好きなところ

OIDCを使うメリットは冒頭に書きましたが、せっかくなのでTerraformとCI/CDの組み合わせで好きなところも書いておきます。

各ユーザーに強い権限渡さなくて良くなる

これはCI/CD(ここではBitbucket Pipelines)に権限設定しておけば、Terraformコード書いてコミットしたらAWSリソースが作れるので個々のユーザーに権限を必要に応じて追加などしなくても済みます。むしろ運用に乗せる上で、マネジメントコンソールで手動で変更できないようにしていきます。

プルリクエストが申請/承認フローに出来る

IAMユーザーやロールは勝手に作られては困るものもプルリクエストの形でワークフローが作れて統制が出来ます。
リソースの命名規則のチェックも出来ます。

コミットログやCI/CDのログが変更履歴になる

これはAWS側にもCloudtrailやConfigもありますが、個人的にはリポジトリ側のログの方が誰がいつ一連のリソースを変更したか見やすくて便利です。
AWS側のログは理由/背景までは分かりませんが、コミットログにWhyを書くルールにしていると後から把握しやすいですね。チケット番号書くともっといいですね。Bitbucketの場合はJIRAの課題番号を書くと自動でリンクされます。

tfsecでセキュリティ的に良くない設定も作る前に指摘してくれる

暗号化していない、セキュリティグループが甘い、ログ保存設定していない等漏れている場合はCIで指摘してくれるので、いちいち指摘しなくて良いですし角も立たないので便利です。

というわけでTerraform + OIDCはクレデンシャル管理としてもAWS統制の上でも良いぞ、Bitbucketでも十分使えるぞという話でした。

そのうち書く

  • 1passwordとGoogle WorkspaceをSCIM Bridgeを立てて自動プロビジョニングした話
  • Mailgunで迷惑メール報告を受けたらSlackに通知している話

Discussion