[AWS CDK] CloudFront Origin Access Identity を使用した S3 Bucket へのアクセス制限の動作を確認してみた

2021.07.24

こんにちは、CX事業本部 IoT事業部の若槻です。

Amazon CloudFront + S3を使用してバケットからコンテンツを配信する構成では、S3コンテンツへのアクセスを特定のCloudFront Distributionからのみに制限することが重要となります。もしCloudFrontをバイパスしたコンテンツへの直接アクセスが許可されていると、下記のような不都合が生じてしまいます。

  • CloudFront Distributionのエッジキャッシュによるコンテンツ配信が行えないため、配信のパフォーマンスが下がったり、コストが大きくなったりする
  • CloudFront Distributionに関連付けられたAWS WAFのWeb ACLもバイパスされるため、マネージドルールやIPアドレスによるアクセス制御が適用できない

このS3バケットのアクセス制御の設定は、CloudFrontのOrigin Access Identityを使用することにより可能です。

  1. オリジンアクセスアイデンティティ (OAI) と呼ばれる特別な CloudFront ユーザーを作成し、ディストリビューションに関連付けます。

  2. CloudFront が OAI を使用してバケット内のファイルにアクセスしてユーザーに提供できるように、S3 バケットのアクセス許可を設定します。ユーザーが S3 バケットへのダイレクト URL を使用して、そこにあるファイルにアクセスできないようにしてください。

今回は、CloudFront Origin Access Identityを使用したS3 Bucketへの直接アクセスの制限をAWS CDKを使用して実装し、動作を確認してみました。

直接アクセスを制限する

CDKスタック

lib/sample-app-stack.ts

import * as cdk from "@aws-cdk/core";
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as s3 from "@aws-cdk/aws-s3";
import * as s3deploy from "@aws-cdk/aws-s3-deployment";
import * as iam from "@aws-cdk/aws-iam";

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

    const accountId = cdk.Stack.of(this).account;

    // 1. OriginとなるS3 Bucket作成
    const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
      bucketName: `sample-app-${accountId}`,
      websiteErrorDocument: "index.html",
      websiteIndexDocument: "index.html",
    });

    // 2. Origin Access Identity作成
    const websiteIdentity = new cloudfront.OriginAccessIdentity(
      this,
      "WebsiteIdentity",
      {
        comment: `website-identity`,
      }
    );

    // 3. Origin Access Identityからのアクセスのみ許可するBucket Policyを作成
    const webSiteBucketPolicyStatement = new iam.PolicyStatement({
      actions: ["s3:GetObject"],
      effect: iam.Effect.ALLOW,
      principals: [
        new iam.CanonicalUserPrincipal(
          websiteIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
      resources: [`${websiteBucket.bucketArn}/*`],
    });

    // 4. Bucket PolicyをS3 Bucketに適用
    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    // 5. CloudFront Distributionを作成
    const websiteDistribution = new cloudfront.CloudFrontWebDistribution(
      this,
      "WebsiteDistribution",
      {
        comment: `website-distribution`,
        errorConfigurations: [
          {
            errorCachingMinTtl: 300,
            errorCode: 403,
            responseCode: 200,
            responsePagePath: "/index.html",
          },
          {
            errorCachingMinTtl: 300,
            errorCode: 404,
            responseCode: 200,
            responsePagePath: "/index.html",
          },
        ],
        originConfigs: [
          {
            // 6. DistributionにOrigin情報(S3 Bucket、Origin Access Identity)を設定
            s3OriginSource: {
              s3BucketSource: websiteBucket,
              originAccessIdentity: websiteIdentity,
            },
            behaviors: [
              {
                isDefaultBehavior: true,
              },
            ],
          },
        ],
        priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
      }
    );

    new s3deploy.BucketDeployment(this, "WebsiteDeploy", {
      sources: [s3deploy.Source.asset("./web/build")],
      destinationBucket: websiteBucket,
      distribution: websiteDistribution,
      distributionPaths: ["/*"],
    });
  }
}

リソース作成と設定を下記の通り行っています。

  1. OriginとなるS3 Bucket作成
  2. Origin Access Identity作成
  3. Origin Access Identityからのアクセスのみ許可するBucket Policyを作成
  4. Bucket PolicyをS3 Bucketに適用
  5. CloudFront Distributionを作成
  6. DistributionにOrigin情報(S3 Bucket、Origin Access Identity)を設定

cdk deployでスタックをデプロイします。

動作確認

CloudFront DistributionのURLを開くと、アクセスできました。

OriginとなるS3BucketのコンテンツのURLを直接開くと、アクセスは拒否されました。

ちゃんとOrigin Access Identityによるアクセス制限ができていますね。

直接アクセスを許可する

Bucketへの直接アクセスを許可する場合の実装の方も試してみます。

CDKスタックの変更

S3 Bucketの定義で、publicReadAccess: trueを追記し、Bucketを公開設定にします。

lib/sample-app-stack.ts

    const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
      bucketName: `sample-app-${accountId}`,
      websiteErrorDocument: "index.html",
      websiteIndexDocument: "index.html",
      publicReadAccess: true,
    });

Bucket Policyの定義で、許可対象のPrincipalをnew iam.AnyPrincipal()に変更し、どのPrincipalからもアクセス可能とします。

lib/sample-app-stack.ts

    const webSiteBucketPolicyStatement = new iam.PolicyStatement({
      actions: ["s3:GetObject"],
      effect: iam.Effect.ALLOW,
      principals: [new iam.AnyPrincipal()],
      resources: [`${websiteBucket.bucketArn}/*`],
    });

動作確認

CloudFront DistributionのURLを開くと、引き続きアクセスすることができました。

OriginとなるS3BucketのコンテンツのURLを直接開くと、こちらもアクセスすることができました。

おわりに

CloudFront Origin Access Identityを使用したS3 Bucketへの直接アクセスの制限をAWS CDKを使用して実装し、動作を確認してみました。

今までAWS CDKでウェブサイトを実装する際のお約束として行ってきた設定ではあるのですが、CDK上での設定の意味や効果などを改めて確認することが出来て良かったです。

参考

以上