Skip to content

Commit

Permalink
Feature: add private chatbot deployment option
Browse files Browse the repository at this point in the history
  • Loading branch information
manuwaik authored and bigadsoleiman committed Jan 30, 2024
1 parent bb77eea commit ecf0234
Show file tree
Hide file tree
Showing 15 changed files with 642 additions and 107 deletions.
9 changes: 6 additions & 3 deletions bin/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ export function getConfig(): SystemConfig {
// Default config
return {
prefix: "",
/*vpc: {
vpcId: "vpc-00000000000000000",
createVpcEndpoints: true,
/* vpc: {
vpcId: "vpc-00000000000000000",
createVpcEndpoints: true,
},*/
privateWebsite: false,
certificate : "",
bedrock: {
enabled: true,
region: SupportedRegion.US_EAST_1,
Expand All @@ -32,6 +34,7 @@ export function getConfig(): SystemConfig {
kendra: {
enabled: false,
createIndex: false,
enterprise: false
},
},
embeddingsModels: [
Expand Down
43 changes: 36 additions & 7 deletions cli/magic-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SupportedRegion,
SupportedSageMakerModels,
SystemConfig,
SupportedBedrockRegion
} from "../lib/shared/types";
import { LIB_VERSION } from "./version.js";
import * as fs from "fs";
Expand Down Expand Up @@ -57,6 +58,9 @@ const embeddingModels = [
fs.readFileSync("./bin/config.json").toString("utf8")
);
options.prefix = config.prefix;
options.privateWebsite = config.privateWebsite;
options.certificate = config.certificate;
options.domain = config.domain;
options.bedrockEnable = config.bedrock?.enabled;
options.bedrockRegion = config.bedrock?.region;
options.bedrockRoleArn = config.bedrock?.roleArn;
Expand Down Expand Up @@ -113,6 +117,34 @@ async function processCreateOptions(options: any): Promise<void> {
initial: options.prefix,
askAnswered: false,
},
{
type: "confirm",
name: "privateWebsite",
message: "Do you want to deploy a private website? I.e only accessible in VPC",
initial:
options.privateWebsite ||
false,
},
{
type: "input",
name: "certificate",
message: "ACM certificate ARN",
initial:
options.certificate,
skip(): boolean {
return !(this as any).state.answers.privateWebsite;
},
},
{
type: "input",
name: "domain",
message: "Domain for private website",
initial:
options.domain,
skip(): boolean {
return !(this as any).state.answers.privateWebsite;
},
},
{
type: "confirm",
name: "bedrockEnable",
Expand All @@ -123,13 +155,7 @@ async function processCreateOptions(options: any): Promise<void> {
type: "select",
name: "bedrockRegion",
message: "Region where Bedrock is available",
choices: [
SupportedRegion.US_EAST_1,
SupportedRegion.US_WEST_2,
SupportedRegion.EU_CENTRAL_1,
SupportedRegion.AP_SOUTHEAST_1,
SupportedRegion.AP_NORTHEAST_1,
],
choices: Object.values(SupportedBedrockRegion),
initial: options.bedrockRegion ?? "us-east-1",
skip() {
return !(this as any).state.answers.bedrockEnable;
Expand Down Expand Up @@ -310,6 +336,9 @@ async function processCreateOptions(options: any): Promise<void> {
// Create the config object
const config = {
prefix: answers.prefix,
privateWebsite: answers.privateWebsite,
certificate: answers.certificate,
domain: answers.domain,
bedrock: answers.bedrockEnable
? {
enabled: answers.bedrockEnable,
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default defineConfig({
{
text: 'Documentation',
items: [
{ text: 'Private Chatbot', link: '/documentation/private-chatbot' },
{ text: 'Model Requirements', link: '/documentation/model-requirements' },
{ text: 'Inference Script', link: '/documentation/inference-script' },
{ text: 'Document Retrieval', link: '/documentation/retriever' },
Expand Down
28 changes: 28 additions & 0 deletions docs/documentation/private-chatbot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Private Chatbot

Allows the deployment of a private chatbot via the 'npm run create' CLI setup.

- VPC only accessible website with an Application Load Balancer in front of an S3 hosted website.
- Private Appsync APIs and Web Sockets
- VPC endpoints for AWS services
- Utilises a AWS Private CA certifice
- Utilises a Amazon Route 53 Private Hosted Zone and Domain


### Prerequisites: Private Chatbot Deployment
1. [AWS Private CA issued ACM certificate](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-private.html) for your chosen domain. (i.e. chatbot.example.org)
2. A Route 53 [Private Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html) (i.e. for example.org)

### During 'npm run create'
```shellsession
$ ✔ Do you want to deploy a private website? I.e only accessible in VPC (Y/n) ·
true
$ ✔ ACM certificate ARN ·
arn:aws:acm:us-east-1:1234567890:certificate/12345678-1234-1234-1234-12345678
$ ✔ Domain for private website ·
chatbot.example.org
```

### After Private Deployment:
1. In Route 53 [link the created VPC to the Private Hosted Zone (PHZ)](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zone-private-associate-vpcs.html)
2. In the PHZ, [add an "A Record"](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-elb-load-balancer.html) with your chosen subdomain (i.e. chatbot.example.org) that points to the website Application Load Balancer Alias.
1 change: 1 addition & 0 deletions docs/guide/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ You'll be prompted to configure the different aspects of the solution, such as:

- The LLMs or MLMs to enable (we support all models provided by Bedrock along with SageMaker hosted Idefics, FalconLite, Mistral and more to come)
- Setup of the RAG system: engine selection (i.e. Aurora w/ pgvector, OpenSearch, Kendra..) embeddings selection and more to come.
- Private Chatbot: Limit accessibility to website and backend to VPC.

When done, answer `Y` to create a new configuration.

Expand Down
33 changes: 33 additions & 0 deletions lib/aws-genai-llm-chatbot-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,5 +415,38 @@ export class AwsGenAILLMChatbotStack extends cdk.Stack {
reason: "Not yet upgraded from Python 3.11 to 3.12.",
},
]);

if (props.config.privateWebsite) {
NagSuppressions.addResourceSuppressionsByPath(
this,
[
`/${this.stackName}/UserInterface/PrivateWebsite/DescribeNetworkInterfaces-0/CustomResourcePolicy/Resource`,
`/${this.stackName}/UserInterface/PrivateWebsite/DescribeNetworkInterfaces-1/CustomResourcePolicy/Resource`,
`/${this.stackName}/UserInterface/PrivateWebsite/DescribeNetworkInterfaces-2/CustomResourcePolicy/Resource`,
`/${this.stackName}/UserInterface/PrivateWebsite/describeVpcEndpoints/CustomResourcePolicy/Resource`,
],
[
{
id: "AwsSolutions-IAM5",
reason:
"Custom Resource requires permissions to Describe VPC Endpoint Network Interfaces",
},
]
);
NagSuppressions.addResourceSuppressionsByPath(
this,
[
`/${this.stackName}/AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/Resource`
],
[
{
id: "AwsSolutions-IAM4",
reason:
"IAM role implicitly created by CDK.",
},
]
);

}
}
}
2 changes: 2 additions & 0 deletions lib/chatbot-api/appsync-ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class RealtimeResolvers extends Construct {
SNS_TOPIC_ARN: props.topic.topicArn,
},
layers: [props.shared.powerToolsLayer],
vpc: props.shared.vpc
});

const outgoingMessageHandler = new NodejsFunction(
Expand All @@ -58,6 +59,7 @@ export class RealtimeResolvers extends Construct {
environment: {
GRAPHQL_ENDPOINT: props.api.graphqlUrl,
},
vpc: props.shared.vpc
}
);

Expand Down
1 change: 1 addition & 0 deletions lib/chatbot-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class ChatBotApi extends Construct {
role: loggingRole,
},
xrayEnabled: true,
visibility: props.config.privateWebsite ? appsync.Visibility.PRIVATE : appsync.Visibility.GLOBAL
});

new ApiResolvers(this, "RestApi", {
Expand Down
85 changes: 84 additions & 1 deletion lib/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as logs from "aws-cdk-lib/aws-logs";
import { Construct } from "constructs";
import * as path from "path";
import { Layer } from "../layer";
import { SystemConfig } from "./types";
import { SystemConfig, SupportedBedrockRegion } from "./types";
import { SharedAssetBundler } from "./shared-asset-bundler";
import { NagSuppressions } from "cdk-nag";

Expand All @@ -30,6 +30,7 @@ export class Shared extends Construct {
readonly commonLayer: lambda.ILayerVersion;
readonly powerToolsLayer: lambda.ILayerVersion;
readonly sharedCode: SharedAssetBundler;
readonly s3vpcEndpoint: ec2.InterfaceVpcEndpoint;

constructor(scope: Construct, id: string, props: SharedProps) {
super(scope, id);
Expand Down Expand Up @@ -90,6 +91,8 @@ export class Shared extends Construct {
privateDnsEnabled: true,
open: true,
});

this.s3vpcEndpoint = s3vpcEndpoint;

s3vpcEndpoint.node.addDependency(s3GatewayEndpoint);

Expand All @@ -109,6 +112,86 @@ export class Shared extends Construct {
service: ec2.InterfaceVpcEndpointAwsService.SAGEMAKER_RUNTIME,
open: true,
});

}

if (props.config.privateWebsite) {
// Create VPC Endpoint for AppSync
vpc.addInterfaceEndpoint("AppSyncEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.APP_SYNC,
});

// Create VPC Endpoint for Lambda
vpc.addInterfaceEndpoint("LambdaEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.LAMBDA,
});

// Create VPC Endpoint for SNS
vpc.addInterfaceEndpoint("SNSEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.SNS,
});

// Create VPC Endpoint for Step Functions
vpc.addInterfaceEndpoint("StepFunctionsEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.STEP_FUNCTIONS,
});

// Create VPC Endpoint for SSM
vpc.addInterfaceEndpoint("SSMEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.SSM,
});

// Create VPC Endpoint for KMS
vpc.addInterfaceEndpoint("KMSEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.KMS,
});

// Create VPC Endpoint for Bedrock
if (props.config.bedrock?.enabled && Object.values(SupportedBedrockRegion).some(val => val === cdk.Stack.of(this).region)){
vpc.addInterfaceEndpoint("BedrockEndpoint", {
service: new ec2.InterfaceVpcEndpointService('com.amazonaws.'+cdk.Aws.REGION+'.bedrock-runtime', 443),
privateDnsEnabled: true
});
}

// Create VPC Endpoint for Kendra
if (props.config.rag.engines.kendra.enabled){
vpc.addInterfaceEndpoint("KendraEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.KENDRA,
});
}

