📝

KMS で暗号化された S3 オブジェクトのクロスアカウントコピーを Lambda (Python) でやってみた

2022/04/23に公開

今回は、以下の AWS 公式ドキュメント [1] [2] を参考に、KMS で暗号化された S3 オブジェクトのクロスアカウントコピーを Lambda (Python) でやってみました。

[1] 別の AWS アカウントから Amazon S3 オブジェクトをコピーする
[2] AWS KMS の暗号化を使用して S3 バケットへのアクセスをユーザーに許可する

1. ソースアカウントで S3 バケットと KMS キーを作成

まずはソースアカウントの S3 バケットと、暗号化に使用する KMS キーを作成します。
S3 コンソールからバケットを作成します。
作成時にデフォルトの暗号化の項目で、SSE-KMS を選択し、「キーの作成」をクリックします。

キー管理者は任意の IAM ユーザーやロールを選択してください。
その他の設定はデフォルトのまま作成します。

作成したキーのキーポリシーは以下のようになります。

{
    "Id": "key-consolepolicy-3",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<Account ID>:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<Account ID>:role/OrganizationAccountAccessRole"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        }
    ]
}

キー作成後、S3 のバケット作成画面に戻り、作成したキーを選択してバケットを作成します。

S3 バケット作成後、適当なファイルをアップロードしておきます。
今回は test.txt というファイルをアップロードしておきました。

2. ターゲットアカウントで S3 バケットと KMS キーを作成

1 と同様の手順をターゲットアカウントでも行います。
キーポリシーは後で修正するので、この時点では 1 と同様のキーポリシーで問題ありません。

こちらはコピー先の S3 バケットなので、ファイルのアップロードは不要です。
なお、この後の手順で KMS キーの ARN が必要なのでコピーしておいてください。

3. ソースアカウントで IAM ポリシーと IAM ロールを作成

ソースアカウントの Lambda 関数にアタッチする IAM ロールと、IAM ロールにアタッチする IAM ポリシーを作成します。

S3 に関する IAM ポリシーは、AWS 公式ドキュメント [1] に記載されている以下の JSON を使用します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::source-DOC-EXAMPLE-BUCKET",
                "arn:aws:s3:::source-DOC-EXAMPLE-BUCKET/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::destination-DOC-EXAMPLE-BUCKET",
                "arn:aws:s3:::destination-DOC-EXAMPLE-BUCKET/*"
            ]
        }
    ]
}

source-DOC-EXAMPLE-BUCKET はソースアカウントの S3 バケット名、destination-DOC-EXAMPLE-BUCKET はターゲットアカウントの S3 バケット名に置き換えます。

KMS に関する IAM ポリシーは AWS 公式ドキュメント [2] を参考に、以下の JSON を使用します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": [
                "arn:aws:kms:example-region-1:123456789098:key/111aa2bb-333c-4d44-5555-a111bb2c33dd",
                "arn:aws:kms:example-region-1:123456789098:key/111aa2bb-333c-4d44-5555-a111bb2c33dd2"
            ]
        }
    ]
}

Resource 内の KMS キーの ARN を、ソースアカウントとターゲットアカウントで作成した KMS キーの ARN に置き換えます。

IAM ポリシーを作成後に Lambda 関数にアタッチする IAM ロールを作成します。
作成した 2 つの IAM ポリシーと、AWSLambdaBasicExecutionRole をアタッチします。
AWSLambdaBasicExecutionRole はなくても構いませんが、CloudWatch Logs にログが出力されなくなりますので、必要に応じて付け外ししてください。

なお、この後の手順で、作成した IAM ロールの ARN が必要なのでコピーしておいてください。

4. ターゲットアカウントの S3 バケットポリシーを編集

2 で作成したターゲットアカウントの S3 バケットは、ソースアカウントとは別アカウントなので、バケットポリシーでアクセスを許可する必要があります。
そのため、ターゲットアカウントの S3 バケットポリシーに以下の JSON を記述します。

{
    "Version": "2012-10-17",
    "Id": "Policy1650435733472",
    "Statement": [
        {
            "Sid": "Stmt1650435732453",
            "Effect": "Allow",
            "Principal": {
                "AWS": "<Lambda IAM role ARN>"
            },
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::destination-DOC-EXAMPLE-BUCKET",
                "arn:aws:s3:::destination-DOC-EXAMPLE-BUCKET/*"
            ]
        }
    ]
}

<Lambda IAM role ARN> を 3 で作成した Lambda 用の IAM ロール ARN に置き換えてください。
また、バケット名もターゲットアカウントの S3 バケット名に置き換えてください。

5. ソースアカウントで Lambda 関数を作成

