AWS FargateでAtlantisを動かして、AWSリソースに対するTerraformのPlanのレビューやApplyをPR上で行なっていたのですが、GCPリソースに対しても必要になってきたので、その時に行った作業のメモ。
2022.12.23: 下記の方法は、起動直後は動くが、AWS_SESSION_TOKENの有効期限が切れると動かなくなる。 完全にうっかりしてました。 現在、他の方法について検証中。 リベンジ編は果たしてあるか!?
前提
前提としては、GCPの鍵ファイルを生成せずにやりたいというのがあります。 鍵ファイルの管理・ローテーションは煩わしいし、穴になる可能性もあるので。
タスクロール
Fargate上のタスクとしてAtlantis を動かしていおり、すでにタスクロールがあるのでこのタスクロールを利用します。
GCP(Google Cloud)側
GCP側では、サービスアカウント、Workload Identity Pool の作成を行なっていきます。 ドキュメントとしては「Workload Identity 連携の構成」のところ。
必要なリソースをTerraformで書くと以下のような感じに。
aws_account_id
、aws_task_role_name
は適宜、自分の環境のものを設定する感じです。
locals { aws_account_id = "000000000000" aws_task_role_name = "ecs_task_role" } #### # Atlantis が利用するサービスアカウント resource "google_service_account" "atlantis" { account_id = "atlantis" description = "Service Account for Atlantis (Managed by Terraform)" } resource "google_project_iam_member" "atlantis" { project = var.gcp_project role = "roles/editor" member = "serviceAccount:${google_service_account.atlantis.email}" } #### # AWS 連携のための Workload Identity resource "google_iam_workload_identity_pool" "atlantis" { workload_identity_pool_id = "atlantis" display_name = "atlantis" description = "Pool for Atlantis (Managed by Terraform)" } resource "google_iam_workload_identity_pool_provider" "atlantis" { workload_identity_pool_provider_id = "aws-atlantis" workload_identity_pool_id = google_iam_workload_identity_pool.atlantis.workload_identity_pool_id aws { account_id = local.aws_account_id } attribute_mapping = { "google.subject" = "assertion.arn" "attribute.aws_role" = "assertion.arn.contains('assumed-role') ? assertion.arn.extract('{account_arn}assumed-role/') + 'assumed-role/' + assertion.arn.extract('assumed-role/{role_name}/') : assertion.arn" } attribute_condition = "attribute.aws_role=='arn:aws:sts::${local.aws_account_id}:assumed-role/${local.aws_task_role_name}'" } #### # Workload Identity とサービスアカウントの関連付け resource "google_service_account_iam_binding" "atlantis" { service_account_id = google_service_account.atlantis.id role = "roles/iam.workloadIdentityUser" members = [ "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.atlantis.name}/attribute.aws_role/arn:aws:sts::${local.aws_account_id}:assumed-role/${local.aws_task_role_name}" ] }
これをapplyしたら、構成ファイルを取得します。
プール詳細画面の右側にあるペインで、接続済みサービスアカウント
に行けば、ダウンロードできます。
AWS 側
パラメータストアの準備
構成ファイルの場所は、環境変数 GOOGLE_APPLICATION_CREDENTIALS
で指定するのですが、GCPのシークレットのようにファイルとしてみせるような設定ができないので、
のやり方を真似る感じにします。
私の場合は、パラメータストアに構成ファイルのデータを保存し、それを GOOGLE_APPLICATION_CREDENTIALS_DATA
環境変数で取得できるようにして、 GOOGLE_APPLICATION_CREDENTIALS
環境変数の指すファイルに保存しています。
なお、保存するデータですが、
にあるように、メタデータURLがECSタスクの場合は異なるので、credential_source の url, region_url を消して登録しています。
カスタム Atlantis イメージの作成
構成ファイルの環境変数からのファイル化と、ECSタスクのメタデータURLからAWSクレデンシャルを取得するために、カスタム Atlantis Dockerイメージを作成します
こんな wrapper.sh
#!/usr/bin/dumb-init /bin/sh set -e echo $GOOGLE_APPLICATION_CREDENTIALS_DATA | jq . > $GOOGLE_APPLICATION_CREDENTIALS token_file=$(mktemp) # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html curl -sS "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" > "$token_file" AWS_ACCESS_KEY_ID=$(jq -r ".AccessKeyId" "$token_file") AWS_SECRET_ACCESS_KEY=$(jq -r ".SecretAccessKey" "$token_file") AWS_SESSION_TOKEN=$(jq -r ".Token" "$token_file") rm "$token_file" export AWS_ACCESS_KEY_ID export AWS_SECRET_ACCESS_KEY export AWS_SESSION_TOKEN exec docker-entrypoint.sh "$@"
を用意して、Docerfile が以下のような感じにしています。
FROM ghcr.io/runatlantis/atlantis:latest RUN apk update && apk add jq COPY wrapper.sh /usr/local/bin/wrapper.sh ENTRYPOINT ["wrapper.sh"] CMD ["server"]
なお、wrapper.sh
のところで、jq を通して $GOOGLE_APPLICATION_CREDENTIALS
に書き出していますが、これは必須ではなくて、パラメータストアに保存したデータが改行情報を失っているので、どうせ jq 入れていることもあり綺麗にしておこう、くらいな感じです。
DockerビルドしたイメージをECRレポジトリに登録しておきます。
タスク定義の更新
あとは、タスク定義において * GOOGLE_APPLICATION_CREDENTIALS * GOOGLE_APPLICATION_CREDENTIALS_DATA
の環境変数を設定し(以下のような感じ)
イメージURLを登録したECRリポジトリのものに変更した上で、デプロイすればOK。
以上で、PR上でAtlantisからGCPにapplyできるようになります。
1つのAtlantisで複数クラウドを管理したい方の参考になれば!
補足:複数のGCPプロジェクトを対象とする場合
複数のGCPプロジェクトを対象とする場合は、上記で作成したサービスアカウント(のメールアドレス)に対して、各プロジェクトのIAMで権限付与すればOKです。