15
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

今年もアドベントカレンダーの季節がやってきました!
今年の記事は、最近AWS CDKから離れているので、久しぶりにCDK関連の話を書いてみようと思います。
ちょうど、先月AWS CDKの新機能(CDK Mixins)がプレビュー版としてリリースされたので、CDK Mixinsについて紹介します。

AWS CDK(Cloud Development Kit)

AWS CDKは、TypeScriptやPythonなどのプログラミング言語でAWSリソースを定義できるIaC(Infrastructure as Code)のフレームワークです。従来のCloudFormationテンプレートはYAMLやJSONで記述されていましたが、CDKを使うとより抽象化されたコードでインフラを定義できます。

詳細は公式ドキュメントをご覧ください。

L1/L2/L3 コンストラクト

CDK Mixinsの話に入る前に、CDKのコンストラクトについて簡単に説明します。
抽象化されたコードでAWSリソースを定義するために、CDKは「コンストラクト」という概念を導入しています。コンストラクトは、L1、L2、L3の3つの抽象度レベルに分類されます。

  • L1コンストラクト
    • AWS CloudFormationの各リソース(AWS::S3::Bucket, AWS::EC2::Instanceなど)のJSON/YAML定義に1対1で対応
  • L2コンストラクト
    • 特定のAWSリソース(VPC, S3 Bucket, DynamoDB Tableなど)のL1コンストラクトをラップしてフォルト値やベストプラクティスを適用
  • L3コンストラクト
    • 複数のAWSリソース(L2コンストラクトなど)を組み合わせて、特定のアプリケーションパターンを構成(LambdaとAPI Gatewayなど)

詳細については、以下の公式ドキュメントを参照してください。

CDK Mixinsとは?

やっと本題のCDK Mixinsについて説明します。
CDK Mixinsは、一言で言うとL1コンストラクトと L2コンストラクトの隔たりを埋め、必要な抽象化だけを柔軟に組み込める機能といったものになります。
ただこれだと少し抽象的なので、具体的に何ができるのかを説明します。
公式によると以下のような利点があるそうです。

  • L1、L2、カスタムコンストラクトのどれにも同じ抽象化を適用
  • 特定の実装に縛られず、機能を組み合わせて使用可能
  • 異なるAWSサービス間で共通のパターン(例:暗号化)を利用
  • 抽象化を維持しつつ、安全で型付けされた方法でリソースをカスタマイズ可能

今回は、異なるAWSサービス間で共通のパターンに焦点を当て、クロスサービスで暗号化を統一的に適用する例L2 未実装の新機能へのアクセスする方法について紹介します!

CDK Mixinsは、2025年11月にAWS CDK v2.229.0でプレビュー版として導入されました。
この記事の内容はプレビュー版に基づいており、将来のリリースで仕様変更される可能性があります。
最新情報は公式ドキュメントやGitHubリポジトリを参照してください。

https://github.com/aws/aws-cdk/releases/tag/v2.229.0

CDK Mixinsのセットアップ

まずは、CDK Mixinsを使うためのセットアップ方法を説明します。
以下のコマンドによりCDK Mixinsパッケージをインストールします。

npm install --save-dev aws-cdk-lib @aws-cdk/mixins-preview

CDK Mixinsは、@aws-cdk/mixins-preview パッケージで提供されており、Mixins クラスを使ってスタック内の複数リソースに対して設定を適用できます。CDK Mixinsは型安全であり、L1コンストラクトの具体的な型に基づいて設定を適用できるため、IDEの補完やコンパイル時エラー検出などの利点があります。

クロスサービスで暗号化を統一的に適用する例

これまで、異なるAWSサービスに共通のセキュリティポリシーを適用する場合、サービスごとに異なる構文を書く必要がありました。
例えば、S3、DynamoDB、SNS、SQS に暗号化を設定したい場合、各サービスでそれぞれの暗号化設定に応じたプロパティの設定を書く必要がありました。 

// S3: bucketEncryption プロパティ
const bucket = new s3.Bucket(this, "Bucket", {
  encryption: s3.BucketEncryption.S3_MANAGED,
});

// DynamoDB: encryption プロパティ
const table = new dynamodb.Table(this, "Table", {
  partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
  encryption: dynamodb.TableEncryption.AWS_MANAGED,
});

// SNS: masterKey プロパティ
const topic = new sns.Topic(this, "Topic", {
  masterKey: kmsKey,
});

// SQS: encryption プロパティ
const queue = new sqs.Queue(this, "Queue", {
  encryption: sqs.QueueEncryption.SQS_MANAGED,
});

