Terraformで複数AWSアカウントをまたぐ設計|provider aliasとassume_roleでマルチアカウント構成を実装する方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > Terraformで複数AWSアカウントをまたぐ設計|provider aliasとassume_roleでマルチアカウント構成を実装する方法
「開発環境と本番環境で別々のAWSアカウントを使っているが、Terraformのコードを2セット書いて管理するのは辛い」
「provider aliasは名前だけ知っているが、assume_roleと組み合わせてクロスアカウント操作をどう書けばいいのかわからない」

複数のAWSアカウントを扱う場合、Terraformのprovider aliasとassume_roleを組み合わせると、1つのコードベースで開発・ステージング・本番の各アカウントにまたがるリソースを同時管理できます。アカウントをまたぐ認証切り替えはTerraformが自動的に行ってくれます。

この記事では、provider aliasの基本設定からassume_roleによるクロスアカウント認証の仕組み、IAMクロスアカウントロールの準備手順、実際のリソース実装例、さらに「provider aliasで統合管理」と「ディレクトリ分割で分離管理」のどちらを選ぶべきかの判断基準まで解説します。

実行環境:Terraform 1.8.x(RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済み)

この記事のポイント

・provider aliasで複数AWSアカウントへのproviderを1つのコードで定義できる
・assume_roleブロックにrole_arnを指定するだけでクロスアカウント認証が切り替わる
・リソース定義でprovider = aws.devのように明示すると作成先アカウントを固定できる
・本番/開発で構成差が大きい場合はディレクトリ分割のほうが安全


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜAWSをマルチアカウントで分離するのか

AWSのベストプラクティスでは、用途ごとにAWSアカウントを分離することが強く推奨されています。1つのアカウントに開発・ステージング・本番を同居させると、次のリスクが生まれます。

・IAMロールのミスで開発者が本番のリソースを誤操作するリスク
・本番のコスト増加に開発のトラフィックが影響し、原因特定が難しくなる
・セキュリティインシデントが発生したとき、被害が全環境に広がる

マルチアカウント構成では、アカウントそのものがセキュリティ境界になります。開発アカウントで何が起きても、IAMポリシーの設定ミスが起きても、本番アカウントとは別のアカウントのため影響が遮断されます。

AWS Organizationsを使う組織では、管理アカウント(マスターアカウント)の下に開発・ステージング・本番の各メンバーアカウントを配置する構成が標準です。Terraformでこの構成を管理する場合、大きく2つのアプローチがあります。

provider aliasで統合管理:1つのTerraformコードで複数アカウントのリソースを同時定義する
ディレクトリ分割で分離管理:アカウントごとにTerraformのルートモジュールを分けてそれぞれ管理する

この2つの判断基準については後述します。まずprovider aliasの仕組みを理解しましょう。

provider aliasの仕組みと基本的な書き方

Terraformでは通常、1つのprovider(例:aws)を1つだけ定義します。しかしprovider aliasを使うと、同じproviderに複数の設定を持たせることができます。

同一アカウントの異なるリージョンにリソースを配置する場合も、provider aliasを使います。マルチアカウントではこれに加えてassume_roleを組み合わせます。

1. 基本的なprovider alias設定(同一アカウント・リージョン違い)

まず理解しやすいリージョン違いの例で仕組みを確認します。

# providers.tf - 同一アカウントの東京/大阪リージョンにprovider aliasを定義 terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } # デフォルトprovider(東京リージョン) provider "aws" { region = "ap-northeast-1" } # aliasを持つprovider(大阪リージョン) provider "aws" { alias = "osaka" region = "ap-northeast-3" }

aliasが設定されたproviderは、リソース定義でprovider = aws.osakaのように参照します。

# main.tf - リソースにprovider aliasを指定 resource "aws_s3_bucket" "dr_backup" { provider = aws.osaka # 大阪リージョンのproviderを明示 bucket = "my-app-dr-backup-osaka" tags = { Name = "DR用バックアップバケット" } }

2. assume_roleを使ったクロスアカウントprovider alias

別AWSアカウントへのアクセスには、IAMのSTS(Security Token Service)経由でロールを引き受けるassume_roleを使います。assume_roleブロックに切り替え先アカウントのIAMロールARNを指定するだけで、TerraformはAPI呼び出し時に自動でSTS:AssumeRoleを実行して認証を切り替えます。

# providers.tf - マルチアカウント設定 # 管理アカウント(実行元): 111122223333 # 開発アカウント: 123456789012 # 本番アカウント: 987654321098 # 実行元(管理アカウント)のデフォルトprovider provider "aws" { region = "ap-northeast-1" } # 開発アカウントへのprovider alias provider "aws" { alias = "dev" region = "ap-northeast-1" assume_role { role_arn = "arn:aws:iam::123456789012:role/TerraformCrossAccountRole" } } # 本番アカウントへのprovider alias provider "aws" { alias = "prod" region = "ap-northeast-1" assume_role { role_arn = "arn:aws:iam::987654321098:role/TerraformCrossAccountRole" } }

