はじめに

ご覧いただき、ありがとうございます。

AWS CDK で S3 クロスリージョンレプリケーションを設定する AWS CDK Advanced Workshop をやってみました。
設定自体はこのワークショップに沿って簡単にできましたが
その過程で気になったことを書き残したいと思います。

気になったところ

S3 バケット暗号化用 KMS キーを作成する必要がある

2023 年 1 月 5 日 から全ての S3 バケットの暗号化が必須になっています。
単にバケットを使用するだけであれば、デフォルトでキーの作成や設定は自動的にやってくれるため、特に問題はありません。
しかしながら、レプリケーションを設定する際は、レプリケーション先の S3 バケットで使用するキーを明示的に指定する必要があります。
そのため、予めレプリケーション先で新規にキーを作成しておいて、それを指定するようにします。

S3 コンソール、API、または AWS SDK を使用して、Amazon S3 バケットのデフォルト暗号化を設定します。

レプリケーション元からレプリケーション先の KMS キーを参照する方法

先述のとおり、レプリケーションを設定する際は、レプリケーション先の KMS キーを指定する必要があるため
レプリケーション元から KMS キーを参照できるようにする必要があります。
しかし、CloudFormation はネイティブでリージョン間の参照を行えません。
そこで、キーを SSM パラメータに格納し、SSM 経由で参照できるようにします。

export class MultiRegionS3CrrKmsCmkTarget extends Construct {
  // プロパティを定義する
  public readonly targetBucket: s3.Bucket;
  public readonly targetKeyIdSsmParameterName: string;

  constructor(scope: Construct, id: string) {

  (中略)

    // キーを SSM パラメータに格納する
    const stack = cdk.Stack.of(this);
    const parameterName = `${stack.stackName}.MyTargetKeyId`;

    new ssm.StringParameter(this, 'MyTargetKeyIdSSMParam', {
      parameterName: parameterName,
      description: 'The KMS Key Id for the target stack',
      stringValue: targetKmsKey.keyArn
    });

    // 値を設定する
    this.targetBucket = targetBucket;
    this.targetKeyIdSsmParameterName = parameterName;
  }
}

SSM に格納したターゲット KMS キーは、カスタムリソースを使用して取得します。
カスタムリソースは簡単に説明すると、CloudFormation で提供されていない処理を、ユーザ自身が作成できる仕組みになります。
異なるリージョンの SSM パラメータはインポートできないため、このような機能を利用する必要があります。
ちなみに、同じリージョンであれば、ssm.StringParameter.valueFromLookup() メソッドで対応できます。

const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
  // 今回はスタック作成、もしくは更新時に実行されてほしいため、`onUpdate` プロパティで設定しています。
  // (`onUpdate` は作成時も実行されます。)
  onUpdate: {
    service: 'SSM',
    action: 'getParameter',
    parameters: {
      Name: props.targetKeyIdSsmParameterName
    },
    region: props.targetRegion,
    physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
  },
  policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
});

この物理リソース ID(physicalResourceId プロパティ)に、現在時間(Date.now())を含めることで
デプロイされるたびに値は変化し、スタックが更新されることになります。

このような設定をしている理由は、CDK(CloudFormation)で作成されるリソースのうち、物理名が付与されているものについては
例えば、イミュータブルなプロパティの変更、つまり、リソースの置き換えが必要になった際に
物理名を変更するか、もしくはスタックを 1 度削除しなければならないからです。

すなわち、キーが変更された際も、自動的に新しいキーがインポートされるようになるということです。

最後の policy プロパティでは、カスタムリソースを実行するための権限を付与しています。
SSM パラメータの ARN を指定し、ポリシーを設定します。
なお、内部的には Lambda 関数及びそのための IAM ポリシーが作成され、実行されることになります。

終わりに

やはりクロスリージョン参照がやや複雑であると感じました。
一発で設定できる crossRegionReferences プロパティというものもありますが、本記事を投稿した時点では experimental であるようなので、ご注意ください。
それ以外の方法としては、サードパーティのパッケージを利用するという手もあります。

最後までご覧いただき、ありがとうございました。