AWS CDK で S3 バケットポリシーによるアクセス制限を公開バケットに設定する場合は、aws_s3.BucketPolicy で明示的にバケットポリシーを作成しよう

2023.11.25

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

Amazon S3 バケットポリシーを使用すると、バケット内のオブジェクトへのアクセスを保護して、適切な権限を持つユーザーやサービスだけがアクセスできるように制限を設けることができます。

今回は、AWS CDK で S3 バケットポリシーによるアクセス制限を公開バケットに設定しようとした際に、既定で許可ステートメントが追加されてしまう問題に遭遇したので、その解決方法をご紹介します。

[問題] S3 バケットポリシーに既定で許可ステートメントが追加されてしまう

Web サイトホスティング用の S3 バケットへのアクセスをリファラーおよびプリンシパルで制限するために、バケットポリシーに許可ステートメントを追加する設定を AWS CDK で行いました。

lib/cdk-sample-stack.ts

import {
  aws_iam,
  aws_s3,
  Stack,
  StackProps,
  RemovalPolicy,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

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

    // S3 バケット
    const websiteBucket = new aws_s3.Bucket(this, 'WebsiteBucket', {
      publicReadAccess: true,
      blockPublicAccess: aws_s3.BlockPublicAccess.BLOCK_ACLS,
      websiteIndexDocument: 'index.html',
      removalPolicy: RemovalPolicy.DESTROY,
      // autoDeleteObjects: true, // ポリシー単純化のため一旦コメントアウト
    });

    // アクセス制限用のステートメント
    const bucketStatement = new aws_iam.PolicyStatement({
      effect: aws_iam.Effect.ALLOW,
      principals: [new aws_iam.ServicePrincipal('cloudfront.amazonaws.com')],
      actions: ['s3:GetObject'],
      resources: [websiteBucket.arnForObjects('*')],
      conditions: {
        StringEquals: { 'aws:Referer': ['hoge'] },
      },
    });

    // バケットポリシーにアクセス制限用のステートメントを追加
    websiteBucket.addToResourcePolicy(bucketStatement);
  }
}

しかし、上記を CDK でデプロイし、作成された S3 バケットポリシーを確認すると、アクセス制限用のステートメントに加えて、以下のように既定で許可ステートメントが追加されていました。

$ aws s3api get-bucket-policy --bucket ${BUCKET_NAME} | jq -r .Policy | jq . 
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::cdksamplestack-websitebucket75c24d94-wjvvm1ewh1be/*"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::cdksamplestack-websitebucket75c24d94-wjvvm1ewh1be/*",
      "Condition": {
        "StringEquals": {
          "aws:Referer": "hoge"
        }
      }
    }
  ]
}

これだとすべての s3:GetObject アクセスが許可されてしまい、アクセスを制限するために設定した許可ステートメントの意味がなくなってしまいます。

[解決] aws_s3.BucketPolicy クラスで明示的にバケットポリシーを作成する

BucketPolicy コンストラクトクラスを使って予め明示的にバケットポリシーを作成し、そのバケットポリシーに対してアクセス制限用の許可ステートメントを追加する方法で解決しました。

次のように明示的に作成したバケットポリシーに対してアクセス制限用の許可ステートメントを追加するようにします。

lib/cdk-sample-stack.ts

import {
  aws_iam,
  aws_s3,
  Stack,
  StackProps,
  RemovalPolicy,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

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

    // S3 バケット
    const websiteBucket = new aws_s3.Bucket(this, 'WebsiteBucket', {
      publicReadAccess: true,
      blockPublicAccess: aws_s3.BlockPublicAccess.BLOCK_ACLS,
      websiteIndexDocument: 'index.html',
      removalPolicy: RemovalPolicy.DESTROY,
      // autoDeleteObjects: true, // ポリシー単純化のため一旦コメントアウト
    });

    // バケットポリシーの明示的な作成
    const websiteBucketPolicy = new aws_s3.BucketPolicy(
      this,
      'WebsiteBucketPolicy',
      {
        bucket: websiteBucket,
      }
    );

    // バケットポリシーにアクセス制限用のステートメントを追加
    websiteBucketPolicy.document.addStatements(
      new aws_iam.PolicyStatement({
        effect: aws_iam.Effect.ALLOW,
        principals: [new aws_iam.ServicePrincipal('cloudfront.amazonaws.com')],
        actions: ['s3:GetObject'],
        resources: [websiteBucket.arnForObjects('*')],
        conditions: {
          StringEquals: { 'aws:Referer': ['hoge'] },
        },
      })
    );
  }
}

すると、バケットポリシーにはアクセス制限用の許可ステートメントのみが追加され、既定の許可ステートメントは追加されなくなりました!

aws s3api get-bucket-policy --bucket ${BUCKET_NAME} | jq -r .Policy | jq .
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::cdksamplestack-websitebucket75c24d94-wjvvm1ewh1be/*",
      "Condition": {
        "StringEquals": {
          "aws:Referer": "hoge"
        }
      }
    }
  ]
}

非公開バケットに OAI のアクセス権限を設定した場合は、既定の許可ステートメントが追加されない

その他によくあるケースとして、非公開バケットに対して CloudFront の Origin Access Identity (OAI) のアクセス権限を設定した場合も確認してみました。

import {
  aws_iam,
  aws_s3,
  aws_cloudfront,
  Stack,
  StackProps,
  RemovalPolicy,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

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

    const websiteBucket = new aws_s3.Bucket(this, 'WebsiteBucket', {
      removalPolicy: RemovalPolicy.DESTROY,
      // autoDeleteObjects: true, // ポリシー単純化のため一旦コメントアウト
    });

    const originAccessIdentity = new aws_cloudfront.OriginAccessIdentity(
      this,
      'OriginAccessIdentity',
      {
        comment: 'website-distribution-originAccessIdentity',
      }
    );

    const webSiteBucketPolicyStatement = new aws_iam.PolicyStatement({
      actions: ['s3:GetObject'],
      effect: aws_iam.Effect.ALLOW,
      principals: [
        new aws_iam.CanonicalUserPrincipal(
          originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
      resources: [websiteBucket.arnForObjects('*')],
    });

    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);
  }
}

すると、バケットポリシーには OAI のアクセス権限のみが追加され、既定の許可ステートメントは追加されませんでした。

$ aws s3api get-bucket-policy --bucket ${BUCKET_NAME} | jq -r .Policy | jq .
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E20LBITCOYEE3P"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::cdksamplestack-websitebucket75c24d94-wjvvm1ewh1be/*"
    }
  ]
}

バケットポリシーに既定の許可ステートメントが追加されるのは公開バケットの場合のみのようです。バケットを公開設定にしたら、パブリックアクセスを可能にするための権限設定が既定で追加されるのは、仕様として意図した挙動みたいですね。

おわりに

今回は、AWS CDK で S3 バケットポリシーによるアクセス制限を公開バケットに設定しようとした際に、既定で許可ステートメントが追加されてしまう問題に遭遇したので、その解決方法をご紹介しました。

バケットポリシーに意図しない許可ステートメントが追加されているため、実際にはアクセス制限が想定通りににできていないケースもあるかと思います。バケットがそもそも非公開であれば良いのですが、公開設定となっている場合は不正アクセスが行わてしまう可能性もあります。一度ご自身の環境も確認をしてみてください。

参考

以上