diff --git a/API.md b/API.md index 4ca7e0f6..dcc834ec 100644 --- a/API.md +++ b/API.md @@ -2982,6 +2982,7 @@ const nextjsDistributionOverrides: NextjsDistributionOverrides = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | +| imageResponseHeadersPolicyProps | aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps | *No description.* | | cloudFrontFunctionProps | OptionalCloudFrontFunctionProps | *No description.* | | distributionProps | OptionalDistributionProps | *No description.* | | edgeFunctionProps | OptionalEdgeFunctionProps | *No description.* | @@ -2992,7 +2993,19 @@ const nextjsDistributionOverrides: NextjsDistributionOverrides = { ... } | serverBehaviorOptions | aws-cdk-lib.aws_cloudfront.BehaviorOptions | *No description.* | | serverCachePolicyProps | aws-cdk-lib.aws_cloudfront.CachePolicyProps | *No description.* | | serverHttpOriginProps | aws-cdk-lib.aws_cloudfront_origins.HttpOriginProps | *No description.* | +| serverResponseHeadersPolicyProps | aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps | *No description.* | | staticBehaviorOptions | aws-cdk-lib.aws_cloudfront.BehaviorOptions | *No description.* | +| staticResponseHeadersPolicyProps | aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps | *No description.* | + +--- + +##### `imageResponseHeadersPolicyProps`Required + +```typescript +public readonly imageResponseHeadersPolicyProps: ResponseHeadersPolicyProps; +``` + +- *Type:* aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps --- @@ -3096,6 +3109,16 @@ public readonly serverHttpOriginProps: HttpOriginProps; --- +##### `serverResponseHeadersPolicyProps`Optional + +```typescript +public readonly serverResponseHeadersPolicyProps: ResponseHeadersPolicyProps; +``` + +- *Type:* aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps + +--- + ##### `staticBehaviorOptions`Optional ```typescript @@ -3106,6 +3129,16 @@ public readonly staticBehaviorOptions: BehaviorOptions; --- +##### `staticResponseHeadersPolicyProps`Optional + +```typescript +public readonly staticResponseHeadersPolicyProps: ResponseHeadersPolicyProps; +``` + +- *Type:* aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicyProps + +--- + ### NextjsDistributionProps #### Initializer diff --git a/src/NextjsDistribution.ts b/src/NextjsDistribution.ts index dad290ce..33502bde 100644 --- a/src/NextjsDistribution.ts +++ b/src/NextjsDistribution.ts @@ -27,11 +27,14 @@ export interface NextjsDistributionOverrides { readonly edgeFunctionProps?: OptionalEdgeFunctionProps; readonly imageBehaviorOptions?: BehaviorOptions; readonly imageCachePolicyProps?: CachePolicyProps; + readonly imageResponseHeadersPolicyProps: cloudfront.ResponseHeadersPolicyProps; readonly imageHttpOriginProps?: HttpOriginProps; readonly serverBehaviorOptions?: BehaviorOptions; readonly serverCachePolicyProps?: CachePolicyProps; + readonly serverResponseHeadersPolicyProps?: cloudfront.ResponseHeadersPolicyProps; readonly serverHttpOriginProps?: HttpOriginProps; readonly staticBehaviorOptions?: BehaviorOptions; + readonly staticResponseHeadersPolicyProps?: cloudfront.ResponseHeadersPolicyProps; readonly s3OriginProps?: OptionalS3OriginProps; } @@ -97,6 +100,26 @@ export class NextjsDistribution extends Construct { compress: true, }; + /** + * Common security headers applied by default to all origins + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html#managed-response-headers-policies-security + */ + private commonSecurityHeadersBehavior: cloudfront.ResponseSecurityHeadersBehavior = { + contentTypeOptions: { override: false }, + frameOptions: { frameOption: cloudfront.HeadersFrameOption.SAMEORIGIN, override: false }, + referrerPolicy: { + override: false, + referrerPolicy: cloudfront.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN, + }, + strictTransportSecurity: { + accessControlMaxAge: Duration.days(365), + includeSubdomains: true, + override: false, + preload: true, + }, + xssProtection: { override: false, protection: true, modeBlock: true }, + }; + private s3Origin: origins.S3Origin; private staticBehaviorOptions: cloudfront.BehaviorOptions; @@ -160,39 +183,15 @@ export class NextjsDistribution extends Construct { { header: 'cache-control', override: false, - // by default tell browser to cache static files for this long - // this is separate from the origin cache policy - // copied from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#caching_static_assets_with_cache_busting - value: `public,max-age=${Duration.days(30).toSeconds()},immutable`, - }, - // below security headers copied from: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html#managed-response-headers-policies-security - { - header: 'referrer-policy', - override: false, - value: 'strict-origin-when-cross-origin', - }, - { - header: 'strict-transport-security', - override: false, - value: 'max-age=31536000', - }, - { - header: 'x-content-type-options', - override: true, - value: 'nosniff', - }, - { - header: 'x-frame-options', - override: false, - value: 'SAMEORIGIN', - }, - { - header: 'x-xss-protection', - override: false, - value: '1; mode=block', + // MDN Cache-Control Use Case: Caching static assets with "cache busting" + // @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#caching_static_assets_with_cache_busting + value: `max-age=${Duration.days(365).toSeconds()}, immutable`, }, ], }, + securityHeadersBehavior: this.commonSecurityHeadersBehavior, + comment: 'Nextjs Static Response Headers Policy', + ...this.props.overrides?.staticResponseHeadersPolicyProps, }); return { ...this.commonBehaviorOptions, @@ -264,9 +263,25 @@ export class NextjsDistribution extends Construct { minTtl: Duration.seconds(0), enableAcceptEncodingBrotli: true, enableAcceptEncodingGzip: true, - comment: 'Nextjs Server Default Cache Policy', + comment: 'Nextjs Server Cache Policy', ...this.props.overrides?.serverCachePolicyProps, }); + const responseHeadersPolicy = new ResponseHeadersPolicy(this, 'ServerResponseHeadersPolicy', { + customHeadersBehavior: { + customHeaders: [ + { + header: 'cache-control', + override: false, + // MDN Cache-Control Use Case: Up-to-date contents always + // @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#up-to-date_contents_always + value: `no-cache`, + }, + ], + }, + securityHeadersBehavior: this.commonSecurityHeadersBehavior, + comment: 'Nextjs Server Response Headers Policy', + ...this.props.overrides?.serverResponseHeadersPolicyProps, + }); return { ...this.commonBehaviorOptions, origin, @@ -275,7 +290,7 @@ export class NextjsDistribution extends Construct { cachePolicy, edgeLambdas: this.edgeLambdas.length ? this.edgeLambdas : undefined, functionAssociations: this.createCloudFrontFnAssociations(), - responseHeadersPolicy: ResponseHeadersPolicy.SECURITY_HEADERS, + responseHeadersPolicy, ...this.props.overrides?.serverBehaviorOptions, }; } @@ -312,9 +327,25 @@ export class NextjsDistribution extends Construct { minTtl: Duration.days(0), enableAcceptEncodingBrotli: true, enableAcceptEncodingGzip: true, - comment: 'Nextjs Image Default Cache Policy', + comment: 'Nextjs Image Cache Policy', ...this.props.overrides?.imageCachePolicyProps, }); + const responseHeadersPolicy = new ResponseHeadersPolicy(this, 'ImageResponseHeadersPolicy', { + customHeadersBehavior: { + customHeaders: [ + { + header: 'cache-control', + override: false, + // MDN Cache-Control Use Case: Up-to-date contents always + // @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#up-to-date_contents_always + value: `no-cache`, + }, + ], + }, + securityHeadersBehavior: this.commonSecurityHeadersBehavior, + comment: 'Nextjs Image Response Headers Policy', + ...this.props.overrides?.imageResponseHeadersPolicyProps, + }); return { ...this.commonBehaviorOptions, origin, @@ -323,7 +354,7 @@ export class NextjsDistribution extends Construct { cachePolicy, originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER, edgeLambdas: this.edgeLambdas, - responseHeadersPolicy: ResponseHeadersPolicy.SECURITY_HEADERS, + responseHeadersPolicy, ...this.props.overrides?.imageBehaviorOptions, }; }