ソースアカウントで Lambda 関数を作成します。
今回は Python 3.9 を選択しました。
IAM ロールは 3 で作成した IAM ロールを選択してください。

作成した Lambda 関数のコードソースに以下のコードを貼り付けます。

import boto3
s3 = boto3.client('s3')

def lambda_handler(event, context):
    s3.copy_object(Bucket="target-bucket-name", Key="object-key-name", CopySource={'Bucket': "source-bucket-name", 'Key': "object-key-name"})

コードの内容はシンプルに Boto3 のcopy_objectを実行するだけです。

S3 バケット名とオブジェクト名のみ置き換えます。
なお、Key に指定するのは KMS キーではなく、コピーしたいS3 のオブジェクト名なので注意してください。
例えば、test.txt というオブジェクトをコピーしたい場合は以下のように置き換えます。

s3.copy_object(Bucket="target-bucket-name", Key="test.txt", CopySource={'Bucket': "source-bucket-name", 'Key': "test.txt"})

Lambda 関数はこれで完成ですが、この時点でテスト実行しても以下のエラーが発生します。

An error occurred (AccessDenied) when calling the CopyObject operation: Access Denied

現状ではターゲットアカウントの KMS キーのキーポリシーで、Lambda 関数にアタッチした IAM ロールからのアクセスを許可していないために発生しているエラーです。

ちなみに、今回とは別のパターンで、別アカウントの S3 バケットをソース、Lambda 関数を実行するアカウントの S3 バケットをターゲットとして場合には、この手順の時点で以下のエラーが発生しました。

An error occurred (AccessDenied) when calling the CopyObject operation: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access.

どちらのパターンでもこの後の手順で行っている、別アカウントの KMS キーへのアクセス権限の編集が必要です。

6. ターゲットアカウントの KMS キーポリシーを編集する

先ほどのエラーを解消するために、ターゲットアカウントの KMS キーのキーポリシーを編集します。
キーポリシーを編集する際、ソースアカウントの Lambda 関数にアタッチした IAM ロールの ARN が必要なので、IAM ロールの画面からコピーしておいてください。

ターゲットアカウントの KMS コンソールに移動し、S3 バケット作成時に一緒に作成した KMS キーを選択します。
キーポリシーの部分に「ポリシービューへの切り替え」というボタンがあるので、クリックします。

JSON 形式のキーポリシーのビューに変わるので、「編集」をクリックします。

JSON 部分に以下のように書き換えます。

{
    "Id": "key-consolepolicy-3",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<target Account ID>:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<target Account ID>:role/<role name>"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ExampleStmt",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<source Account ID>:role/<Lambda role name>"
            },
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "*"
        }
    ]
}

もともとのキーポリシーに追記したのは、以下の部分です。

{
    "Sid": "ExampleStmt",
    "Effect": "Allow",
    "Principal": {
	"AWS": "arn:aws:iam::<source Account ID>:role/<Lambda role name>"
    },
    "Action": [
	"kms:Decrypt",
	"kms:GenerateDataKey"
    ],
    "Resource": "*"
}

上記の Principal の部分を、ソースアカウントでコピーした IAM ロールの ARN に置き換えてください。

追記後、「変更の保存」をクリックします。

7. 動作確認

再度ソースアカウントの Lambda 関数をテスト実行すれば、先ほどのエラーが発生せずに、S3 オブジェクトのコピーが実行されます。

うまくいかない場合は以下の点を確認してください。
・IAM ポリシーが正しいか
・IAM ポリシーを IAM ロールにアタッチしたか
・Lambda にアタッチした IAM ロールが正しいか
・ターゲットアカウントの S3 バケットポリシーが正しいか
・ターゲットアカウントの KMS キーポリシーが正しいか
・Lambda ソースコード内のバケット名やキー名が正しいか

まとめ

今回は AWS 公式ドキュメント 2 つの内容を組み合わせて、KMS で暗号化された S3 オブジェクトのクロスアカウントコピーを Lambda (Python) でやってみました。
KMS の暗号化を使用した場合のクロスアカウントコピーでは別アカウントの S3 バケットポリシーを編集する必要がある点に加えて、別アカウントの KMS キーポリシーも編集が必要でした。

クロスアカウントでの権限周りは IAM 以外も絡んでくるので、リソースベースのポリシーがサポートされているサービスでは要注意だと感じました。

今回の内容がどなたかの参考になれば幸いです。

参考資料

[1] 別の AWS アカウントから Amazon S3 オブジェクトをコピーする
[2] AWS KMS の暗号化を使用して S3 バケットへのアクセスをユーザーに許可する

Discussion