リソースが少ない場合、これでも問題ありませんが、多数のリソースに同じポリシーを適用する場合、コードが煩雑になり、設定ミスなどが発生しやすくなります。
また、リソース間のセキュリティポリシーを確認したい場合も、各リソースの設定を個別に確認する必要があり少し面倒だったりします。

CDK Mixins を使うと、異なるサービス(S3、DynamoDB、SNS、SQS)に対して、一つのファイルで共通の暗号化ポリシーを定義し、各リソースに一括適用できます。これにより、コードの重複を減らし、保守性を向上させることができます。

以下は、カスタム Mixin を使って上記の各サービスに対して、共通に暗号化を適用する例です。これにより、前項で述べたサービスごとの異なる構文を統一できます。

import * as s3 from "aws-cdk-lib/aws-s3";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as sns from "aws-cdk-lib/aws-sns";
import * as sqs from "aws-cdk-lib/aws-sqs";
import { core } from "@aws-cdk/mixins-preview";
import { IConstruct } from "constructs";
import { Mixin, IMixin } from "@aws-cdk/mixins-preview/core"; 

/**
 * 複数サービスに暗号化を強制する Mixin
 */
export class EncryptionEnforcementMixin extends Mixin implements IMixin {
  supports(construct: IConstruct): boolean {
    return (
      construct instanceof s3.CfnBucket ||
      construct instanceof dynamodb.CfnTable ||
      construct instanceof sns.CfnTopic ||
      construct instanceof sqs.CfnQueue
    );
  }

  applyTo(construct: IConstruct): IConstruct {
    // S3 バケットの場合: SSE-S3 暗号化
    if (construct instanceof s3.CfnBucket) {
      construct.bucketEncryption = {
        serverSideEncryptionConfiguration: [
          {
            serverSideEncryptionByDefault: {
              sseAlgorithm: "AES256",
            },
          },
        ],
      };
      return construct;
    }

    // DynamoDB テーブルの場合: AWS管理キーで暗号化
    if (construct instanceof dynamodb.CfnTable) {
      construct.sseSpecification = {
        sseEnabled: true,
        sseType: "KMS",
      };
      return construct;
    }

    // SNS トピックの場合: AWS管理のKMSキーで暗号化
    if (construct instanceof sns.CfnTopic) {
      construct.kmsMasterKeyId = "alias/aws/sns";
      return construct;
    }

    // SQS キューの場合: SQS管理の暗号化
    if (construct instanceof sqs.CfnQueue) {
      construct.sqsManagedSseEnabled = true;
      return construct;
    }

    return construct;
  }
}

上記のコードでは、EncryptionEnforcementMixin というカスタム Mixins を定義しています。この Mixins は、.apply() メソッドを使って、S3 バケット、DynamoDB テーブル、SNS トピック、SQS キューに対して暗号化設定を一括で適用します。

上記の Mixins を使うと、先ほどのコードが各サービスの暗号化設定を省略できます。

import { Mixins } from "@aws-cdk/mixins-preview";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as sns from "aws-cdk-lib/aws-sns";
import * as sqs from "aws-cdk-lib/aws-sqs";
+import { EncryptionEnforcementMixin } from "./custom-mixins";

// S3 バケット
const bucket = new s3.Bucket(this, "Bucket", {
 // encryption プロパティ不要
- encryption: s3.BucketEncryption.S3_MANAGED,  

});

// DynamoDB テーブル
const table = new dynamodb.Table(this, "Table", {
  partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
  // encryption プロパティ不要
- encryption: dynamodb.TableEncryption.AWS_MANAGED,
  
});

// SNS トピック
const topic = new sns.Topic(this, "Topic", {
 // masterKey プロパティ不要
-  masterKey: kmsKey,
 
});

// SQS キュー
const queue = new sqs.Queue(this, "Queue", {
  // encryption プロパティ不要
-   encryption: sqs.QueueEncryption.SQS_MANAGED,
});

// Stack 全体に EncryptionEnforcementMixin を適用
+ Mixins.of(this).apply(new EncryptionEnforcementMixin());

仕組みとしては、Mixins.of(this) でスタック全体を対象にし、.apply(new EncryptionEnforcementMixin()) でカスタム Mixinsを適用しています。Mixins 内で supports() メソッドにより、対象のリソースタイプを判定し、applyTo() メソッドで各リソースに対して暗号化設定を追加しています。

L2 未実装の新機能へのアクセス

