AWS CDK v2でOACってどうやってやるの？CloudFront+S3+OAC構築（コード付き-Typescript）

# はじめに

　AWS CDKでリソース作ってますか？？
　最近、AWS CDKを見ること、触ることが多く、少しずつ仲良くなってきました。そんな中、AWS CDKでAmazon CloudFront オリジンアクセスコントロールをどうやってやるのか、困ったのでご紹介です。

# アーキテクチャ

　以下の通り、利用者からアクセスされ、オリジンサーバとなるS3から表示用HTMLを利用者に返却するアーキテクチャです。よくあるやつですね。

![名称未設定-ページ3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129814/cf801073-e5b7-413f-355a-e40f85bad23b.png)

　上記のCloudFrontとS3のアクセス部分でオリジンアクセスコントロール（OAC）を利用します。OACを利用することでS3自体をパブリックにする必要がなくなり、S3への直接的な意図しないアクセスをできない様にします。
　下記の赤枠部分です。

![名称未設定-ページ3.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129814/24eeff5f-d865-bc65-1b07-9b2bb39ace79.png)


　従来は、オリジンアクセスアイデンティティ（OAI）を利用していましたが、OACが登場してからはOACが推奨されています。
　OACは、S3に対して安全にアクセスする形となり、OAIよりよりセキュアになっています。（OAC自体がよりセキュアでいつくかのオプション設定も可能になっています。）

　以下のブログで詳細が紹介されています。

https://aws.amazon.com/jp/blogs/news/amazon-cloudfront-introduces-origin-access-control-oac/

　デベロッパーガイドはこちらです。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

# AWS CDKでのOAC

　では、さっそくやってみよう、とAWS CDKのリファレンスを覗きます。
（最初、みんなどうやって作ってるのかな？やっぱリファレンス？）

　aws-cdk-lib.aws_cloudfrontです。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront-readme.html

　そして、眺めると「class OriginAccessIdentity」しかないと、気づきます。。。（2023/3/14時点）

　で、いろいろ調べるとここに行きつきました。まだ、L2ではサポートされていないようです。

https://github.com/aws/aws-cdk/issues/21771

　ということで、L1で頑張って作ることになりました。

# AWS CDKでの実装（Typescript）

　作ったのが以下です。

:::note warn
各リソースのプロパティは省略しており、デフォルト値が利用される形になっています。利用される場合は、ご注意ください。
:::

```typescript:FrontendStack.ts
import { aws_cloudfront, aws_cloudfront_origins, aws_iam, aws_s3, Stack, StackProps } from 'aws-cdk-lib';
import { BlockPublicAccess } from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

interface SubStackProps extends StackProps {
  accountId: string;
}

export class FrontendStack extends Stack {

  public readonly contentsBucket: aws_s3.IBucket;
  public readonly distribution: aws_cloudfront.IDistribution;

  constructor(scope: Construct, id: string, props: SubStackProps) {
    super(scope, id, props);

    // S3
    this.contentsBucket = new aws_s3.Bucket(this, 'ContentsBucket', {
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
    });

    // OAC
    // https://github.com/aws/aws-cdk/issues/21771
    const cfnOriginAccessControl = new aws_cloudfront.CfnOriginAccessControl(this, 'OriginAccessControl', {
      originAccessControlConfig: {
        name: 'OriginAccessControlForContentsBucket',
        originAccessControlOriginType: 's3',
        signingBehavior: 'always',
        signingProtocol: 'sigv4',
        description: 'Access Control',
      },
    });

    // CloudFront
    this.distribution = new aws_cloudfront.Distribution(this, 'Distribution', {
      comment: 'distribution.',
      defaultBehavior: {
        origin: new aws_cloudfront_origins.S3Origin(this.contentsBucket),
        allowedMethods: aws_cloudfront.AllowedMethods.ALLOW_ALL,
      },
      defaultRootObject: 'index.html',
      httpVersion: aws_cloudfront.HttpVersion.HTTP2_AND_3,
    });

    const cfnDistribution = this.distribution.node.defaultChild as aws_cloudfront.CfnDistribution;
    // OAI削除（勝手に設定されるため）
    cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity', '');
    // OAC設定
    cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginAccessControlId', cfnOriginAccessControl.attrId);

    // S3 - BucketPolicy
    const contentsBucketPolicyStatement = new aws_iam.PolicyStatement({
      actions: ['s3:GetObject'],
      effect: aws_iam.Effect.ALLOW,
      principals: [
        new aws_iam.ServicePrincipal('cloudfront.amazonaws.com'),
      ],
      resources: [`${this.contentsBucket.bucketArn}/*`],
    });
    contentsBucketPolicyStatement.addCondition('StringEquals', {
      'AWS:SourceArn': `arn:aws:cloudfront::${props.accountId}:distribution/${this.distribution.distributionId}`
    })
    this.contentsBucket.addToResourcePolicy(contentsBucketPolicyStatement);

  }

}

```

### <注意点>
* aws_cloudfront.DistributionがデフォルトでOAIを設定するようになっていますので、（無理矢理？）OACを利用するようになっています。
* S3のバケットポリシーにOAIの設定が入ってしまいます。削除する部分まで入れていません。

![スクリーンショット 2023-03-14 23.24.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129814/697f2e1a-afe3-23a2-23d5-972d9b0d34a6.png)


## 作成されたリソース（OAC)
　実際にデプロイすると、CloudFront、S3、OACが作成され、CloudFront distribution設定内にあるS3のオリジン設定を見ると以下の様に構成されます。OACとなっていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129814/8318e5fa-7248-469c-123c-5277b24e4597.png)


# まとめ
　今回はAWS CDKでCloudFront + S3 + OACを構築しました。やり方わかると意外とあっさりしていました。CloudFormationは対応できているけど、AWS CDKのL2はまだ対応されていない、というものも多々ありますので、参考になれば幸いです。