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
```
CDK Security and Compliance - Project Rule for Amazon Q Developer