前項の例では、異なるサービスに共通の暗号化設定を適用しましたが、CDK MixinsはL2コンストラクトに存在しない新しいCloudFormationプロパティにもアクセスできます。
L1コンストラクトは、各AWSサービスに新機能が追加された際にプロパティとして使用できますが、L2コンストラクトは、プロパティの反映に時間がかかります。
L2 コンストラクトで存在しない新しい新機能のプロパティを使いたい場合、Escape Hatch を使う必要があり、コードが煩雑になりがちでした。

EScape Hatch とは、L2 コンストラクトの node.defaultChild プロパティを使って L1 コンストラクトにアクセスし、直接プロパティを設定する方法です。
より詳しく知りたい方は以下の記事を参照してください。
https://docs.aws.amazon.com/cdk/v2/guide/cfn-layer.html

例えば、S3 の Storage Class Analysisは 2025年12月時点(記事執筆時点)でL2 コンストラクトではプロパティ設定できません。
Storage Class Analysis はS3バケットの分析機能で、S3に保存されたデータのアクセスパターンを分析し、どのストレージクラス(Standard, Intelligent-Tieringrなど)が最適かを特定するための機能です。

CDK Mixins を使うと、L2 コンストラクトを使いながら、L1 コンストラクトの新機能にも簡単にアクセスできます。

import { Mixins } from "@aws-cdk/mixins-preview";
import * as s3 from "aws-cdk-lib/aws-s3";
import { IConstruct } from "constructs";
import { Mixin, IMixin } from "@aws-cdk/mixins-preview/core";   
/**
 * S3 バケットに Storage Class Analysis を追加する Mixin
 */
export class S3StorageClassAnalysisMixin extends Mixin implements IMixin {
  supports(construct: IConstruct): boolean {  
    return construct instanceof s3.CfnBucket;
  }     
  applyTo(construct: IConstruct): IConstruct {
    if (construct instanceof s3.CfnBucket) {
      construct.analyticsConfigurations = [
        {
          id: "EntireBucketAnalysis",
          storageClassAnalysis: {
            dataExport: {
              destination: {
               // 実在するバケットARNに書き換える必要あり
                bucketArn: "arn:aws:s3:::analytics-destination-bucket",
                format: "CSV",
              },
              outputSchemaVersion: "V_1",
            },
          },
        },
      ];
    }
    return construct;
  }

上記は、construct.analyticsConfigurationsプロパティを使って、S3 バケットに Storage Class Analysis を追加しています。
上記の Mixins を使うと、以下のようにL2コンストラクトで Storage Class Analysis を利用できます。


 const bucket1 = new s3.Bucket(this, "Bucket1", {
  encryption: s3.BucketEncryption.S3_MANAGED,
});
// 従来の方法: L2 + Escape Hatch
// Escape Hatch で L1 にアクセスして analyticsConfigurations を設定
-const cfnBucket1 = bucket1.node.defaultChild as s3.CfnBucket;
-cfnBucket1.analyticsConfigurations = [
-  {
-    id: "EntireBucketAnalysis",
-    storageClassAnalysis: {
-      dataExport: {
-        destination: {
-          bucketArn: "arn:aws:s3:::analytics-destination-bucket",
-          format: "CSV",
-        },
-        outputSchemaVersion: "V_1",
-      },
-    },
-  },
];
// 複数バケットで同じコードを繰り返す必要がある...
const bucket2 = new s3.Bucket(this, "Bucket2", {
  encryption: s3.BucketEncryption.S3_MANAGED,
});
-const cfnBucket2 = bucket2.node.defaultChild as s3.CfnBucket;
- // ... 同じ Escape Hatch コードを繰り返し

+ // Stack 全体に EncryptionEnforcementMixin を適用
+ // analyticsConfigurations が自動で設定される
+ Mixins.of(this).apply(new EncryptionEnforcementMixin());

上記のように、複数のS3バケットに適用する場合でも、その度に、Escape Hatchを使うことなく、
一括でStack全体に、L1コンストラクトのプロパティを適用できるのは便利ですね。

まとめ

今回は、例として、複数サービスに共通の暗号化設定を適用する方法を紹介しました。
CDK Mixinsは、まだプレビュー版ですが、今後のコンストラクトやスタックの設計が変わりそうな機能だなと感じました!
個人的には、aspectsと似たような機能に見えましたが、公式によると「変更を加える場合には Mixins、動作を検証する場合には Aspects を使用することが推奨」とのことで、使い分けというより互いに補完し合う機能のようです。

今後もCDK Mixinsの新機能や活用方法について、引き続きウォッチしていきたいと思います。

15
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?