背景
方針
- 可能な限り余計な情報を持ちたくないので、OIDCを用いた仕組みを構築したい
- 手元での実行もありうるが変にロックを掛け合いたくないので、terraformのstate/lockはリモートで管理したい
まずはOIDCを組むために、AWSアカウントと何かしらを紐づけて管理する必要がある。
今回はDeploymentsの1つの設定とAWSアカウントが紐づく形をとることで解決を図る。
2つ目はterraformを実行するために必要な設定となるのでAWS CLIを用いて作成する。
これ自体は別に都度回すようなものでもないので、手作業。
実装
1. BitBucket PipelineとAWSでOIDCができるようにする
参考:
Bitbucket Pipelines OpenID Connect を使用して AWS にデプロイする | Bitbucket Cloud | Atlassian サポート
基本的には上記公式ドキュメントを参考にして設定をしてあげれば良い。
前提として、設定を行う際にはリポジトリの設定を行える権限を持っている必要がある。
これがないとリポジトリごとの"プロバイダーURL"を取得できない。
a. リポジトリのプロバイダーURLとAudience等必要な値を取得しておく
リポジトリの設定画面から、OpenID Connectの設定画面を開く。
IDプロバイダの設定に必要なIdentity provider URL
とAudience
の値を取得しておく。
さらに、後続で必要となる環境ごとの識別に必要なDeployments EnvironmentのUUIDを拾っておく。
ここではTestのEnvironmentに対して20af...のUUIDが発行されている。
Uniquer identifiersのexample payloadにある通りのリクエストがAWSに送られ、AWSはそれを検証するイメージ。
なのでAWS側にどの値を許可するのかを設定してあげる必要がある。
b. IDプロバイダの設定を行う
terraformの連携先となるAWSのアカウントにログインし、IAMからIDプロバイダの設定を行う。
基本的には公式ドキュメントに書いてある通りなので引っかかりそうなポイントだけ記載する。
この画面での完成形は下図のようになる。
- プロバイダを追加する際は、OpenID Connectを選択する
- プロバイダのURLはリポジトリごとに払い出されるURLを指定する
- 念の為サムプリントを取得して、疎通を確認しておく
- 対象者には、Audienceの値を入力する
- わかりやすい名前で、と思って適当な名前入れたらIAM Roleの検証で詰まって時間溶かしたので要注意...
c. IAM Roleの作成
ここで設定したIAM Roleを使ってterraformはアクセスをする。
ので、紐づけるIAM Policyは作成したいリソースに限ったものを付与する。
ここでは面倒なのでAdministratorの権限をつけてしまうが、ちゃんとやるなら最小権限の原則に従う。
ドキュメント記載の通り、エンティティタイプはウェブアイデンティティを選択すること。
選択すると、上で作成したbitbucketのリポジトリをIDプロバイダとして選択できるようになる。
また、IDプロバイダを選択するとAudienceも選択できる。
(ここでは)ポリシーにはAdministratorAccessを選択する。
ロールの名前や説明は適当にやっておく。
信頼されたエンティティを選択するでは前述したDeploymentsを指定してそれ以外の環境からのアクセスは弾くようにしておきたい。
なので、以下のようにConditionsを変更する
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRoleWithWebIdentity", "Principal": { "Federated": "arn:aws:iam::xxxxxxxxxxxx:oidc-provider/api.bitbucket.org/2.0/workspaces/xxxxxxxxxx/pipelines-config/identity/oidc" }, "Condition": { - "StringEquals": { - "api.bitbucket.org/2.0/workspaces/xxxxxxxxxx/pipelines-config/identity/oidc:aud": [ - "ari:cloud:bitbucket::workspace/2ab1d73d-ec10-4934-8233-xxxxxxxxxxxx" - ] - } + "StringLike": { + "api.bitbucket.org/2.0/workspaces/xxxxxxxxxx/pipelines-config/identity/oidc:sub": "{b0ef120b-014d-4b4f-96c8-xxxxxxxxxxxx}:{20af05a4-cc1b-4e45-ad22-xxxxxxxxxxxx}:*" + } } } ] }
元々はAudienceが一致しているかを見ていたが、今回はDeploymentsまで含めて見たいのでsubjectの値を見る。
公式ドキュメントで言えば ここらへん で言及されている話題になる。
Roleの作成ができたら一旦完了。 これを環境分繰り返しておく。もちろん、環境ごとにDeployments のUUIDを変えること。
d. とりあえず動作して確認する
一旦動かして確認するのであれば以下のようなbitbucket pipelineを用意して確認するとよさそう。
image: atlassian/pipelines-awscli pipelines: branches: test: - step: name: s3 ls on test deployment: test oidc: true script: - export AWS_REGION=ap-northeast-1 - export AWS_ROLE_ARN=arn:aws:iam::xxxxxxxx:role/BitbucketPipelineForTesting - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token - unset AWS_SECRET_ACCESS_KEY - unset AWS_ACCESS_KEY_ID - aws s3 ls staging: - step: name: s3 ls on staging deployment: staging oidc: true script: - export AWS_REGION=ap-northeast-1 - export AWS_ROLE_ARN=arn:aws:iam::yyyyyyyy:role/BitbucketPipelineForTesting - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token - unset AWS_SECRET_ACCESS_KEY - unset AWS_ACCESS_KEY_ID - aws s3 ls
重要なのは、AWS_ACCESS_KEY_IDを一度unsetしておくこと。
これがあるとどちらも同じアカウントで動作してしまう。たぶんAWS CLIの内部でAWS_ACCESS_KEY_IDが優先されているだけだと思うんだけど。
今回は AWS_WEB_IDENTITY_TOKEN_FILE が指定されているのでそちらをベースに動く。
terraformも同様の動作をする。
もしかしたら共通のステップを用意して、AWS_ROLE_ARNをdeploymentのvariableに指定すればもっと綺麗になるかもしれないがご容赦いただければと。
2. terraform用に必要なリソースを準備する
方針に記載の通り、リモートでstate/lockを管理したい。
stateはS3で、lockはdynamodbで管理するのが一般的っぽいのでそちらを。
a. S3 バケットの作成
特に複雑に考えず、ap-northeast-1にバケットを作成し、public accessをブロックし、バージョニングを有効にしているだけ。
export BUCKET_NAME=tekitouna-tfstate-dev aws s3api create-bucket --bucket $BUCKET_NAME --region ap-northeast-1 --create-bucket-configuration LocationConstraint="ap-northeast-1" aws s3api put-public-access-block --region ap-northeast-1 --bucket $BUCKET_NAME --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true aws s3api put-bucket-versioning --region ap-northeast-1 --bucket $BUCKET_NAME --versioning-configuration Status=Enabled
b. dynamodbのテーブル作成
export TABLE_NAME=tekitouna-tflock-dev aws dynamodb create-table \ --attribute-definitions "AttributeName=LockID,AttributeType=S"\ --table-name "${TABLE_NAME}" \ --key-schema "AttributeName=LockID,KeyType=HASH" \ --billing-mode PAY_PER_REQUEST
LockIDの指定はterraform側の仕様なのでそれに従っている
ref.
Backend Type: s3 | Terraform | HashiCorp Developer
c. ローカルから接続できるか確認する
とりあえずbackend configはファイル指定の方が楽だと思うので以下のようなファイルを作って指定させる
./env/dev.tfbackend
bucket = "tekitouna-tfstate-dev" key = "dev.tfstate" region = "ap-northeast-1" dynamodb_table = "tekitouna-tflock-dev"
./provider.tf
terraform { backend "s3" { } required_providers { aws = { source = "hashicorp/aws" version = "~> 3.70.0" } } }
terraform init -reconfigure -backend-config ./env/dev.tfbackend
で動くことを確認
3. BitBucket Pipelineに実装する
あとは先ほど作成したテスト用のbitbucket pipelineをいじっていい感じにすればOK
image: hashicorp/terraform:1.4 pipelines: branches: test: - step: name: terraform-plan on test deployment: test oidc: true script: - export AWS_REGION=ap-northeast-1 - export AWS_ROLE_ARN=arn:aws:iam::xxxxxxxx:role/BitbucketPipelineForTesting - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token - unset AWS_SECRET_ACCESS_KEY - unset AWS_ACCESS_KEY_ID - terraform init -input=false -backend-config=env/dev.tfbackend -reconfigure - terraform plan -input=false -var-file=env/dev.tfvars staging: - step: name: terraform-plan on staging deployment: staging oidc: true script: - export AWS_REGION=ap-northeast-1 - export AWS_ROLE_ARN=arn:aws:iam::yyyyyyyy:role/BitbucketPipelineForTesting - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token - unset AWS_SECRET_ACCESS_KEY - unset AWS_ACCESS_KEY_ID - terraform init -input=false -backend-config=env/dev.tfbackend -reconfigure - terraform plan -input=false -var-file=env/dev.tfvars