import * as cdk from 'aws-cdk-lib'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; import * as cloudfrontOrigins from 'aws-cdk-lib/aws-cloudfront-origins'; import { Construct } from 'constructs'; import { EnvConfig } from './config'; export interface FrontendStackProps extends cdk.StackProps { config: EnvConfig; } export class FrontendStack extends cdk.Stack { constructor(scope: Construct, id: string, props: FrontendStackProps) { super(scope, id, props); const { config } = props; // Fix: INF-M5 — enforce HTTPS-only access on S3 bucket const siteBucket = new s3.Bucket(this, 'SiteBucket', { bucketName: `proposal-system-web-${this.account}${config.stackSuffix}`, encryption: s3.BucketEncryption.S3_MANAGED, enforceSSL: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); const distribution = new cloudfront.Distribution(this, 'Distribution', { comment: `proposal-system-web${config.stackSuffix}`, defaultBehavior: { origin: cloudfrontOrigins.S3BucketOrigin.withOriginAccessControl(siteBucket), viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, responseHeadersPolicy: cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS, }, defaultRootObject: 'index.html', errorResponses: [ { httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: cdk.Duration.seconds(0), }, { httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: cdk.Duration.seconds(0), }, ], minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, }); new cdk.CfnOutput(this, 'DistributionId', { value: distribution.distributionId }); new cdk.CfnOutput(this, 'DistributionDomainName', { value: distribution.distributionDomainName }); new cdk.CfnOutput(this, 'SiteBucketName', { value: siteBucket.bucketName }); } }