// Create VPC Endpoint for RDS/Aurora
if (props.config.rag.engines.aurora.enabled) {
vpc.addInterfaceEndpoint("RDSEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.RDS,
});

// Create VPC Endpoint for RDS Data
vpc.addInterfaceEndpoint("RDSDataEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.RDS_DATA,
});
}

// Create VPC Endpoints needed for Aurora & Opensearch Indexing
if (props.config.rag.engines.aurora.enabled ||
props.config.rag.engines.opensearch.enabled) {
// Create VPC Endpoint for ECS
vpc.addInterfaceEndpoint("ECSEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.ECS,
});

// Create VPC Endpoint for Batch
vpc.addInterfaceEndpoint("BatchEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.BATCH,
});

// Create VPC Endpoint for EC2
vpc.addInterfaceEndpoint("EC2Endpoint", {
service: ec2.InterfaceVpcEndpointAwsService.EC2,
});
}

}

const configParameter = new ssm.StringParameter(this, "Config", {
Expand Down
11 changes: 11 additions & 0 deletions lib/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export enum SupportedRegion {
US_WEST_1 = "us-west-1",
US_WEST_2 = "us-west-2",
}

export enum SupportedBedrockRegion {
AP_NORTHEAST_1 = "ap-northeast-1",
AP_SOUTHEAST_1 = "ap-southeast-1",
EU_CENTRAL_1 = "eu-central-1",
US_EAST_1 = "us-east-1",
US_WEST_2 = "us-west-2",
}

export enum ModelInterface {
LangChain = "langchain",
Expand All @@ -65,6 +73,9 @@ export interface SystemConfig {
vpcId?: string;
createVpcEndpoints?: boolean;
};
certificate?: string;
domain?: string;
privateWebsite?: boolean;
bedrock?: {
enabled?: boolean;
region?: SupportedRegion;
Expand Down
Loading

0 comments on commit ecf0234

Please sign in to comment.