🚀

【AWSCDK】CloudFront+S3でOACを使いたい(L2)

2024/09/07に公開

今までCloudFront+S3でOACを使いたい場合はL1で細工していたのですが、v2.156.0で晴れてL2でのサポートが行われました!
早速試していきましょう!
https://github.com/aws/aws-cdk/releases/tag/v2.156.0
https://github.com/aws/aws-cdk/issues/21771

OAIで対応していた時の記事
https://zenn.dev/thyt_lab/articles/d6423c883882b7

前提

  • AWSCDK v2.156.0がインストールされていること

OAIでの実装

v2.156.0以前の実装方法は以下の通り。
OriginAccessIdentityを作成し、バケットに権限付与します。

lib/cdkstack.ts
import { RemovalPolicy, Stack, type StackProps } from "aws-cdk-lib";
import { Distribution, OriginAccessIdentity } from "aws-cdk-lib/aws-cloudfront";
import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment";
import type { Construct } from "constructs";

export class AwsCdkCloudfrontStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // S3
    const bucket = new Bucket(this, "MyBucket", {
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // OAI
    const originAccessIdentity = new OriginAccessIdentity(
      this,
      "OriginAccessIdentity",
    );
    // grant read permission to the OAI
    bucket.grantRead(originAccessIdentity);

    // CloudFront
    const distribution = new Distribution(this, "Distribution", {
      defaultRootObject: "index.html",
      defaultBehavior: {
        origin: new S3Origin(bucket, {
          originAccessIdentity,
        }),
      },
    });

    // deploy
    new BucketDeployment(this, "DeployWebsite", {
      sources: [Source.asset("../out")],
      destinationBucket: bucket,
      distribution,
      distributionPaths: ["/*"],
    });
  }
}

OAIの時に使用していたS3Originは非推奨になっていました。

デプロイが完了した後、CloudFormationで確認するとOAIが作成されていることを確認できます。

CloudFront側でも確認。

Distribution URLにアクセスすると、デプロイしたアプリが表示されます。

OACでの実装

S3Originが非推奨になり、S3BucketOriginが新たに用意されています。
S3BucketOrigin.withOriginAccessControlを使って、OACを設定していきます。

