【AWSCDK】CloudFront+S3でOACを使いたい
はじめに
久しぶりにAWSCDKを使って、静的アセットのデプロイを行っていたところ、OAI(Origin Access Identify)がLegacy扱いとなっていたため、OAC(Origin Access Control)に変更したときの備忘録です。
OAIを使った時のCDK
// s3
const s3 = new cdk.aws_s3.Bucket(this, 'bucket-id', {
websiteIndexDocument: 'index.html',
publicReadAccess: true
})
// OAI
const originAccessIdentity = new cdk.aws_cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity')
// cloudfront(distribution)
const distribution = new cdk.aws_cloudfront.Distribution(this, 'distribution-id', {
defaultRootObject: 'index.html',
defaultBehavior: {
origin: new cdk.aws_cloudfront_origins.S3Origin(s3, { originAccessIdentity })
},
})
const bucketPolicyStatement = new cdk.aws_iam.PolicyStatement({
actions: ['s3:GetObject'],
effect: cdk.aws_iam.Effect.ALLOW,
principals: [
new cdk.aws_iam.CanonicalUserPrincipal(originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId)
],
resources: [`${s3.bucketArn}/*`]
})
s3.addToResourcePolicy(bucketPolicyStatement)
new cdk.aws_s3_deployment.BucketDeployment(this, 'CDKReactDeployPractice', {
sources: [cdk.aws_s3_deployment.Source.asset('<appPath>')],
destinationBucket: s3,
distribution,
distributionPaths: ['/*']
})
OACに変更する
// s3
const s3 = new cdk.aws_s3.Bucket(this, 'bucket-id', {
websiteIndexDocument: 'index.html',
blockPublicAccess: cdk.aws_s3.BlockPublicAccess.BLOCK_ALL
})
// OAC
const cfnOriginAccessControl = new cdk.aws_cloudfront.CfnOriginAccessControl(this, 'OriginAccessControl', {
originAccessControlConfig: {
name: 'OriginAccessControlForContentsBucket',
originAccessControlOriginType: 's3',
signingBehavior: 'always',
signingProtocol: 'sigv4',
description: 'Access Control',
},
});
// cloudfront(distribution)
const distribution = new cdk.aws_cloudfront.Distribution(this, 'distribution-id', {
defaultRootObject: 'index.html',
defaultBehavior: {
origin: new cdk.aws_cloudfront_origins.S3Origin(s3)
},
})
const bucketPolicyStatement = new cdk.aws_iam.PolicyStatement({
actions: ['s3:GetObject'],
effect: cdk.aws_iam.Effect.ALLOW,
principals: [
new cdk.aws_iam.ServicePrincipal('cloudfront.amazonaws.com')
],
resources: [`${s3.bucketArn}/*`]
})
bucketPolicyStatement.addCondition('StringEquals', {
'AWS:SourceArn': `arn:aws:cloudfront::${cdk.Stack.of(this).account}:distribution/${distribution.distributionId}`
})
s3.addToResourcePolicy(bucketPolicyStatement)
const cfnDistribution = distribution.node.defaultChild as cdk.aws_cloudfront.CfnDistribution
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginAccessControlId', cfnOriginAccessControl.getAtt('Id'))
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.DomainName', s3.bucketRegionalDomainName)
cfnDistribution.addOverride('Properties.DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity', "")
cfnDistribution.addPropertyDeletionOverride('DistributionConfig.Origins.0.CustomOriginConfig')
new cdk.aws_s3_deployment.BucketDeployment(this, 'CDKReactDeployPractice', {
sources: [cdk.aws_s3_deployment.Source.asset('<appPath>')],
destinationBucket: s3,
distribution,
distributionPaths: ['/*']
})
OACの作成
L2 Constructではまだ用意されていないので、L1 Constructを使って定義します。
const cfnOriginAccessControl = new cdk.aws_cloudfront.CfnOriginAccessControl(this, 'OriginAccessControl', {
originAccessControlConfig: {
name: 'OriginAccessControlForContentsBucket',
originAccessControlOriginType: 's3',
signingBehavior: 'always',
signingProtocol: 'sigv4',
description: 'Access Control',
},
});
principalの設定
S3ポリシーのprincipalはOACではサービスを対象とします。
principals: [
- new cdk.aws_iam.CanonicalUserPrincipal(originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId)
+ new cdk.aws_iam.ServicePrincipal('cloudfront.amazonaws.com')
],
AWS:SourceArnの設定
CloudFrontのArnからのアクセスに対応するよう設定を追加します。
+ bucketPolicyStatement.addCondition('StringEquals', {
+ 'AWS:SourceArn': `arn:aws:cloudfront::${cdk.Stack.of(this).account}:distribution/${distribution.distributionId}`
+ })
CloudFrontのオリジン設定を変更
OAIを使うときの設定がデフォルトで行われるため、OACに適応したテンプレートに上書きします。
const cfnDistribution = distribution.node.defaultChild as cdk.aws_cloudfront.CfnDistribution
// OACのidを指定する
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginAccessControlId', cfnOriginAccessControl.getAtt('Id'))
// デフォルトではs3のWebsiteURLが設定されてエラーとなるため、S3のドメイン名を設定する
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.DomainName', s3.bucketRegionalDomainName)
// OAIの設定は不要のため、空文字で上書き
cfnDistribution.addOverride('Properties.DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity', "")
// OACではCustomOriginConfigは不要
cfnDistribution.addPropertyDeletionOverride('DistributionConfig.Origins.0.CustomOriginConfig')
S3のドメイン名はs3.bucketDomainName
でも良いが、CloudFrontに反映されるまで時間を要するため、理由がない限りはリージョンを含めたドメイン名を設定するのがよいかと。
トラブルシューティング
OACに変更する際に発生したエラーと解消方法を以下に記載しておきますので、参考になれば幸いです。
The origin type and OAC origin type differ
OACで設定するS3Originの値はWebsiteURLではなく、バケットのドメイン名を設定する必要があります。デフォルトではOAIを使った設定でテンプレートが作成されるので、上書きする必要があります。
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.DomainName', s3.bucketRegionalDomainName)
Exactly one of CustomOriginConfig and S3OriginConfig must be specified
S3OriginConfig
とCustomOriginConfig
はOACにおいて不要なので、上書き及び削除の対応が必要になります。
cfnDistribution.addOverride('Properties.DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity', "")
cfnDistribution.addPropertyDeletionOverride('DistributionConfig.Origins.0.CustomOriginConfig')
おわりに
OACがL2 Constructで設定できるようになると、この辺の対応は不要になるかなと。
世界中の天才が作ってくれています。感謝感謝。
CDKでどのようにテンプレートが作られているか、深く知ることができたので自分にとっては大きな収穫でした。
S3+CloudFrontでOACを使った構成を考えている方の参考になれば幸いです。
Discussion