diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts index 3c4e561fd463c..e16230fd49650 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts @@ -114,13 +114,36 @@ export class ResponseHeadersPolicy extends Resource implements IResponseHeadersP maxLength: 128, }); + let securityHeadersBehavior = props.securityHeadersBehavior; + let customHeadersBehavior = props.customHeadersBehavior; + + if (securityHeadersBehavior?.contentSecurityPolicy?.reportOnly) { + const reportOnlyCSPHeader = { + header: 'Content-Security-Policy-Report-Only', + value: securityHeadersBehavior.contentSecurityPolicy.contentSecurityPolicy, + override: true, + }; + securityHeadersBehavior = { + ...securityHeadersBehavior, + contentSecurityPolicy: undefined, + }; + + if (!customHeadersBehavior) { + customHeadersBehavior = { + customHeaders: [], + } + } + // TODO: log a warning if custom headers already contains CSP-Report-Only header? + customHeadersBehavior.customHeaders.push(reportOnlyCSPHeader); + } + const resource = new CfnResponseHeadersPolicy(this, 'Resource', { responseHeadersPolicyConfig: { name: responseHeadersPolicyName, comment: props.comment, corsConfig: props.corsBehavior ? this._renderCorsConfig(props.corsBehavior) : undefined, - customHeadersConfig: props.customHeadersBehavior ? this._renderCustomHeadersConfig(props.customHeadersBehavior) : undefined, - securityHeadersConfig: props.securityHeadersBehavior ? this._renderSecurityHeadersConfig(props.securityHeadersBehavior) : undefined, + customHeadersConfig: customHeadersBehavior ? this._renderCustomHeadersConfig(customHeadersBehavior) : undefined, + securityHeadersConfig: securityHeadersBehavior ? this._renderSecurityHeadersConfig(securityHeadersBehavior) : undefined, removeHeadersConfig: props.removeHeaders ? this._renderRemoveHeadersConfig(props.removeHeaders) : undefined, serverTimingHeadersConfig: props.serverTimingSamplingRate ? this._renderServerTimingHeadersConfig(props.serverTimingSamplingRate) : undefined, }, @@ -337,6 +360,11 @@ export interface ResponseHeadersContentSecurityPolicy { * received from the origin with the one specified in this response headers policy. */ readonly override: boolean; + + /** + * A Boolean that determines whether CloudFront includes the -Report-Only suffix in the Content-Security-Policy HTTP response header. + */ + readonly reportOnly?: boolean; } /** diff --git a/packages/aws-cdk-lib/aws-cloudfront/test/response-headers-policy.test.ts b/packages/aws-cdk-lib/aws-cloudfront/test/response-headers-policy.test.ts index 2950ec5373dea..ea878ad37ab06 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/test/response-headers-policy.test.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/test/response-headers-policy.test.ts @@ -180,4 +180,26 @@ describe('ResponseHeadersPolicy', () => { }, }); }); + + test('it respects CSP `reportOnly` flag by mapping to custom header', () => { + new ResponseHeadersPolicy(stack, 'ResponseHeadersPolicy', { + securityHeadersBehavior: { + contentSecurityPolicy: { contentSecurityPolicy: 'default-src https:;', override: true, reportOnly: true }, + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CloudFront::ResponseHeadersPolicy', { + ResponseHeadersPolicyConfig: { + CustomHeadersConfig: { + Items: [ + { + Header: 'Content-Security-Policy-Report-Only', + Value: 'default-src https:;', + Override: true, + }, + ] + }, + }, + }); + }) });