OACのL2 Constructを使って、CloudFront＋S3での静的コンテンツ配信をやってみた

# はじめに
AWSサービスを用いて静的コンテンツを配信する場合、S3とCloudFrontを組み合わせて構築することが一般的かと思いますが、この際セキュリティを強化するために`OAC（Origin Access Control）`の設定が推奨されています。OACを設定することで、S3バケットへの直接的なアクセスを防ぎ、CloudFront経由の安全なアクセスだけを許可することが可能となります。
上記の構成をCDKで構築する場合、L2 Constructが提供されていなかったため、構築が少し手間でした。しかし、CDKのバージョン`2.156.0`から、OACのL2 Constructがサポートされるようになりました。これにより簡単にOACを構成できるようになったので、早速試してみました。

# OACとは
OAC（Origin Access Control）とは、CloudFrontとS3バケット間のセキュアなアクセスを制御するための仕組みです。元々OAI（Origin Access Identity）という方法があったのですが、現在ではOACの方が推奨される方法となっています。
詳しくは下記の記事をご覧ください。
https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/

# 動作環境
- Node.js 20.15.0
- TypeScript 5.5.2
- AWS CDK v2(2.156.0)
:::note warn
cdkのバージョン2.156.0からのサポートされた機能なので、2.156.0未満のバージョンを使用している場合はバージョンアップ対応をお願いします。
:::


# L2 Constructを使ったOACの実装
CloudFrontのdistributionを作成する時に、`S3BucketOrigin.withOriginAccessControl`を使ってOriginとなるS3を指定することで、OACを設定しています。
`app`ディレクトリにindex.htmlファイルを格納しており、このファイルを静的コンテンツ配信用のS3 バケットにdeployしています。

```lib/static_site-stack.ts
import {
  RemovalPolicy,
  Stack,
  StackProps,
  aws_cloudfront as cloudfront,
  aws_cloudfront_origins as cloudfront_origins,
  aws_s3 as s3,
  aws_s3_deployment as s3_deployment,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

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

    // 静的コンテンツを配置する S3 バケットを作成
    const staticContentBucket = new s3.Bucket(this, 'StaticContentBucket', {
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // CloudFront ディストリビューションを作成
    const distribution = new cloudfront.Distribution(this, 'Distribution', {
      defaultRootObject: 'index.html',
      defaultBehavior: {
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        origin: cloudfront_origins.S3BucketOrigin.withOriginAccessControl(staticContentBucket)
      },
    });

    // 静的コンテンツをS3にデプロイ
    new s3_deployment.BucketDeployment(this, 'staticContentDeploy', {
      sources: [s3_deployment.Source.asset('./app')],
      destinationBucket: staticContentBucket,
      distribution: distribution,
      distributionPaths: ['/*'],
    });
  }
}
```

```app/index.html
<h1>Hello, World!</h1>
```

たったこれだけの記述でOACが設定できるようになりました。

# 動作確認
cdkで作成したリソースの設定が問題ないか、マネージメントコンソールから確認してみます。
## ディストリビューションドメインからのアクセス確認
CloudFrontのディストリビューションドメイン名を確認して、ブラウザからアクセスしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1382306/262b889f-2546-1e68-51fd-f0801f92147f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1382306/76092b45-556a-f116-98b6-a7e5d49c5511.png)
Hello, World!が表示されました。

## オリジンの設定の確認
また、CloudFrontのオリジンを確認してみるとOACが設定されていること確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1382306/21f51de6-acb0-edae-bd7c-41188c48816f.png)

## 直接S3にアクセスできないことの確認
次に静的コンテンツを設置したS3に直接アクセスしてみます。
Origin domain欄に記載されているURLにブラウザからアクセスしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1382306/47d6d3b8-b38d-57cc-e484-62a48473e668.png)
ブラウザからS3に直接アクセスできないことも確認できました。

## S3バケットポリシーの確認
静的コンテンツをデプロイしたS3バケットのバケットポリシーを確認してみます。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::staticwebstack-frontendstaticcontentbucketf9d324c0-yrfwytyz71ki/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::AccountId:distribution/E25JIDG0CN8B70"
                }
            }
        }
    ]
}
```

上記のようなポリシーが作成されていて、cdkで作成したディストリビューションからのアクセスのみを許可していることがわかります。


# （補足）L2がない場合のcdkでのOAC実装方法
以下の記事では、L2 Constructがサポートされていなかった時にcdkでOACを設定するやり方について紹介されています。
s3バケットポリシーの変更やCloudFrontのオリジン設定の変更などを記述する必要があり、結構手間だったんではないでしょうか。
https://zenn.dev/thyt_lab/articles/d6423c883882b7

# まとめ
本記事では、OACのL2 Constructの使って、安全に静的コンテンツを配信する方法を試してみました。L2 ConstructがサポートされたことでCDKでの実装も簡単になったかと思うで、是非使っていきたいと思います。

# 参考
https://github.com/aws/aws-cdk/releases/tag/v2.156.0
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront_origins-readme.html