[マルチアカウント]HCP Terraformのdynamic credentials用のAWS IAM RoleをAWS CloudFormation StackSetで作成してみた
AWS CloudFormation StackSetを使用して、HCP Terraform用のIAM Roleを作成してみました。
なぜCloudFormation StackSetを使うか
アカウント数が多い環境のHCP Terraform用のIAM Role作成の手間を削減するためです。
HCP TerraformからAWSアカウントを操作する際は、IAMロールまたはIAMユーザーを事前に作成する必要があります。
これを作成するまでは、HCP TerraformからAWSアカウントは操作できないため、IAMロールの作成の自動化は工夫が必要です。
「手動で作成」「CloudFormationで作成」「Workspaceの実行モードをlocalで実行」等方法は色々あります。
多くの方法では、AWSアカウント数分作業が必要です。
CloudFormation StackSetを利用することで手動作業を最小限に、HCP Terraform IAMロール作成を自動化できます。
構成図
CloudFormation StackSet管理アカウントに、HCP Terraform用のIAM Roleを作成します。
メンバーアカウントは、TerraformからCloudFormation StackSet経由でIAM Roleを作成します。
tfe providerを使えば、TerraformでHCP TerraformのVariable setsも作成できます。
Variable setの作成と同時に、作成したIAM Role ARNをセットします。
TFC_AWS_PROVIDER_AUTH=true
TFC_AWS_RUN_ROLE_ARN=<IAM Role ARN>
この構成を取ることで、メンバーアカウントのIAM Role作成からAWS環境へのアクセス用のVariable set作成までを自動化できます。
やってみた
CloudFormation StackSet管理アカウントにHCP Terraform用のIAM Roleを作成する
以下を参考にCloudFormation StackSet管理アカウントでHCP Terraform用IAM Roleを作成します。
上記をCloudFormationで作成する手順もありますので、ご参考までに。
CloudFormation StackSet管理アカウント アクセス用のVariable setsを作成する
HCP Terraformにアクセスして、Settings
-> Variable set
-> Create organization variable set
の順に選択します。
任意のName・Descriptionを設定します。
参考までに、私は以下のように設定することが多いです。
Name: auth-aws-<AWSアカウントID>
Description: AWS credentials for <AWSアカウント名>
次の手順で作成するWorkspaceに紐づけるため、Variable set scope
はデフォルトの状態(workspace,project未選択)でOKです。
Variablesは以下を設定してください。iam_role_arn
は前の手順で作成したIAM RoleのARNを設定します。
Variable category | Key | Value | Sensitive |
---|---|---|---|
Environment variable | TFC_AWS_PROVIDER_AUTH | true | No |
Environment variable | TFC_AWS_RUN_ROLE_ARN | <iam_role_arn> | No |
最後にCreate variable set
を選択して、Variable setを作成します。
HCP Terraform Token作成を作成する
HCP Terraform Workspace上でtfe providerを使って、Variable setを作ります。
tfe providerを使うために、認証用のHCP Terraform Tokenが必要です。
HCP Terraformのコンソール上で、Settings
-> API tokens
->Team Tokens
-> Create a team token
の順に選択します。
Variable setsが作成可能なTeam(※)を選択し、Create
を選択します。
Tokenが表示されるため、控えておきます。
「CloudFormation StackSet管理アカウント アクセス用のVariable setsを作成する」と同様にVariable setsを作成します。
こちらもVariable set scope
は未設定で問題ありません。
Name: auth-hcp-tf-<HCP Terraform Team名>
Description: AWS credentials for <HCP Terraform Team名>
Variable category | Key | Value | Sensitive |
---|---|---|---|
Environment variable | TFE_TOKEN | <Token> | Yes |
※ Teamをまだ作っていない場合は、owners
を選びましょう。owners
はデフォルトで作成されるTeamで管理者権限を持っています。
CloudFormationテンプレートとTerraformコードを用意する
HCP Terraform WorkspaceのWorkflow TypeにVCS Driven Workflowを使います。
これは、GitHub等のVCS上のコードを使ってHCP TerraformでTerraformを実行するWorkflowです。
次のファイルを作成して、任意のGitHubリポジトリにPushしてください。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'HCP Terraform OIDC Provider and IAM Role'
Parameters:
HCPTerraformOrgName:
Type: String
Description: 'HCP Terraform Organization Name'
HCPTerraformIAMRoleName:
Type: String
Description: 'HCP Terraform IAM Role Name'
Resources:
HCPTerraformOIDCProvider:
Type: 'AWS::IAM::OIDCProvider'
Properties:
Url: 'https://app.terraform.io'
ClientIdList:
- 'aws.workload.identity'
HCPTerraformRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Ref HCPTerraformIAMRoleName
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Federated: !GetAtt HCPTerraformOIDCProvider.Arn
Action: 'sts:AssumeRoleWithWebIdentity'
Condition:
StringEquals:
'app.terraform.io:aud': 'aws.workload.identity'
StringLike:
'app.terraform.io:sub': !Sub 'organization:${HCPTerraformOrgName}:project:*:workspace:*:run_phase:*'
HCPTerraformRolePolicyAttachment:
Type: 'AWS::IAM::RolePolicy'
Properties:
PolicyName: 'AdministratorAccess'
RoleName: !Ref HCPTerraformRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: '*'
Resource: '*'
Outputs:
RoleArn:
Description: 'ARN of the created IAM Role'
Value: !GetAtt HCPTerraformRole.Arn
まずはStackSetで利用するCloudFormationテンプレートです。
HCP Terraform用のIAM RoleとOIDC Providerを作成します。
詳しくは以下のブログをご確認ください。
次は、Terraformコードです。
terraform {
required_version = ">= 1.11.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.82.2"
}
tfe = {
source = "hashicorp/tfe"
version = ">= 0.62.0"
}
}
}
provider "aws" {
region = var.aws_region
}
provider "tfe" {
organization = var.hcp_tf_organization_name
}
resource "aws_cloudformation_stack_set" "this" {
name = "hcp-tf-role"
permission_model = "SERVICE_MANAGED"
auto_deployment {
enabled = true
retain_stacks_on_account_removal = false
}
parameters = {
HCPTerraformOrgName = var.hcp_tf_organization_name
HCPTerraformIAMRoleName = var.hcp_tf_iam_role_name
}
template_body = file("${path.module}/files/cfn.yaml")
capabilities = ["CAPABILITY_NAMED_IAM"]
}
resource "aws_cloudformation_stack_set_instance" "this" {
stack_set_name = aws_cloudformation_stack_set.this.name
deployment_targets {
organizational_unit_ids = [var.aws_ou_id]
}
region = var.aws_region
}
data "aws_organizations_organization" "org" {}
data "aws_organizations_organizational_unit_child_accounts" "accounts" {
parent_id = var.aws_ou_id
}
locals {
accounts_map = { for account in data.aws_organizations_organizational_unit_child_accounts.accounts.accounts : account.id => account }
}
resource "tfe_variable_set" "this" {
for_each = local.accounts_map
name = "auth-aws-${each.key}"
description = "AWS credentials for ${each.value.name}"
}
resource "tfe_variable" "aws_provider_auth" {
for_each = tfe_variable_set.this
key = "TFC_AWS_PROVIDER_AUTH"
value = "true"
category = "env"
variable_set_id = each.value.id
}
resource "tfe_variable" "aws_run_role_arn" {
for_each = tfe_variable_set.this
key = "TFC_AWS_RUN_ROLE_ARN"
# StackインスタンスのOutputをStackSet実行アカウントでは取得できないため、IAMロールのARNを直接指定する
value = "arn:aws:iam::${each.key}:role/${var.hcp_tf_iam_role_name}"
category = "env"
variable_set_id = each.value.id
}
variable "aws_region" {
description = "The AWS region to deploy the IAM role to"
type = string
default = "ap-northeast-1"
}
variable "aws_ou_id" {
description = "The id of the OU to deploy the IAM role to"
type = string
}
variable "hcp_tf_organization_name" {
description = "The name of the HCP Terraform organization"
type = string
}
variable "hcp_tf_iam_role_name" {
description = "The name of the IAM role to create"
type = string
default = "hcp-tf-role"
}
Terraformコードでは、CloudFormation StackSetとVariable Setを作成します。
StackSetのデプロイ先はOUを指定しています。
OU内のAWSアカウントに先程のCloudFormationテンプレートで定義されたHCP Terraform用のIAM Role関連リソースが作成されます。
作成したIAM Role ARNを登録したHCP TerraformのVariable Setも同時に作成されます。
StackSetで作成したスタックのOutputをStackSet実行側のAWSアカウントで取ることができません。
そのため、ResourceからIAM Role ARNを渡す形ではなく、AWSアカウントIDとIAM Role名からARNを指定する形としています。
mkdir hcp-tf-iam-role-stack-set && cd "$_"
vi variables.tf
vi main.tf
touch files/cfn.yaml
git add .
git commit -m "HCP Terraform IAM Role and OIDC ProviderのCloudFormationスタックセットを追加"
git push origin HEAD
Workspaceの作成(Version Control Workflow)
Terraform実行用のWorkspaceを作成します。
HCP Terraformのコンソールから、Workspaces
-> New
-> Workspace
-> <任意のProject>
を選択します。
Version Control Workflow
-> <前の手順のTerraformコードをあげたリポジトリ>
選択します。
Workspace名
は任意の名前を設定してください。
私は以下としました。
sato-org-hcp-tf-iam-role
Terraformコードがリポジトリのルート直下にない場合は、Terraform Working Directory
から設定してください。
他はデフォルトのままでOKです。Create
を選択してWorkspaceを作成します。
Terraform variableの設定が求められるため、環境にあった値を設定し、Save variables
を選択します。
Variable setをWorkspaceにセットします。
作成したWorkspaceのVariables
を選択し、Apply variable set
から作成した以下2つのVariablesをセットします。
- StackSet管理アカウントアクセス用IAM Role(
auth-aws-<AWSアカウントID>
) - HCP Terraform Token(
auth-hcp-tf-<HCP Terraform Team名>
)
以下のようにセットされたらOKです。
TerraformでCloudFormation StackSetを作成する(メンバーアカウントIAMロール作成・Variable sets作成)
WorkspaceのRuns
-> New run
-> (Run nameは設定しなくてもOK) -> Start
を選択します。
Planが実行されます。内容を確認し、問題なければConfirm & apply
を選択して、Applyを実行します。
Applyが成功したらリソースの作成は完了です。
動作確認
CloudFormation StackSet管理AWSアカウントにログインして、リソースを確認してみます。
以下を確認できました。
- StackSetが作成されている
- 2つのAWSアカウントにスタックインスタンスが作成されている
HCP Terraformを確認してみます。
AWSアカウントに対応する2つのVariable setが作成されていることを確認できました。
最後にVariable setを使って、WorkspaceのPlan・Applyがうまくいくか確認してみます。
EC2インスタンスを作成するTerraformコードを実行しました。
Plan/Applyが成功し、メンバーアカウントにリソースを作成できました。
各アカウントのIAM RoleとVariable setが正常に設定できているようです。
補足: AWSアカウントが新規に作成された場合の対応
CloudFormation StackSetの自動デプロイは有効にしています。
AWSアカウントがOU内に移動してきたタイミングで、IAM Role関連のリソースがAWSアカウントにデプロイされます。
しかし、Variable setはTerraformで管理しているため自動的に作成されません。
Terraformをもう一度実行することで、Variable setが自動的に作成されます。
Variable set作成対象のAWSアカウントIDをOU IDからdata
リソースで取得する形にしているため、Terraformコード自体の変更は不要で対応できます。
おわりに
AWSアカウントにHCP Terraformからアクセスできない状態なので、最初のIAM Role作成は悩ましい問題だなと感じました。
今回はCloudFormation StackSetを使った方法を紹介しました。
最初だけCloudFormation StackSet実行アカウントでは手作業が必要になりますが、以降の運用はかなり楽になるかと思います。
最初の手作業部分もWorkspace実行モードlocalでやれば、Terraformでほぼ完結できそうですね。
AFT導入済み環境だったら、AFTでやるのが一番シンプルで良いかと思います。(これのためだけに、AFTを入れるのはオーバースペックな気もします)
以上、AWS事業本部の佐藤(@chari7311)でした。