packages/blueprints/gen-ai-chatbot/static-assets/chatbot-genai-cdk/lib/chatbot-genai-cdk-stack.ts (287 lines of code) (raw):

import { CfnOutput, Duration, RemovalPolicy, StackProps } from "aws-cdk-lib"; import { BlockPublicAccess, Bucket, BucketEncryption, IBucket, HttpMethods, ObjectOwnership, } from "aws-cdk-lib/aws-s3"; import { Construct } from "constructs"; import { Auth } from "./constructs/auth"; import { Api } from "./constructs/api"; import { Database } from "./constructs/database"; import { Frontend } from "./constructs/frontend"; import { WebSocket } from "./constructs/websocket"; import * as cdk from "aws-cdk-lib"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import { Embedding } from "./constructs/embedding"; import { VectorStore } from "./constructs/vectorstore"; import { UsageAnalysis } from "./constructs/usage-analysis"; import { TIdentityProvider, identityProvider } from "./utils/identity-provider"; import { ApiPublishCodebuild } from "./constructs/api-publish-codebuild"; import { WebAclForPublishedApi } from "./constructs/webacl-for-published-api"; import { CronScheduleProps, createCronSchedule } from "./utils/cron-schedule"; import { NagSuppressions } from "cdk-nag"; export interface ChatbotGenAiCdkStackProps extends StackProps { readonly bedrockRegion: string; readonly webAclId: string; readonly identityProviders: TIdentityProvider[]; readonly userPoolDomainPrefix: string; readonly publishedApiAllowedIpV4AddressRanges: string[]; readonly publishedApiAllowedIpV6AddressRanges: string[]; readonly allowedSignUpEmailDomains: string[]; readonly autoJoinUserGroups: string[]; readonly rdsSchedules: CronScheduleProps; readonly enableMistral: boolean; readonly embeddingContainerVcpu: number; readonly embeddingContainerMemory: number; readonly selfSignUpEnabled: boolean; } export class ChatbotGenAiCdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props: ChatbotGenAiCdkStackProps) { super(scope, id, { description: "Bedrock Chat Stack", ...props, }); const cronSchedule = createCronSchedule(props.rdsSchedules); const vpc = new ec2.Vpc(this, "VPC", {}); vpc.publicSubnets.forEach((subnet) => { (subnet.node.defaultChild as ec2.CfnSubnet).mapPublicIpOnLaunch = false; }); const vectorStore = new VectorStore(this, "VectorStore", { vpc: vpc, rdsSchedule: cronSchedule, }); const idp = identityProvider(props.identityProviders); const accessLogBucket = new Bucket(this, "AccessLogBucket", { encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, enforceSSL: true, removalPolicy: RemovalPolicy.DESTROY, objectOwnership: ObjectOwnership.OBJECT_WRITER, autoDeleteObjects: true, }); // CodeBuild is used for api publication const apiPublishCodebuild = new ApiPublishCodebuild( this, "ApiPublishCodebuild", { accessLogBucket, dbSecret: vectorStore.secret, } ); const frontend = new Frontend(this, "Frontend", { accessLogBucket, webAclId: props.webAclId, env: { account: this.account, region: this.region, }, enableMistral: props.enableMistral, }); const auth = new Auth(this, "Auth", { origin: frontend.getOrigin(), userPoolDomainPrefixKey: props.userPoolDomainPrefix, idp, allowedSignUpEmailDomains: props.allowedSignUpEmailDomains, autoJoinUserGroups: props.autoJoinUserGroups, selfSignUpEnabled: props.selfSignUpEnabled, }); const largeMessageBucket = new Bucket(this, "LargeMessageBucket", { encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, enforceSSL: true, removalPolicy: RemovalPolicy.DESTROY, objectOwnership: ObjectOwnership.OBJECT_WRITER, autoDeleteObjects: true, }); const documentBucket = new Bucket(this, "DocumentBucket", { encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, enforceSSL: true, removalPolicy: RemovalPolicy.DESTROY, objectOwnership: ObjectOwnership.OBJECT_WRITER, autoDeleteObjects: true, versioned: true, serverAccessLogsBucket: new Bucket(this, "DdbBucketLogs", { encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, enforceSSL: true, removalPolicy: RemovalPolicy.DESTROY, lifecycleRules: [ { enabled: true, expiration: Duration.days(3653), id: "ExpireAfterTenYears", }, ], versioned: true, serverAccessLogsPrefix: "self/", }), serverAccessLogsPrefix: "DocumentBucket", }); const database = new Database(this, "Database", { // Enable PITR to export data to s3 pointInTimeRecovery: true, }); const usageAnalysis = new UsageAnalysis(this, "UsageAnalysis", { accessLogBucket, sourceDatabase: database, }); const backendApi = new Api(this, "BackendApi", { vpc, database: database.table, auth, bedrockRegion: props.bedrockRegion, tableAccessRole: database.tableAccessRole, dbSecrets: vectorStore.secret, documentBucket, apiPublishProject: apiPublishCodebuild.project, usageAnalysis, largeMessageBucket, enableMistral: props.enableMistral, }); documentBucket.grantReadWrite(backendApi.handler); // For streaming response const websocket = new WebSocket(this, "WebSocket", { accessLogBucket, vpc, dbSecrets: vectorStore.secret, database: database.table, tableAccessRole: database.tableAccessRole, websocketSessionTable: database.websocketSessionTable, auth, bedrockRegion: props.bedrockRegion, largeMessageBucket, }); frontend.buildViteApp({ backendApiEndpoint: backendApi.api.apiEndpoint, webSocketApiEndpoint: websocket.apiEndpoint, userPoolDomainPrefix: props.userPoolDomainPrefix, enableMistral: props.enableMistral, auth, idp, }); documentBucket.addCorsRule({ allowedMethods: [HttpMethods.PUT], allowedOrigins: [frontend.getOrigin(), "http://localhost:5173", "*"], allowedHeaders: ["*"], maxAge: 3000, }); const embedding = new Embedding(this, "Embedding", { vpc, bedrockRegion: props.bedrockRegion, database: database.table, dbSecrets: vectorStore.secret, tableAccessRole: database.tableAccessRole, documentBucket, embeddingContainerVcpu: props.embeddingContainerVcpu, embeddingContainerMemory: props.embeddingContainerMemory, }); documentBucket.grantRead(embedding.container.taskDefinition.taskRole); vectorStore.allowFrom(embedding.taskSecurityGroup); vectorStore.allowFrom(embedding.removalHandler); vectorStore.allowFrom(backendApi.handler); vectorStore.allowFrom(websocket.handler); // WebAcl for published API const webAclForPublishedApi = new WebAclForPublishedApi( this, this.disambiguateIdentifier("WebAclForPublishedApi"), { allowedIpV4AddressRanges: props.publishedApiAllowedIpV4AddressRanges, allowedIpV6AddressRanges: props.publishedApiAllowedIpV6AddressRanges, } ); new CfnOutput(this, "DocumentBucketName", { value: documentBucket.bucketName, }); new CfnOutput(this, "FrontendURL", { value: frontend.getOrigin(), }); // Outputs for API publication new CfnOutput(this, "PublishedApiWebAclArn", { value: webAclForPublishedApi.webAclArn, exportName: this.disambiguateIdentifier("PublishedApiWebAclArn"), }); new CfnOutput(this, "VpcId", { value: vpc.vpcId, exportName: this.disambiguateIdentifier("BedrockClaudeChatVpcId"), }); new CfnOutput(this, "AvailabilityZone0", { value: vpc.availabilityZones[0], exportName: this.disambiguateIdentifier( "BedrockClaudeChatAvailabilityZone0" ), }); new CfnOutput(this, "AvailabilityZone1", { value: vpc.availabilityZones[1], exportName: this.disambiguateIdentifier( "BedrockClaudeChatAvailabilityZone1" ), }); new CfnOutput(this, "PublicSubnetId0", { value: vpc.publicSubnets[0].subnetId, exportName: this.disambiguateIdentifier( "BedrockClaudeChatPublicSubnetId0" ), }); new CfnOutput(this, "PublicSubnetId1", { value: vpc.publicSubnets[1].subnetId, exportName: this.disambiguateIdentifier( "BedrockClaudeChatPublicSubnetId1" ), }); new CfnOutput(this, "PrivateSubnetId0", { value: vpc.privateSubnets[0].subnetId, exportName: this.disambiguateIdentifier( "BedrockClaudeChatPrivateSubnetId0" ), }); new CfnOutput(this, "PrivateSubnetId1", { value: vpc.privateSubnets[1].subnetId, exportName: this.disambiguateIdentifier( "BedrockClaudeChatPrivateSubnetId1" ), }); new CfnOutput(this, "DbConfigSecretArn", { value: vectorStore.secret.secretArn, exportName: this.disambiguateIdentifier( "BedrockClaudeChatDbConfigSecretArn" ), }); new CfnOutput(this, "DbConfigHostname", { value: vectorStore.cluster.clusterEndpoint.hostname, exportName: this.disambiguateIdentifier( "BedrockClaudeChatDbConfigHostname" ), }); new CfnOutput(this, "DbConfigPort", { value: vectorStore.cluster.clusterEndpoint.port.toString(), exportName: this.disambiguateIdentifier("BedrockClaudeChatDbConfigPort"), }); new CfnOutput(this, "ConversationTableName", { value: database.table.tableName, exportName: this.disambiguateIdentifier( "BedrockClaudeChatConversationTableName" ), }); new CfnOutput(this, "TableAccessRoleArn", { value: database.tableAccessRole.roleArn, exportName: this.disambiguateIdentifier( "BedrockClaudeChatTableAccessRoleArn" ), }); new CfnOutput(this, "DbSecurityGroupId", { value: vectorStore.securityGroup.securityGroupId, exportName: this.disambiguateIdentifier( "BedrockClaudeChatDbSecurityGroupId" ), }); new CfnOutput(this, "LargeMessageBucketName", { value: largeMessageBucket.bucketName, exportName: this.disambiguateIdentifier( "BedrockClaudeChatLargeMessageBucketName" ), }); } private disambiguateIdentifier(identifier: string) { const disambiguator = this.node.tryGetContext("stackDisambiguator"); return disambiguator ? `${identifier}-${disambiguator}` : identifier; } }