lib/cdkstack.ts
import { RemovalPolicy, Stack, type StackProps } from "aws-cdk-lib";
import { Distribution } from "aws-cdk-lib/aws-cloudfront";
import { S3BucketOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment";
import type { Construct } from "constructs";

export class AwsCdkCloudfrontStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // s3
    const bucket = new Bucket(this, "MyBucket", {
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // cloudfront
    const distribution = new Distribution(this, "Distribution", {
      defaultRootObject: "index.html",
      defaultBehavior: {
        origin: S3BucketOrigin.withOriginAccessIdentity(bucket),
      },
    });

    // deploy
    new BucketDeployment(this, "DeployWebsite", {
      sources: [Source.asset("../out")],
      destinationBucket: bucket,
      distribution,
      distributionPaths: ["/*"],
    });
  }
}
OAIとOACのdiff
lib/cdkstack.ts
import { RemovalPolicy, Stack, type StackProps } from "aws-cdk-lib";
- import { Distribution, OriginAccessIdentity } from "aws-cdk-lib/aws-cloudfront";
- import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
+ import { Distribution } from "aws-cdk-lib/aws-cloudfront";
+ import { S3BucketOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment";
import type { Construct } from "constructs";

export class AwsCdkCloudfrontStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // s3
    const bucket = new Bucket(this, "MyBucket", {
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

-    // OAI
-    const originAccessIdentity = new OriginAccessIdentity(
-      this,
-      "OriginAccessIdentity",
-    );
-    // grant read permission to the OAI
-    bucket.grantRead(originAccessIdentity);

    // cloudfront
    const distribution = new Distribution(this, "Distribution", {
      defaultRootObject: "index.html",
      defaultBehavior: {
-        origin: new S3Origin(bucket, {
-          originAccessIdentity,
-        }),
+        origin: S3BucketOrigin.withOriginAccessControl(bucket),
      },
    });

    // deploy
    new BucketDeployment(this, "DeployWebsite", {
      sources: [Source.asset("../out")],
      destinationBucket: bucket,
      distribution,
      distributionPaths: ["/*"],
    });
  }
}

リソースの差分は以下の通り。
PrincipalがCanonicalUserからServiceに変わっていることがわかります。

cdk diff
Stack AwsCdkCloudfrontStack
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
IAM Statement Changes
┌───┬────────────────────────────────────────────┬────────┬────────────────────────────────────────────┬────────────────────────────────────────────┬─────────────────────────────────────────────┐
│   │ Resource                                   │ Effect │ Action                                     │ Principal                                  │ Condition                                   │
├───┼────────────────────────────────────────────┼────────┼────────────────────────────────────────────┼────────────────────────────────────────────┼─────────────────────────────────────────────┤
│ - │ ${MyBucket.Arn}                            │ Allow  │ s3:GetBucket*                              │ CanonicalUser:${OriginAccessIdentity.S3Can │                                             │
│   │ ${MyBucket.Arn}/*                          │        │ s3:GetObject*                              │ onicalUserId}                              │                                             │
│   │                                            │        │ s3:List*                                   │                                            │                                             │
├───┼────────────────────────────────────────────┼────────┼────────────────────────────────────────────┼────────────────────────────────────────────┼─────────────────────────────────────────────┤
│ - │ ${MyBucket.Arn}/*                          │ Allow  │ s3:GetObject                               │ CanonicalUser:${OriginAccessIdentity.S3Can │                                             │
│   │                                            │        │                                            │ onicalUserId}                              │                                             │
├───┼────────────────────────────────────────────┼────────┼────────────────────────────────────────────┼────────────────────────────────────────────┼─────────────────────────────────────────────┤
│ + │ ${MyBucket.Arn}/*                          │ Allow  │ s3:GetObject                               │ Service:cloudfront.amazonaws.com           │ "StringEquals": {                           │
│   │                                            │        │                                            │                                            │   "AWS:SourceArn": "arn:${AWS::Partition}:c │
│   │                                            │        │                                            │                                            │ loudfront::${AWS::AccountId}:distribution/$ │
│   │                                            │        │                                            │                                            │ {Distribution}"                             │
│   │                                            │        │                                            │                                            │ }                                           │
└───┴────────────────────────────────────────────┴────────┴────────────────────────────────────────────┴────────────────────────────────────────────┴─────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::CloudFront::CloudFrontOriginAccessIdentity OriginAccessIdentity OriginAccessIdentityDF1E3CAC destroy
[+] AWS::CloudFront::OriginAccessControl Distribution/Origin1/S3OriginAccessControl DistributionOrigin1S3OriginAccessControlEB606076 
[~] AWS::S3::BucketPolicy MyBucket/Policy MyBucketPolicyE7FBAC7B 
 └─ [~] PolicyDocument
     └─ [~] .Statement:
         └─ @@ -39,53 +39,33 @@
            [ ]   ]
            [ ] },
            [ ] {
            [-]   "Action": [
            [-]     "s3:GetBucket*",
            [-]     "s3:GetObject*",
            [-]     "s3:List*"
            [-]   ],
            [-]   "Effect": "Allow",
            [-]   "Principal": {
            [-]     "CanonicalUser": {
            [-]       "Fn::GetAtt": [
            [-]         "OriginAccessIdentityDF1E3CAC",
            [-]         "S3CanonicalUserId"
            [-]       ]
            [+]   "Action": "s3:GetObject",
            [+]   "Condition": {
            [+]     "StringEquals": {
            [+]       "AWS:SourceArn": {
            [+]         "Fn::Join": [
            [+]           "",
            [+]           [
            [+]             "arn:",
            [+]             {
            [+]               "Ref": "AWS::Partition"
            [+]             },
            [+]             ":cloudfront::",
            [+]             {
            [+]               "Ref": "AWS::AccountId"
            [+]             },
            [+]             ":distribution/",
            [+]             {
            [+]               "Ref": "Distribution830FAC52"
            [+]             }
            [+]           ]
            [+]         ]
            [+]       }
            [ ]     }
            [ ]   },
            [-]   "Resource": [
            [-]     {
            [-]       "Fn::GetAtt": [
            [-]         "MyBucketF68F3FF0",
            [-]         "Arn"
            [-]       ]
            [-]     },
            [-]     {
            [-]       "Fn::Join": [
            [-]         "",
            [-]         [
            [-]           {
            [-]             "Fn::GetAtt": [
            [-]               "MyBucketF68F3FF0",
            [-]               "Arn"
            [-]             ]
            [-]           },
            [-]           "/*"
            [-]         ]
            [-]       ]
            [-]     }
            [-]   ]
            [-] },
            [-] {
            [-]   "Action": "s3:GetObject",
            [ ]   "Effect": "Allow",
            [ ]   "Principal": {
            [-]     "CanonicalUser": {
            [-]       "Fn::GetAtt": [
            [-]         "OriginAccessIdentityDF1E3CAC",
            [-]         "S3CanonicalUserId"
            [-]       ]
            [-]     }
            [+]     "Service": "cloudfront.amazonaws.com"
            [ ]   },
            [ ]   "Resource": {
            [ ]     "Fn::Join": [
[~] AWS::CloudFront::Distribution Distribution Distribution830FAC52 
 └─ [~] DistributionConfig
     └─ [~] .Origins:
         └─ @@ -7,18 +7,14 @@
            [ ]       ]
            [ ]     },
            [ ]     "Id": "AwsCdkCloudfrontStackDistributionOrigin1C6C3A1F0",
            [+]     "OriginAccessControlId": {
            [+]       "Fn::GetAtt": [
            [+]         "DistributionOrigin1S3OriginAccessControlEB606076",
            [+]         "Id"
            [+]       ]
            [+]     },
            [ ]     "S3OriginConfig": {
            [-]       "OriginAccessIdentity": {
            [-]         "Fn::Join": [
            [-]           "",
            [-]           [
            [-]             "origin-access-identity/cloudfront/",
            [-]             {
            [-]               "Ref": "OriginAccessIdentityDF1E3CAC"
            [-]             }
            [-]           ]
            [-]         ]
            [-]       }
            [+]       "OriginAccessIdentity": ""
            [ ]     }
            [ ]   }
            [ ] ]


✨  Number of stacks with differences: 1

リソースのデプロイ後CloudFormationにて確認すると、OACが作成されています。

CloudFrontのオリジンアクセスについてもOACに変わっていることが確認できます。

Distribution URLにアクセスすると、OAIと変わらずデプロイしたアプリが表示されます。

まとめ

L2にてOACの対応ができるようになったのは待ちに待ったアップデートでした。(ありがたや)
CDKで対応するのは大変だったためマネコンから実施する人もいたのではないでしょうか。
この記事が誰かの参考になると嬉しいです。

Discussion