CDK Security and Compliance
Outlines security and compliance best practices for AWS CDK applications. Security is a critical aspect of infrastructure as code, and following these guidelines will help ensure that your CDK applications are secure, compliant, and follow AWS best practices.
CDK
TypeScript
@cremich
Author
Submitted on April 11, 2025
# Security and Compliance ## Rules ### 1. IAM Best Practices #### 1.1 Least Privilege Principle - Grant only the permissions required for a specific task - Avoid using wildcard permissions (`*`) in IAM policies - Regularly review and audit IAM permissions ```typescript // ❌ Bad: Overly permissive policy const role = new iam.Role(this, "LambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), }); role.addToPolicy( new iam.PolicyStatement({ actions: ["s3:*"], resources: ["*"], }) ); // ✅ Good: Least privilege policy const role = new iam.Role(this, "LambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), }); role.addToPolicy( new iam.PolicyStatement({ actions: ["s3:GetObject", "s3:ListBucket"], resources: [`arn:aws:s3:::${inputBucket.bucketName}`, `arn:aws:s3:::${inputBucket.bucketName}/*`], }) ); ``` #### 1.2 Use IAM Roles for Services - Use IAM roles instead of access keys for service-to-service authentication - Configure service roles with appropriate permissions - Use managed policies when appropriate, but prefer custom policies for more control ```typescript // ✅ Good: Using IAM roles for services const lambdaRole = new iam.Role(this, "LambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), }); // Add specific permissions lambdaRole.addToPolicy( new iam.PolicyStatement({ actions: ["dynamodb:GetItem", "dynamodb:PutItem"], resources: [table.tableArn], }) ); // Create Lambda function with role const lambdaFunction = new lambda.Function(this, "Function", { runtime: lambda.Runtime.NODEJS_18_X, handler: "index.handler", code: lambda.Code.fromAsset("lambda"), role: lambdaRole, }); ``` #### 1.3 Temporary Credentials - Use temporary credentials instead of long-term access keys - Implement credential rotation for any long-term credentials - Set appropriate expiration times for temporary credentials ```typescript // ✅ Good: Using temporary credentials for Lambda functions const lambdaRole = new iam.Role(this, "LambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), maxSessionDuration: Duration.hours(1), // Limit session duration }); // Lambda function with role const lambdaFunction = new lambda.Function(this, "Function", { runtime: lambda.Runtime.NODEJS_18_X, handler: "index.handler", code: lambda.Code.fromAsset("lambda"), role: lambdaRole, }); ``` ### 2. Data Protection #### 2.1 Encryption at Rest - Enable encryption for all storage services - Use customer managed KMS keys for sensitive data - Configure appropriate key rotation policies ```typescript // ✅ Good: Encryption for S3 buckets const key = new kms.Key(this, "DataBucketKey", { enableKeyRotation: true, description: "KMS key for data bucket encryption", }); const bucket = new s3.Bucket(this, "DataBucket", { encryption: s3.BucketEncryption.KMS, encryptionKey: key, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, enforceSSL: true, }); ``` #### 2.2 Encryption in Transit - Enforce HTTPS for all external communications - Use TLS 1.2 or later for all encrypted connections - Configure appropriate security policies for CloudFront distributions ```typescript // ✅ Good: Enforcing HTTPS for CloudFront const distribution = new cloudfront.Distribution(this, "Distribution", { defaultBehavior: { origin: new origins.S3Origin(bucket), viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD, }, minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, }); // ✅ Good: Enforcing SSL for S3 bucket const secureBucket = new s3.Bucket(this, "SecureBucket", { enforceSSL: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); ``` #### 2.3 Secrets Management - Use AWS Secrets Manager or Parameter Store for secrets - Don't hardcode sensitive information in CDK code - Implement appropriate access controls for secrets ```typescript // ❌ Bad: Hardcoded secrets const api = new apigateway.RestApi(this, "Api", { defaultMethodOptions: { apiKeyRequired: true, }, }); const apiKey = new apigateway.ApiKey(this, "ApiKey", { apiKeyName: "MyApiKey", value: "hardcoded-api-key-value", // Hardcoded secret }); // ✅ Good: Using Secrets Manager const apiKeySecret = secretsmanager.Secret.fromSecretNameV2(this, "ApiKeySecret", "api-key-secret"); const api = new apigateway.RestApi(this, "Api", { defaultMethodOptions: { apiKeyRequired: true, }, }); const apiKey = new apigateway.ApiKey(this, "ApiKey", { apiKeyName: "MyApiKey", }); // Use the secret in a Lambda function const lambdaFunction = new lambda.Function(this, "Function", { runtime: lambda.Runtime.NODEJS_18_X, handler: "index.handler", code: lambda.Code.fromAsset("lambda"), environment: { API_KEY_SECRET_ARN: apiKeySecret.secretArn, }, }); // Grant the Lambda function permission to read the secret apiKeySecret.grantRead(lambdaFunction); ``` ### 3. Network Security #### 3.1 VPC Configuration - Use private subnets for resources that don't need internet access - Configure appropriate security groups with least privilege - Use VPC endpoints for AWS services when possible ```typescript // ✅ Good: Secure VPC configuration const vpc = new ec2.Vpc(this, "ApplicationVpc", { maxAzs: 2, subnetConfiguration: [ { name: "private", subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24, }, { name: "public", subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, ], }); // Create VPC endpoints for AWS services new ec2.InterfaceVpcEndpoint(this, "SecretsManagerEndpoint", { vpc, service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, privateDnsEnabled: true, subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // Lambda function in VPC const lambdaFunction = new lambda.Function(this, "Function", { runtime: lambda.Runtime.NODEJS_18_X, handler: "index.handler", code: lambda.Code.fromAsset("lambda"), vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); ``` #### 3.2 Security Groups - Configure security groups with least privilege - Allow only necessary inbound and outbound traffic - Use security group references instead of CIDR ranges when possible ```typescript // ✅ Good: Secure security group configuration const lambdaSecurityGroup = new ec2.SecurityGroup(this, "LambdaSecurityGroup", { vpc, description: "Security group for Lambda function", allowAllOutbound: false, // Restrict outbound traffic }); // Allow only necessary outbound traffic lambdaSecurityGroup.addEgressRule( ec2.Peer.ipv4("10.0.0.0/16"), ec2.Port.tcp(443), "Allow HTTPS traffic to internal services" ); // Database security group const dbSecurityGroup = new ec2.SecurityGroup(this, "DbSecurityGroup", { vpc, description: "Security group for database", allowAllOutbound: false, }); // Allow traffic from Lambda to database using security group reference dbSecurityGroup.addIngressRule( ec2.Peer.securityGroupId(lambdaSecurityGroup.securityGroupId), ec2.Port.tcp(5432), "Allow traffic from Lambda function" ); ``` #### 3.3 WAF Configuration - Configure AWS WAF for public-facing applications - Implement appropriate rule sets for common attack vectors - Enable logging for WAF events ```typescript // ✅ Good: WAF configuration for CloudFront const wafLoggingBucket = new s3.Bucket(this, "WafLoggingBucket", { encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, enforceSSL: true, }); const wafAcl = new wafv2.CfnWebACL(this, "WebAcl", { defaultAction: { allow: {} }, scope: "CLOUDFRONT", visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "WebAclMetric", sampledRequestsEnabled: true, }, rules: [ { name: "AWSManagedRulesCommonRuleSet", priority: 0, statement: { managedRuleGroupStatement: { name: "AWSManagedRulesCommonRuleSet", vendorName: "AWS", }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "AWSManagedRulesCommonRuleSetMetric", sampledRequestsEnabled: true, }, }, { name: "RateLimitRule", priority: 1, statement: { rateBasedStatement: { limit: 1000, aggregateKeyType: "IP", }, }, action: { block: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "RateLimitRuleMetric", sampledRequestsEnabled: true, }, }, ], }); // Configure logging new wafv2.CfnLoggingConfiguration(this, "WafLogging", { resourceArn: wafAcl.attrArn, logDestinationConfigs: [wafLoggingBucket.bucketArn], }); // Associate WAF with CloudFront distribution const distribution = new cloudfront.Distribution(this, "Distribution", { defaultBehavior: { origin: new origins.S3Origin(bucket), viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }, webAclId: wafAcl.attrArn, }); ``` ### 4. Compliance Checks #### 4.1 CDK Nag - Use CDK Nag to check for security issues in CDK applications - Configure appropriate rule packs for your compliance requirements - Address or suppress findings with justification ```typescript // ✅ Good: Using CDK Nag for compliance checks import { App, Stack } from "aws-cdk-lib"; import { Aspects } from "aws-cdk-lib"; import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; const app = new App(); const stack = new Stack(app, "MyStack"); // Add resources to the stack // ... // Apply CDK Nag checks Aspects.of(app).add(new AwsSolutionsChecks()); // Suppress specific findings with justification NagSuppressions.addStackSuppressions(stack, [ { id: "AwsSolutions-IAM4", reason: "This role requires managed policies for AWS service integration", }, { id: "AwsSolutions-IAM5", reason: "This policy requires wildcard permissions for specific use case", appliesTo: ["Resource::*"], }, ]); ``` #### 4.2 Security Testing - Implement security testing in CI/CD pipelines - Use tools like cdk-nag, cfn-lint, and cfn-guard - Fail builds on security issues ```typescript // package.json { "scripts": { "build": "tsc", "test": "jest", "security-check": "npx cdk-nag", "lint": "eslint --ext .js,.ts .", "ci": "npm run build && npm run test && npm run security-check && npm run lint" } } ``` #### 4.3 Compliance Documentation - Document compliance requirements and how they are addressed - Maintain evidence of compliance checks - Regularly review and update compliance documentation ```typescript // ✅ Good: Documenting compliance /** * This stack implements the following compliance requirements: * - PCI DSS 3.2.1 Requirement 8.2.1: Using strong cryptography for secrets * - GDPR Article 32: Implementing appropriate security measures * - SOC 2 CC6.1: Restricting access to authorized users */ export class CompliantStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Implementation of compliant resources // ... } } ``` ### 5. Service-Specific Security #### 5.1 Lambda Security - Configure appropriate IAM roles with least privilege - Set appropriate timeout and memory settings - Implement proper error handling and logging ```typescript // ✅ Good: Secure Lambda configuration const lambdaRole = new iam.Role(this, "LambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), }); // Add specific permissions lambdaRole.addToPolicy( new iam.PolicyStatement({ actions: ["dynamodb:GetItem", "dynamodb:PutItem"], resources: [table.tableArn], }) ); // Create Lambda function with secure configuration const lambdaFunction = new lambda.Function(this, "Function", { runtime: lambda.Runtime.NODEJS_18_X, handler: "index.handler", code: lambda.Code.fromAsset("lambda"), role: lambdaRole, environment: { TABLE_NAME: table.tableName, LOG_LEVEL: "INFO", }, tracing: lambda.Tracing.ACTIVE, // Enable X-Ray tracing timeout: Duration.seconds(30), memorySize: 256, logRetention: logs.RetentionDays.ONE_WEEK, }); ``` #### 5.2 API Gateway Security - Configure appropriate authentication and authorization - Implement request validation and throttling - Enable logging and monitoring ```typescript // ✅ Good: Secure API Gateway configuration const userPool = new cognito.UserPool(this, "UserPool", { selfSignUpEnabled: false, passwordPolicy: { minLength: 12, requireLowercase: true, requireUppercase: true, requireDigits: true, requireSymbols: true, }, advancedSecurityMode: cognito.AdvancedSecurityMode.ENFORCED, }); const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, "Authorizer", { cognitoUserPools: [userPool], }); const api = new apigateway.RestApi(this, "Api", { deployOptions: { stageName: "prod", loggingLevel: apigateway.MethodLoggingLevel.INFO, dataTraceEnabled: false, metricsEnabled: true, throttlingRateLimit: 1000, throttlingBurstLimit: 500, }, defaultMethodOptions: { authorizationType: apigateway.AuthorizationType.COGNITO, authorizer, }, }); // Add resources and methods const items = api.root.addResource("items"); items.addMethod("GET", new apigateway.LambdaIntegration(lambdaFunction), { requestValidatorOptions: { validateRequestParameters: true, }, requestParameters: { "method.request.querystring.limit": false, }, }); ``` #### 5.3 S3 Security - Configure appropriate bucket policies and access controls - Enable encryption and versioning for sensitive data - Set up lifecycle rules for cost optimization ```typescript // ✅ Good: Secure S3 bucket configuration const bucket = new s3.Bucket(this, "DataBucket", { bucketName: `${props.appName}-${props.environment}-data-${this.account.substring(0, 6)}`, encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, versioned: true, enforceSSL: true, removalPolicy: props.environment === "prod" ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY, lifecycleRules: [ { id: "TransitionToInfrequentAccess", transitions: [ { storageClass: s3.StorageClass.INFREQUENT_ACCESS, transitionAfter: Duration.days(30), }, { storageClass: s3.StorageClass.GLACIER, transitionAfter: Duration.days(90), }, ], }, { id: "DeleteOldVersions", noncurrentVersionExpiration: Duration.days(30), enabled: true, }, ], }); // Add bucket policy bucket.addToResourcePolicy( new iam.PolicyStatement({ actions: ["s3:GetObject"], resources: [bucket.arnForObjects("*")], principals: [new iam.ServicePrincipal("cloudfront.amazonaws.com")], conditions: { StringEquals: { "AWS:SourceArn": `arn:aws:cloudfront::${this.account}:distribution/${distribution.distributionId}`, }, }, }) ); ``` ## Examples ### Secure Serverless API ```typescript export class SecureServerlessApi extends Construct { public readonly apiEndpoint: string; constructor(scope: Construct, id: string, props: SecureServerlessApiProps) { super(scope, id); // Create KMS key for encryption const key = new kms.Key(this, "DataKey", { enableKeyRotation: true, description: "KMS key for data encryption", }); // Create secure DynamoDB table const table = new dynamodb.Table(this, "Table", { partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }, encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED, encryptionKey: key, pointInTimeRecovery: true, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: props.environment === "prod" ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY, }); // Create Lambda role with least privilege const lambdaRole = new iam.Role(this, "LambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), }); // Add specific permissions lambdaRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")); table.grantReadWriteData(lambdaRole); // Create Lambda function const lambdaFunction = new lambda.Function(this, "Function", { runtime: lambda.Runtime.NODEJS_18_X, handler: "index.handler", code: lambda.Code.fromAsset(props.handlerPath), role: lambdaRole, environment: { TABLE_NAME: table.tableName, ENVIRONMENT: props.environment, }, tracing: lambda.Tracing.ACTIVE, logRetention: logs.RetentionDays.ONE_WEEK, }); // Create Cognito user pool for authentication const userPool = new cognito.UserPool(this, "UserPool", { selfSignUpEnabled: props.allowSelfSignUp ?? false, passwordPolicy: { minLength: 12, requireLowercase: true, requireUppercase: true, requireDigits: true, requireSymbols: true, }, advancedSecurityMode: cognito.AdvancedSecurityMode.ENFORCED, removalPolicy: props.environment === "prod" ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY, }); const userPoolClient = new cognito.UserPoolClient(this, "UserPoolClient", { userPool, authFlows: { userPassword: true, userSrp: true, }, preventUserExistenceErrors: true, }); // Create API Gateway with Cognito authorizer const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, "Authorizer", { cognitoUserPools: [userPool], }); const api = new apigateway.RestApi(this, "Api", { restApiName: props.apiName, description: `Secure API for ${props.apiName}`, deployOptions: { stageName: props.environment, loggingLevel: apigateway.MethodLoggingLevel.INFO, dataTraceEnabled: props.environment !== "prod", metricsEnabled: true, throttlingRateLimit: props.rateLimit || 1000, throttlingBurstLimit: props.burstLimit || 500, }, defaultCorsPreflightOptions: { allowOrigins: props.allowedOrigins || ["*"], allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["Content-Type", "Authorization"], maxAge: Duration.days(1), }, }); // Add resources and methods const items = api.root.addResource("items"); items.addMethod("GET", new apigateway.LambdaIntegration(lambdaFunction), { authorizationType: apigateway.AuthorizationType.COGNITO, authorizer, requestValidatorOptions: { validateRequestParameters: true, }, requestParameters: { "method.request.querystring.limit": false, "method.request.querystring.nextToken": false, }, }); items.addMethod("POST", new apigateway.LambdaIntegration(lambdaFunction), { authorizationType: apigateway.AuthorizationType.COGNITO, authorizer, requestValidatorOptions: { validateRequestBody: true, }, }); const item = items.addResource("{id}"); item.addMethod("GET", new apigateway.LambdaIntegration(lambdaFunction), { authorizationType: apigateway.AuthorizationType.COGNITO, authorizer, }); item.addMethod("PUT", new apigateway.LambdaIntegration(lambdaFunction), { authorizationType: apigateway.AuthorizationType.COGNITO, authorizer, requestValidatorOptions: { validateRequestBody: true, }, }); item.addMethod("DELETE", new apigateway.LambdaIntegration(lambdaFunction), { authorizationType: apigateway.AuthorizationType.COGNITO, authorizer, }); // Set up CloudWatch alarms const alarmTopic = new sns.Topic(this, "AlarmTopic", { displayName: `${props.apiName}-${props.environment}-alarms`, }); // Add email subscription if provided if (props.alarmEmail) { alarmTopic.addSubscription(new subscriptions.EmailSubscription(props.alarmEmail)); } // API Gateway 5xx error alarm new cloudwatch.Alarm(this, "ApiGateway5xxAlarm", { metric: new cloudwatch.Metric({ namespace: "AWS/ApiGateway", metricName: "5XXError", dimensionsMap: { ApiName: api.restApiName, Stage: props.environment, }, statistic: "Sum", period: Duration.minutes(5), }), threshold: 5, evaluationPeriods: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: `API Gateway ${api.restApiName} has 5XX errors`, actionsEnabled: true, alarmActions: [alarmTopic.topicArn], }); // Lambda error alarm new cloudwatch.Alarm(this, "LambdaErrorAlarm", { metric: new cloudwatch.Metric({ namespace: "AWS/Lambda", metricName: "Errors", dimensionsMap: { FunctionName: lambdaFunction.functionName, }, statistic: "Sum", period: Duration.minutes(5), }), threshold: 5, evaluationPeriods: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: `Lambda function ${lambdaFunction.functionName} has errors`, actionsEnabled: true, alarmActions: [alarmTopic.topicArn], }); this.apiEndpoint = api.url; } } ``` ### Secure CloudFront Distribution ```typescript export class SecureCloudFrontDistribution extends Construct { public readonly distributionDomainName: string; constructor(scope: Construct, id: string, props: SecureCloudFrontDistributionProps) { super(scope, id); // Create WAF ACL const wafAcl = new wafv2.CfnWebACL(this, 'WebAcl', { defaultAction: { allow: {} }, scope: 'CLOUDFRONT', visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'WebAclMetric', sampledRequestsEnabled: true, }, rules: [ { name: 'AWSManagedRulesCommonRuleSet', priority: 0, statement: { managedRuleGroupStatement: { name: 'AWSManagedRulesCommonRuleSet', vendorName: 'AWS', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'AWSManagedRulesCommonRuleSetMetric', sampledRequestsEnabled: true, }, }, { name: 'RateLimitRule', priority: 1, statement: { rateBasedStatement: { limit: props.rateLimit || 1000, aggregateKeyType: 'IP', }, }, action: { block: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'RateLimitRuleMetric', sampledRequestsEnabled: true, }, }, ], }); // Create log bucket const logBucket = new s3.Bucket(this, 'LogBucket', { encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, enforceSSL: true, lifecycleRules: [ { id: 'DeleteLogsAfter30Days', expiration: Duration.days(30), }, ], }); // Configure WAF logging new wafv2.CfnLoggingConfiguration(this, 'WafLogging', { resourceArn: wafAcl.attrArn, logDestinationConfigs: [logBucket.bucketArn], }); // Create response headers policy for security headers const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'ResponseHeadersPolicy', { responseHeadersPolicyName: `${id}-security-headers`, corsBehavior: { accessControlAllowCredentials: false, accessControlAllowHeaders: ['*'], accessControlAllowMethods: ['GET', 'HEAD', 'OPTIONS'], accessControlAllowOrigins: props.allowedOrigins || ['*'], originOverride: true, }, securityHeadersBehavior: { contentSecurityPolicy: { contentSecurityPolicy: "default-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'", override: true, }, contentTypeOptions: { override: true }, frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true, }, referrerPolicy: { referrerPolicy: cloudfront.HeadersReferrerPolicy.SAME_ORIGIN, override: true, }, strictTransportSecurity: { accessControlMaxAge: Duration.days(365), includeSubdomains: true, preload: true, override: true, }, xssProtection: { protection: true, modeBlock: true, override: true, }, }, }); // Create distribution const distribution = new cloudfront.Distribution(this, 'Distribution', { defaultBehavior: { origin: new origins.HttpOrigin(props.originDomain), viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS, cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, responseHeadersPolicy, }, priceClass: props.priceClass || cloudfront ```