role_arnには「Terraformが引き受けるロール」のARNを指定します。このロールは切り替え先アカウント側(開発・本番)に事前に作成が必要です。

IAMクロスアカウントロールの準備(Terraform実行前の設定)

provider aliasのassume_roleが機能するためには、切り替え先アカウントにクロスアカウントロールを作成し、実行元アカウントからのassumeを許可する必要があります。

3. 切り替え先アカウントにIAMロールを作成する

開発・本番の各アカウントに「TerraformCrossAccountRole」を作成します。信頼ポリシー(Trust Policy)で、実行元アカウント(管理アカウント)からのSTS:AssumeRoleを許可します。

# AWSマネジメントコンソールまたはCLIで開発アカウント側に作成する信頼ポリシー # (管理アカウントのアカウントID: 111122223333 を許可) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" }, "Action": "sts:AssumeRole" } ] }

Principalに管理アカウントのroot ARNを指定すると、管理アカウントのすべてのIAMエンティティがこのロールを引き受けられる候補になります。実際にAssumeRoleできるのは、次のステップで「sts:AssumeRole権限」を付与したエンティティだけです。

このロールにはTerraformが操作するリソースに応じた権限ポリシーもアタッチします。開発環境でEC2・VPC・S3を管理する場合は、対象サービスへのフルアクセスか、PowerUserAccessポリシーを付与するのが一般的です。

4. 実行元アカウント側にsts:AssumeRoleを許可する

管理アカウント側では、Terraformを実行するIAMユーザー・ロールに、切り替え先アカウントのロールをAssumeRoleできる権限を付与します。

# 管理アカウントのIAMポリシー(TerraformExecutionRole または実行ユーザーにアタッチ) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam::123456789012:role/TerraformCrossAccountRole", "arn:aws:iam::987654321098:role/TerraformCrossAccountRole" ] } ] }

実践|複数アカウントにVPCとサブネットを構築する

IAMの準備が完了したら、実際にリソースを定義します。

5. provider設定とリソース定義

# main.tf - 開発・本番アカウントにVPCを作成する例 # 開発アカウントにVPCを作成(provider = aws.dev を明示) resource "aws_vpc" "dev" { provider = aws.dev cidr_block = "10.1.0.0/16" tags = { Name = "dev-vpc" Environment = "dev" } } resource "aws_subnet" "dev_public" { provider = aws.dev vpc_id = aws_vpc.dev.id cidr_block = "10.1.1.0/24" availability_zone = "ap-northeast-1a" tags = { Name = "dev-public-subnet" } } # 本番アカウントにVPCを作成(provider = aws.prod を明示) resource "aws_vpc" "prod" { provider = aws.prod cidr_block = "10.0.0.0/16" tags = { Name = "prod-vpc" Environment = "prod" } } resource "aws_subnet" "prod_public" { provider = aws.prod vpc_id = aws_vpc.prod.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-1a" tags = { Name = "prod-public-subnet" } }

6. terraform planで差分を確認する

# terraform init 後に terraform plan を実行 $ terraform plan Terraform will perform the following actions: # aws_subnet.dev_public will be created + resource "aws_subnet" "dev_public" { + arn = (known after apply) + availability_zone = "ap-northeast-1a" + cidr_block = "10.1.1.0/24" + id = (known after apply) + tags = { + "Name" = "dev-public-subnet" } + vpc_id = (known after apply) } # aws_vpc.dev will be created + resource "aws_vpc" "dev" { + arn = (known after apply) + cidr_block = "10.1.0.0/16" + id = (known after apply) + tags = { + "Environment" = "dev" + "Name" = "dev-vpc" } } # aws_subnet.prod_public will be created + resource "aws_subnet" "prod_public" { + arn = (known after apply) + availability_zone = "ap-northeast-1a" + cidr_block = "10.0.1.0/24" + id = (known after apply) + tags = { + "Name" = "prod-public-subnet" } + vpc_id = (known after apply) } # aws_vpc.prod will be created + resource "aws_vpc" "prod" { + arn = (known after apply) + cidr_block = "10.0.0.0/16" + id = (known after apply) + tags = { + "Environment" = "prod" + "Name" = "prod-vpc" } } Plan: 4 to add, 0 to change, 0 to destroy.

planの出力で開発アカウント(aws.dev)と本番アカウント(aws.prod)それぞれのリソースが列挙されます。実行元の管理アカウントはSTSを経由してそれぞれのアカウントにAPIコールを発行するため、planの段階でIAMの設定が正しいかどうかも確認できます。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Terraformのマルチアカウント設計からIaCを使ったインフラ自動化まで、ハンズオン形式で体系的に学べるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

provider alias vs ディレクトリ分割|どちらを選ぶか

