Terraform CloudでマルチAWSアカウントに対しDynamic Credentialを設定する方法

はじめに

Terraform Cloud(以下TFC)では、AWSアカウントに接続する際、静的なクレデンシャル(いわゆるアクセスキー)を使わず、Dynamic Credentialを使うことで、よりセキュアな接続をすることができます。

Terraformはその性質上、高権限が必要な場合が殆どですので、なるべくアクセスキーは使いたくない・・・といった場合に活躍しますね👍

ですが1つのワークスペースから、複数AWSアカウントに対してのDynamic Credential設定に関する情報が、公式のものも含めて少なく苦戦したので、ここに書き残そうと思います。

複数のAWSアカウントに接続したいケースとして、例えば

  • AアカウントとBアカウントをVPCピアリングで接続したい
  • Aアカウントで構築したWebシステムのDNSレコードをBアカウントに登録したい

といったものがあると思います。

ゴール

  • TFC環境にて1つのワークスペースから複数AWSアカウントに接続するためのDynamic Credentialを設定する

書かないこと

  • 非TFC環境におけるAWSアカウント接続

まずはシングルAWSアカウントとのDynamic Credential設定

マルチAWSアカウント接続を設定する前に、シングルAWSアカウント接続について理解しておく必要があります。

この記事に辿り着いた方の多くは、ここはすでにクリアされていることも多いでしょうし、公式のドキュメントや、紹介記事も多数ありますので詳細は割愛します。

公式ドキュメント developer.hashicorp.com

Zenn(moaikids様) zenn.dev

簡単に書くと、以下の設定が必要です。

  • OIDC設定
  • IAMロール
  • TFC上にVariables(TFC_AWS_PROVIDER_AUTHTFC_AWS_RUN_ROLE_ARN)を作成 (Variable SetでもOK)

OIDCとIAMロールについては、一撃で作成できるモジュールがHashicorp公式で公開されていますので、これを使うのがオススメです。 github.com

注意事項としては、TFCでこれらOIDC、IAMロール作成を行わないことです! TFCの動作の前提になるものなので、別で管理すべきでしょう。

VariablesについてはTFCでもOKです👍

マルチAWSアカウントとのDynamic Credentials設定

ここからが本題です。 実は上記の公式ドキュメントの一番下にシレっと書いてあるのですが、初見ではなかなか分かりづらいです。

私も色々検証したり、サポートに聞いたりしてようやく設定できました。

AWSへの設定

まず、対象となるAWSアカウント(ここではアカウントA、アカウントB、とします)のそれぞれに、シングルアカウントの時と同様にOIDC設定とIAMロール作成を行います。 ここまでは同じ。

TFCへの設定

次にVariablesですが、以下のように作成します。

アカウントA用

Variable名
TFC_AWS_PROVIDER_AUTH_accountA true
TFC_AWS_RUN_ROLE_ARN_accountA 作成したアカウントAのIAMロールARN
例:arn:aws:iam::123456789012:role/tfc-role

アカウントB用

Variable名
TFC_AWS_PROVIDER_AUTH_accountB true
TFC_AWS_RUN_ROLE_ARN_accountB 作成したアカウントBのIAMロールARN
例:arn:aws:iam::987654321098:role/tfc-role

ポイントは、変数名にサフィックスとして付与している_accountAとか_accountBのところです。変数の値はシングルアカウントの時と全く同じです。 サフィックス文字列自体は何でも良いのですが、後述のTerraformコードで、AWSプロバイダのエイリアスとして指定します。

Terraformコード

providers.tfに以下のように記載します。

# AWSマルチ認証設定
variable "tfc_aws_dynamic_credentials" {
  description = "Object containing AWS dynamic credentials configuration"
  type = object({
    default = object({
      shared_config_file = string
    })
    aliases = map(object({
      shared_config_file = string
    }))
  })
}

# アカウントA
provider "aws" {
  alias  = "accountA"
  region = "ap-northeast-1"
  shared_config_files = [
    var.tfc_aws_dynamic_credentials.aliases["accountA"].shared_config_file,
  ]
}

# アカウントB
provider "aws" {
  alias  = "accountB"
  region = "ap-northeast-1"
  shared_config_files = [
    var.tfc_aws_dynamic_credentials.aliases["accountB"].shared_config_file,
  ]
}
コード解説
  • 2〜12行目のvariable "tfc_aws_dynamic_credentials" {...}
    ここは決まり文句ですので、公式ドキュメントの内容を何も考えずにコピペします。考えたら負け!

  • 15〜21行目
    アカウントAに対する接続の設定になります。Variablesのサフィックスに指定した文字列の先頭アンダースコアを除いたものaccountAを19行目で指定します。 aliasはコード上で呼び出す際の文字列なので何でも良いのですが、揃えた方がわかりやすいのでaccountAとしています。

  • 24〜30行目
    アカウントBに(略

ここまで来たら、あとはリソース作成時に対象AWSアカウント(プロバイダ)を指定するだけです!

resource "aws_instance" "web" {
  provider = aws.accountA
  ...
}

おまけ

terraform.tfには必ずcloudブロックを記載するようにしましょう。 こうすることで、プッシュする前にローカルでterraform planする際にも、terraform loginしておくことで設定したDynamic Credential設定を使ってくれていい感じです。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 5.66.0"
    }
  }

  required_version = "= 1.9.5"

  cloud {
    organization = "hoge"
    hostname     = "app.terraform.io"
    workspaces {
      name = "fuga"
    }
  }
}