【決定版】KMS暗号化付きS3クロスアカウントレプリケーションをCloudFormationで完全自動化する
AWSでマルチアカウント運用をしていると必ず遭遇するのが、「AWS1(送信元アカウント)のS3バケットに入ってきたファイルを、AWS2(送信先アカウント)へ自動的にレプリケーション(コピー)したい」という要件です。
これが「デフォルト暗号化(SSE-S3)」なら簡単なのですが、「KMS(CMK)によるカスタマー管理鍵での暗号化」が絡んだ瞬間、難易度が跳ね上がります。 権限周りが複雑になり、レプリケーションステータスが FAILED になって進まない……という罠が随所に仕掛けられているからです。
この記事では、インフラをすべてCloudFormation(Cfn)でコード管理(IaC)しながら、暗号化の壁を越えるクロスアカウントレプリケーションを1発で成功させる手順を、動くコード付きで完全解説します!
記事の目次
全体構成と前提条件
今回のアーキテクチャのポイントは以下の4つです。
- 双方向のバージョニング有効化(レプリケーションの必須条件)
- 送信元(AWS1)のレプリケーション用IAMロール作成
- 送信先(AWS2)のKMSキーポリシー(Conditionによるロール縛り)
- 送信先(AWS2)のS3バケットポリシー(他アカウントからの受け入れ許可)
これらを、CloudShellから環境変数(.env)を使ってスマートにデプロイしていきます。
【フェーズ1】AWS2(送信先)での受け入れ準備
まずは、データを受け取る側の AWS2 から作業を開始します。
1. KMSキーの作成(AWS2)
クロスアカウントで最大の障壁となるのが、KMSのキーポリシーです。まだ実在しないAWS1のロール名をキーポリシーに直書きすると、CloudFormationが「そのIAMロールは存在しない」とエラーを吐いて止まってしまいます。
そこで、「アカウント全体を信頼しつつ、Condition(条件句)で特定のロール名に縛る」というテクニックを使います。
まずは環境変数ファイル kms-aws2.env を作成します。
作業環境はAWS CloudShell を利用します。
cat << 'EOF' > kms-aws2.env
AccountName=aws2
Aws1AccountId=123456789012 # あなたのAWS1の12桁アカウントIDに書き換えてください
Aws1ReplicationRoleName=aws1-s3-replication-role # 将来名前を変えたくなったらここを修正
EOF続いて、KMS用のテンプレート kms.yaml を作成します。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'KMS Key and Dedicated KMS IAM Role (Dynamic Config)'
Parameters:
AccountName:
Type: String
Description: 'Prefix for resource names (e.g., aws1)'
Aws1AccountId:
Type: String
Description: 'AWS1 12-digit Account ID'
Aws1ReplicationRoleName:
Type: String
Description: 'IAM Role Name for replication in AWS1'
Resources:
# ------------------------------------------------------------
# 1. KMSキー本体とエイリアスの作成
# ------------------------------------------------------------
S3EncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: !Sub "${AccountName}-s3-key"
Enabled: true
KeyPolicy:
Version: '2012-10-17'
Id: !Sub "${AccountName}-s3-key-policy"
Statement:
# ルートアカウントに権限を委譲
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: "kms:*"
Resource: "*"
# ------------------------------------------------------------
# ★Conditionを使って、ロールがまだなくても通るようにする
# ------------------------------------------------------------
- Sid: Allow AWS1 Replication Role to Encrypt
Effect: Allow
Principal:
# 一旦「AWS1アカウント全体」を信頼(これなら実在するのでCfnが通る)
AWS: !Sub "arn:aws:iam::${Aws1AccountId}:root"
Action:
- 'kms:Encrypt'
- 'kms:GenerateDataKey'
Resource: "*"
# ★ここで「でも、実際に鍵を使えるのはこのロール名だけ!」と縛りをかける
Condition:
ArnEquals:
aws:PrincipalArn: !Sub "arn:aws:iam::${Aws1AccountId}:role/${Aws1ReplicationRoleName}"
S3EncryptionKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${AccountName}-s3-key"
TargetKeyId: !Ref S3EncryptionKey
# ------------------------------------------------------------
# 2. KMS専用のIAMロール作成
# ------------------------------------------------------------
KMSDedicatedRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AccountName}-KMSDedicatedRole"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action:
- sts:AssumeRole
Policies:
- PolicyName: !Sub "${AccountName}-KMSAccessPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey
- kms:DescribeKey
Resource:
- !GetAtt S3EncryptionKey.Arn環境変数を読み込んで、デプロイ(またはアップデート)を実行します。
source kms-aws2.env
aws cloudformation update-stack \
--template-body file://kms.yaml \
--stack-name "${AccountName}-kms-stack" \
--parameters \
ParameterKey=AccountName,ParameterValue=$AccountName \
ParameterKey=Aws1AccountId,ParameterValue=$Aws1AccountId \
ParameterKey=Aws1ReplicationRoleName,ParameterValue=$Aws1ReplicationRoleName \
--capabilities CAPABILITY_NAMED_IAM※マネジメントコンソール等で、KMSのキーポリシーに上記の設定が正しく追加されていることを確認してください。
2. S3バケット作成 & バケットポリシー設定(AWS2)
次に、AWS2側にデータを受け取るバケットを作ります。レプリケーションの必須条件である「バージョニングの有効化」と、AWS1からの書き込みを許可する「バケットポリシー」を同時に流し込みます。
まず、AWS2用の環境変数ファイル s3-aws2.env を作成します。
cat << 'EOF' > s3-aws2.env
AccountName=aws2
BucketName=aws2-bucket-om-xxxx-1234-5678-9012
Aws1AccountId=123456789012
Aws1ReplicationRoleName=aws1-s3-replication-role
EOF続いて、以下の s3.yaml を作成します。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'S3 Bucket with KMS Encryption and Bucket Policy (Test)'
Parameters:
AccountName:
Type: String
BucketName:
Type: String
Aws1AccountId:
Type: String
Description: 'AWS1 12-digit Account ID'
Aws1ReplicationRoleName:
Type: String
Description: 'IAM Role Name for replication in AWS1'
Resources:
# S3バケットの作成
TestS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
# レプリケーションの必須条件「バージョニング」を有効化!
VersioningConfiguration:
Status: Enabled
# デフォルトの暗号化設定(KMS連動)
BucketEncryption:
ServerSideEncryptionConfiguration:
- BucketKeyEnabled: true
ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !Sub "alias/${AccountName}-s3-key"
# ------------------------------------------------------------
# AWS1からのレプリケーションを受け入れるバケットポリシー
# ------------------------------------------------------------
TestS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref TestS3Bucket # 上のバケットにこのポリシーを紐付け
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowAWS1Replication
Effect: Allow
Principal:
# AWS1のレプリケーションロールをピンポイントで信頼!
AWS: !Sub "arn:aws:iam::${Aws1AccountId}:role/${Aws1ReplicationRoleName}"
Action:
- 's3:ReplicateObject'
- 's3:ReplicateDelete'
- 's3:ReplicateTags'
Resource: !Sub "arn:aws:s3:::${BucketName}/*" # バケットの中身すべてへの書き込みを許可デプロイを実行します。
# 1. AWS2用の環境変数を読み込み
source s3-aws2.env
# 2. S3スタックをアップデート!
aws cloudformation update-stack \
--template-body file://s3.yaml \
--stack-name "${AccountName}-s3-stack" \
--parameters \
ParameterKey=AccountName,ParameterValue=$AccountName \
ParameterKey=BucketName,ParameterValue=$BucketName \
ParameterKey=Aws1AccountId,ParameterValue=$Aws1AccountId \
ParameterKey=Aws1ReplicationRoleName,ParameterValue=$Aws1ReplicationRoleNameデプロイ完了後、S3のバージョニングがしっかり「有効」になっていることを確認してください。
【フェーズ2】AWS1(送信元)の設定と連携
ここからアカウントを AWS1(送信元) に切り替えます。
1. 環境変数ファイルの準備(AWS1)
AWS2の情報をAWS1側に教えるため、環境変数ファイル(s3-aws1.env)にAWS2のバケット名やKMSの「本物のキーARN」を追記します。
cat << 'EOF' > s3-aws1.env
AccountName=aws1
BucketName=aws1-bucket-om-xxxx-1234-5678-9012
Aws2AccountId=987654321098 # AWS2の12桁アカウントID
Aws2BucketName=aws2-bucket-om-xxxx-1234-5678-9012 # AWS2のバケット名
Aws2KmsKeyArn=arn:aws:kms:ap-northeast-1:987654321098:key/yyyy-yyyy-yyyy-yyyy # ★AWS2で作った本物のKMSキーARN
EOF2. レプリケーション設定付きS3バケットの作成(AWS1)
AWS1側の s3.yaml を以下のように修正します。 ここでは、「レプリケーション用IAMロールの作成」と、KMS暗号化オブジェクトをコピーするための「ReplicationConfiguration」をバケットに組み込みます。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'S3 Bucket with KMS Encryption and Replication (Test)'
Parameters:
AccountName:
Type: String
BucketName:
Type: String
Aws2AccountId:
Type: String
Aws2BucketName:
Type: String
Aws2KmsKeyArn:
Type: String
Resources:
# ------------------------------------------------------------
# 1. レプリケーション用のIAMロール
# ------------------------------------------------------------
S3ReplicationRole:
Type: AWS::IAM::Role
Properties:
RoleName: aws1-s3-replication-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: s3.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: S3ReplicationPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# 送信元(AWS1)バケットの読み込み権限
- Effect: Allow
Action:
- s3:GetReplicationConfiguration
- s3:ListBucket
Resource: !Sub "arn:aws:s3:::${BucketName}"
- Effect: Allow
Action:
- s3:GetObjectVersionForReplication
- s3:GetObjectVersionAcl
- s3:GetObjectVersionTagging
Resource: !Sub "arn:aws:s3:::${BucketName}/*"
# 送信先(AWS2)バケットへの書き込み権限
- Effect: Allow
Action:
- s3:ReplicateObject
- s3:ReplicateDelete
- s3:ReplicateTags
Resource: !Sub "arn:aws:s3:::${Aws2BucketName}/*"
# ★スマート化:AWS1のKMS ARNを入力させず、自身の環境情報から自動組み立て!
- Effect: Allow
Action:
- kms:Decrypt
Resource: !Sub "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*"
# AWS2の鍵(相手)に対する暗号化権限
- Effect: Allow
Action:
- kms:Encrypt
- kms:GenerateDataKey
Resource: !Ref Aws2KmsKeyArn
# ------------------------------------------------------------
# 2. S3バケットの作成とレプリケーションルールの統合
# ------------------------------------------------------------
TestS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
# レプリケーションの必須条件「バージョニング」を有効化
VersioningConfiguration:
Status: Enabled
# デフォルトの暗号化設定(KMS連動)
BucketEncryption:
ServerSideEncryptionConfiguration:
- BucketKeyEnabled: true
ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !Sub "alias/${AccountName}-s3-key"
# 自動コピールールの設定
ReplicationConfiguration:
Role: !GetAtt S3ReplicationRole.Arn
Rules:
- Id: CrossAccountReplicationRule
Status: Enabled
Priority: 1
DeleteMarkerReplication:
Status: Enabled
Filter:
Prefix: ""
Destination:
Bucket: !Sub "arn:aws:s3:::${Aws2BucketName}"
Account: !Ref Aws2AccountId
# KMS暗号化されたオブジェクトをレプリケーション対象にする設定
SourceSelectionCriteria:
SseKmsEncryptedObjects:
Status: Enabled
EncryptionConfiguration:
ReplicaKmsKeyID: !Ref Aws2KmsKeyArn環境変数を読み込み、AWS1側でデプロイを実行します。
source s3-aws1.env
aws cloudformation update-stack \
--template-body file://s3.yaml \
--stack-name "${AccountName}-s3-stack" \
--parameters \
ParameterKey=AccountName,ParameterValue=$AccountName \
ParameterKey=BucketName,ParameterValue=$BucketName \
ParameterKey=Aws2AccountId,ParameterValue=$Aws2AccountId \
ParameterKey=Aws2BucketName,ParameterValue=$Aws2BucketName \
ParameterKey=Aws2KmsKeyArn,ParameterValue=$Aws2KmsKeyArn \
--capabilities CAPABILITY_NAMED_IAM【疎通テスト】
すべてのデプロイが完了したら、AWS1のバケットに新しくテストファイル(例: test5.txt)をアップロードしてみましょう。 ※レプリケーションは、設定後に新しく置かれたオブジェクトのみが対象になります。
アップロード後、AWS1のCLIから以下の確認コマンドを実行します。
aws s3api head-object --bucket $BucketName --key test5.txt --query ReplicationStatus --output text最初は PENDING(処理中)と表示されることがありますが、数秒〜数十秒待って再度実行し、画面に以下の文字が返ってくれば大成功です!
AWS2(送信先)のバケットを確認すると、AWS1のKMSで暗号化されていたファイルが、AWS2のKMSキーで綺麗に再暗号化されて格納されているはずです。
まとめ
KMS暗号化が絡むクロスアカウントレプリケーションをスムーズに成功させる鍵は、「AWS2(送信先)の受け入れ態勢(KMSキーポリシーのCondition縛り・バケットポリシー)を先に完璧に作ること」です。
この手順通りに進めれば、循環参照のようなエラーや FAILED 地獄に悩されることなく、綺麗にCloudFormationでの自動化が完了します。マルチアカウント運用の自動化・バックアップ体制の構築にぜひ役立ててください!