Terraform による全リージョン展開
概要
委任管理者の設定 が完了した Audit アカウントに、Terraform で全リージョンに ORGANIZATION タイプの Analyzer をデプロイする。
本ブログの他の記事では、設定変更と動作確認を主眼に AWS CLI で検証を行っている。一方、IAM Access Analyzer の有効化は一時的な検証ではなく実際の運用で継続利用する設定であり、かつリージョナルサービスのため全リージョンへの展開が必要になる。このため、本記事では Terraform を使用する。
- S3 バックエンドの準備 — Terraform ステート管理用
- Terraform で全リージョンに ORGANIZATION タイプの Analyzer をデプロイ — IAM Access Analyzer はリージョナルサービスのため、全リージョンに展開が必要
- アーカイブルールで IAM ロールの重複検出を抑制(東京以外) — IAM ロールはグローバルリソースのため全リージョンで重複検出される。東京でのみ検出し、他リージョンではアーカイブする
- アーカイブルールで SSO ロールの検出を抑制(東京リージョン) — 上記により東京が IAM ロールの検出拠点となるが、IAM Identity Center が作成するロールは想定内の外部アクセスであるためノイズを除去する
検証環境
本記事のコマンドは、--profile 指定がない場合は Audit アカウントで実行する。
検証の流れ
flowchart LR
A[1. S3 バックエンド<br>準備] --> B[2. Terraform<br>デプロイ]
B --> C[3. デプロイ確認]
結果
- Terraform AWS Provider v6 の
region属性により、単一のプロバイダー設定で全 17 リージョンに Analyzer をデプロイできた。 - アーカイブルールにより、東京リージョン以外の IAM ロール検出が抑制されていることを確認した。
- 東京リージョンでは SSO ロール(
AWSReservedSSO_)の検出がアーカイブされることを確認した。
1. S3 バックエンドの準備
Terraform のステート管理用 S3 バケットを Audit アカウントに作成する。
BUCKET_NAME="<Audit アカウント ID>-tfstate-ap-northeast-1"
aws s3api create-bucket \
--bucket "${BUCKET_NAME}" \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1{
"Location": "http://<Audit アカウント ID>-tfstate-ap-northeast-1.s3.amazonaws.com/"
}バージョニングとパブリックアクセスブロックを設定する。
aws s3api put-bucket-versioning \
--bucket "${BUCKET_NAME}" \
--versioning-configuration Status=Enabled
aws s3api put-public-access-block \
--bucket "${BUCKET_NAME}" \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
(出力なし)2. Terraform デプロイ
ディレクトリ構成
IAMAccessAnalyzer/
├── modules/
│ ├── main.tf
│ └── variables.tf
├── prod/
│ ├── backend.tf
│ └── main.tf
└── scripts/
└── check_resources.shmodules/main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
resource "aws_accessanalyzer_analyzer" "org_analyzer" {
for_each = toset(var.regions)
region = each.value
analyzer_name = "org-access-analyzer"
type = "ORGANIZATION"
}
resource "aws_accessanalyzer_archive_rule" "sso_archive" {
for_each = { for r in var.regions : r => r if r == var.tokyo_region }
region = each.value
analyzer_name = aws_accessanalyzer_analyzer.org_analyzer[each.key].analyzer_name
rule_name = "ArchiveRule-Sso-Reserved-Roles"
filter {
criteria = "resource"
contains = ["AWSReservedSSO_"]
}
}
resource "aws_accessanalyzer_archive_rule" "iam_role_suppress" {
for_each = { for r in var.regions : r => r if r != var.tokyo_region }
region = each.value
analyzer_name = aws_accessanalyzer_analyzer.org_analyzer[each.key].analyzer_name
rule_name = "ArchiveRule-Iam-Role-Global-Suppress"
filter {
criteria = "resourceType"
eq = ["AWS::IAM::Role"]
}
}modules/variables.tf
variable "regions" {
description = "IAM Access Analyzer をデプロイするリージョンのリスト"
type = list(string)
validation {
condition = length(var.regions) > 0
error_message = "少なくとも 1 つのリージョンを指定してください。"
}
}
variable "tokyo_region" {
description = "東京リージョン特有の処理を切り替えるためのリージョン識別子"
type = string
default = "ap-northeast-1"
}prod/backend.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "<Audit アカウント ID>-tfstate-ap-northeast-1"
key = "iam-access-analyzer/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
provider "aws" {
region = "ap-northeast-1"
}prod/main.tf
module "iam_access_analyzer" {
source = "../modules"
regions = [
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
]
}scripts/check_resources.sh
各リージョンの Analyzer の状態とアーカイブルールを一覧表示する確認スクリプト。
#!/bin/bash
echo "Checking IAM Access Analyzer resources across regions..."
echo "--------------------------------------------------------"
printf "%-15s | %-18s | %-15s | %-30s\n" "Region" "Analyzer Name" "Status" "Archive Rules"
echo "--------------------------------------------------------"
REGIONS=$(aws ec2 describe-regions --region "${AWS_DEFAULT_REGION:-ap-northeast-1}" \
--query "Regions[].RegionName" --output text)
for region in ${REGIONS}; do
ANALYZERS_JSON=$(aws accessanalyzer list-analyzers --region "${region}" --output json 2>/dev/null)
AWS_STATUS=$?
if [ "${AWS_STATUS}" -ne 0 ]; then
printf "%-15s | %-18s | %-15s | %-30s\n" "${region}" "ERROR" "ERROR" "ERROR"
continue
fi
COUNT=$(echo "${ANALYZERS_JSON}" | jq '.analyzers | length' 2>/dev/null)
if [[ ! "${COUNT}" =~ ^[0-9]+$ ]]; then
printf "%-15s | %-18s | %-15s | %-30s\n" "${region}" "ERROR" "ERROR" "ERROR"
continue
fi
if [ "${COUNT}" -eq 0 ]; then
printf "%-15s | %-18s | %-15s | %-30s\n" "${region}" "None" "None" "None"
else
for i in $(seq 0 $((COUNT-1))); do
A_NAME=$(echo "${ANALYZERS_JSON}" | jq -r ".analyzers[$i].name")
A_STATUS=$(echo "${ANALYZERS_JSON}" | jq -r ".analyzers[$i].status")
RULES=$(aws accessanalyzer list-archive-rules \
--analyzer-name "${A_NAME}" --region "${region}" \
--query "archiveRules[].ruleName" --output text | tr '\t' ',' | sed 's/,/, /g')
[ -z "${RULES}" ] && RULES="Empty"
printf "%-15s | %-18s | %-15s | %-30s\n" "${region}" "${A_NAME}" "${A_STATUS}" "${RULES}"
done
fi
done
echo "--------------------------------------------------------"アーカイブルールの設計
IAM Access Analyzer を全リージョンにデプロイすると、IAM ロール(グローバルリソース)の検出結果が全リージョンで重複する。これは Security Hub 上でもノイズになるため、アーカイブルールで抑制する。
参考: IAM Access AnalyzerのIAM Roleをアーカイブするルールを複数リージョンで作成するスクリプト(DevelopersIO)
| ルール名 | 適用リージョン | 条件 | 目的 |
|---|---|---|---|
| ArchiveRule-Sso-Reserved-Roles | 東京のみ | リソース名に AWSReservedSSO_ を含む | IAM Identity Center(SSO)が作成するロールは想定内の外部アクセスであるため抑制 |
| ArchiveRule-Iam-Role-Global-Suppress | 東京以外 | リソースタイプが AWS::IAM::Role | IAM ロールはグローバルリソースのため、東京リージョンでのみ検出し、他リージョンでは重複を抑制 |
この設計により:
- IAM ロールの外部アクセスは東京リージョンでのみ検出・通知される
- SSO ロールの検出は東京リージョンでもアーカイブされ、ノイズにならない
- アーカイブされた検出結果は Security Hub からも自動的に消えるため、ダッシュボードがクリーンに保たれる
デプロイ実行
cd IAMAccessAnalyzer/prod
terraform initInitializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing modules...
- iam_access_analyzer in ../modules
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 6.0"...
- Installing hashicorp/aws v6.38.0...
- Installed hashicorp/aws v6.38.0 (signed by HashiCorp)
Terraform has been successfully initialized!terraform planPlan: 34 to add, 0 to change, 0 to destroy.17 リージョン分の Analyzer(17)+ 東京以外のアーカイブルール(16)+ 東京の SSO アーカイブルール(1)= 34 リソース。
terraform apply -auto-approvePlan: 34 to add, 0 to change, 0 to destroy.
...
Apply complete! Resources: 34 added, 0 changed, 0 destroyed.3. デプロイ確認
全リージョンで Analyzer が ACTIVE であることを確認する。
terraform apply 直後は CREATING 状態で、全メンバーアカウントのリソーススキャンが完了すると ACTIVE に遷移する。確認スクリプト(scripts/check_resources.sh)を実行する。
bash ../scripts/check_resources.shChecking IAM Access Analyzer resources across regions...
--------------------------------------------------------
Region | Analyzer Name | Status | Archive Rules
--------------------------------------------------------
ap-south-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
eu-north-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
eu-west-3 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
eu-west-2 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
eu-west-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
ap-northeast-3 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
ap-northeast-2 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
ap-northeast-1 | org-access-analyzer | ACTIVE | ArchiveRule-Sso-Reserved-Roles
ca-central-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
sa-east-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
ap-southeast-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
ap-southeast-2 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
eu-central-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
us-east-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
us-east-2 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
us-west-1 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
us-west-2 | org-access-analyzer | ACTIVE | ArchiveRule-Iam-Role-Global-Suppress
--------------------------------------------------------全 17 リージョンで ACTIVE であること、東京リージョンのみ ArchiveRule-Sso-Reserved-Roles、それ以外は ArchiveRule-Iam-Role-Global-Suppress が適用されていることを確認できた。
検出結果の確認
デプロイ直後に、Analyzer が検出した finding を確認する。
東京リージョンの ACTIVE な finding を確認する。
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}}' \
--query 'findings[].{resource:resource,resourceType:resourceType,status:status}' \
--region ap-northeast-1[
{
"resource": "arn:aws:iam::<Workload アカウント ID>:role/<GitHub Actions OIDC 用ロール名>",
"resourceType": "AWS::IAM::Role",
"status": "ACTIVE"
}
]IAM ロールの外部アクセスが 1 件検出された。これは GitHub Actions から OIDC で AssumeRole するためのロールであり、想定内の外部アクセスである。この finding は現時点ではアーカイブせず残しておく。今後の個別リソース検証で IAM ロールの finding の実例として活用し、検証完了後に必要に応じてアーカイブルールを追加する。
東京リージョンの ARCHIVED な finding を確認する。SSO ロールがアーカイブルールにより自動アーカイブされていることを確認する。
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-1:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ARCHIVED"]}}' \
--query 'findings[].{resource:resource,resourceType:resourceType,status:status}' \
--region ap-northeast-1[
{
"resource": "arn:aws:iam::<Audit アカウント ID>:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_AWSAdministratorAccess_<SSO サフィックス>",
"resourceType": "AWS::IAM::Role",
"status": "ARCHIVED"
},
...
]全メンバーアカウントの SSO ロール(AWSReservedSSO_)が ARCHIVED になっている。ArchiveRule-Sso-Reserved-Roles が機能していることを確認できた。
東京以外のリージョンでは、IAM ロールが丸ごとアーカイブされていることを確認する。
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-2:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}}' \
--query 'findings[].{resource:resource,resourceType:resourceType,status:status}' \
--region ap-northeast-2[]aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-northeast-2:<Audit アカウント ID>:analyzer/org-access-analyzer \
--filter '{"status": {"eq": ["ARCHIVED"]}}' \
--query 'findings[].{resource:resource,resourceType:resourceType,status:status}' \
--region ap-northeast-2[
{
"resource": "arn:aws:iam::<Audit アカウント ID>:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_AWSAdministratorAccess_<SSO サフィックス>",
"resourceType": "AWS::IAM::Role",
"status": "ARCHIVED"
},
...
]東京以外では ACTIVE な finding が 0 件で、IAM ロールはすべて ArchiveRule-Iam-Role-Global-Suppress によりアーカイブされている。東京リージョンで ACTIVE だった IAM ロール(外部アクセス可能なロール)も、東京以外ではアーカイブされている。