マルチアカウント管理の手法は「provider aliasで1つのコードに統合する」か「アカウントごとにディレクトリを分割して独立管理する」かの2択です。それぞれの特徴を比較します。
比較軸 provider alias統合管理 ディレクトリ分割管理
コードの一元化 1か所で全アカウントを定義 アカウントごとに別ディレクトリ
terraform apply範囲 全アカウントが一度に変更対象 ディレクトリごとに独立実行
誤操作リスク 本番provider aliasのミスで本番に意図しない変更 ディレクトリを間違えない限り影響範囲が限定
共通リソースの管理 1つのコードで参照でき整合性が取りやすい remote_stateで参照する設計が必要
向いているケース アカウント間で構成差が小さく共有リソースが多い 本番だけ構成が大きく異なり独立性を重視したい
現場での使い分けの目安:

dev・stg・prodでVPCやサブネットのCIDR設計は同じで、インスタンスサイズだけ違う場合はprovider alias + variableによる統合管理が効率的です。一方、本番環境だけWAFやShieldを導入していて構成が根本的に違う場合、本番リソースを別ディレクトリで分離したほうがterraform applyの影響範囲が明確になります。

「本番への誤操作リスクを最小化したい」という方針の現場では、ディレクトリ分割が選ばれることが多いです。provider aliasは利便性が高い反面、provider = aws.prodの書き忘れが本番意図しない変更につながるリスクがあります。

トラブルシュート|よくあるエラーと対処

「No valid credential sources found」が出た時の対処

Error: No valid credential sources found with provider["registry.terraform.io/hashicorp/aws"].dev, on providers.tf line 14, in provider "aws": 14: assume_role {

原因は実行環境に管理アカウントのAWS認証情報が設定されていないことです。Terraformはまず実行元アカウントの認証情報でSTSにAssumeRoleをリクエストします。この認証情報がないと処理が始まりません。

ローカル実行の場合:aws configureで管理アカウントの認証情報を設定するか、AWS_PROFILE環境変数でプロファイルを指定する
CI/CD実行の場合(GitHub Actions等):実行ランナーにIAMロールをOIDCで割り当てる(アクセスキー不要)か、Secretsにアクセスキーを設定する

「AccessDenied: is not authorized to assume role」が出た時の対処

Error: AuthFailure: AWS was not able to validate the provided access credentials Error: user: arn:aws:iam::111122223333:user/terraform-ci is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/TerraformCrossAccountRole

2か所の設定を確認します。

実行元アカウント側:実行IAMユーザーまたはロールにsts:AssumeRoleの権限が付与されているか確認する
切り替え先アカウント側:TerraformCrossAccountRoleの信頼ポリシーに、実行元アカウントのプリンシパルが正しく記載されているか確認する

信頼ポリシーのPrincipalにarn:aws:iam::111122223333:rootを指定した場合でも、実行元IAMエンティティにsts:AssumeRole権限がなければ失敗します。「許可する側(信頼ポリシー)」と「実行できる側(IAMポリシー)」の両方が必要です。

provider alias指定漏れによる意図しないアカウントへの作成

【要注意】リソース定義でprovider引数を省略すると、デフォルトprovider(実行元アカウント)に作成されます。開発アカウントに作るつもりでprovider = aws.devを書き忘れると、管理アカウントにリソースが作成されてしまいます。この誤操作は気づかないまま進みやすいため注意が必要です。

これを防ぐには次の手順が有効です。

デフォルトproviderに実運用リソースを置かない:管理アカウントにはTerraformのstateバックエンド(S3+DynamoDB)だけを置き、実リソースは全てaliasを持つproviderで管理する設計にする
terraform planの結果を必ず確認する:planの出力にある「provider」の行を確認し、意図したアカウントへの変更になっているか確認してからapplyを実行する

本記事のまとめ

やりたいこと 設定・操作
別アカウント用のproviderを定義する provider "aws" { alias = "dev" }
クロスアカウント認証を設定する assume_role { role_arn = "arn:aws:iam::..." }
リソースを特定アカウントに作成する resource "aws_vpc" "dev" { provider = aws.dev }
実行元IAMに権限を付与する sts:AssumeRoleを含むIAMポリシーをアタッチ
切り替え先に信頼ポリシーを設定する 実行元アカウントIDをPrincipalに記載
provider aliasかディレクトリ分割か判断する 構成差が小さい→alias統合、本番を独立させたい→ディレクトリ分割
provider aliasとassume_roleの組み合わせは、マルチアカウント構成を1つのTerraformコードで扱える強力な手法です。一方で、全アカウントが同一のapplyスコープに入るため「本番誤操作リスク」を念頭に置いた設計が重要です。実際の現場では、小規模チームはprovider aliasで統合し、本番リソースへのapplyはCI/CDの承認ゲートで制御するパターンがバランスよく使われています。
TerraformのIaC設計を実機で体系的に学びたい方へ。マルチアカウント構成・モジュール設計・CI/CDパイプラインなど、現場で即通用するスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。