diff --git a/cdk.context.json b/cdk.context.json index 661656d..698ce8a 100644 --- a/cdk.context.json +++ b/cdk.context.json @@ -2,5 +2,50 @@ "hosted-zone:account=328440206208:domainName=seahaven.com:region=us-east-1": { "Id": "/hostedzone/Z06652411XKH89KTZD3XA", "Name": "seahaven.com." + }, + "vpc-provider:account=328440206208:filter.vpc-id=vpc-0d3d4b67bd0cf8a68:region=us-east-1:returnAsymmetricSubnets=true": { + "vpcId": "vpc-0d3d4b67bd0cf8a68", + "vpcCidrBlock": "10.20.0.0/16", + "ownerAccountId": "328440206208", + "availabilityZones": [], + "vpnGatewayId": "vgw-073737d44762dffc2", + "subnetGroups": [ + { + "name": "Private", + "type": "Private", + "subnets": [ + { + "subnetId": "subnet-04e38c507e96f1926", + "cidr": "10.20.30.0/24", + "availabilityZone": "us-east-1a", + "routeTableId": "rtb-06a2f56f492b9b4de" + }, + { + "subnetId": "subnet-0a0b4fc6f296dfba5", + "cidr": "10.20.40.0/24", + "availabilityZone": "us-east-1b", + "routeTableId": "rtb-01e152fe5cabca7d6" + } + ] + }, + { + "name": "Public", + "type": "Public", + "subnets": [ + { + "subnetId": "subnet-0eea820effe1b3ae5", + "cidr": "10.20.10.0/24", + "availabilityZone": "us-east-1a", + "routeTableId": "rtb-0f2232493a5c43fe8" + }, + { + "subnetId": "subnet-0012f5895182c1580", + "cidr": "10.20.20.0/24", + "availabilityZone": "us-east-1b", + "routeTableId": "rtb-0f2232493a5c43fe8" + } + ] + } + ] } } diff --git a/lib/constructs/bedrock-agent.ts b/lib/constructs/bedrock-agent.ts index d907272..80feb58 100644 --- a/lib/constructs/bedrock-agent.ts +++ b/lib/constructs/bedrock-agent.ts @@ -4,6 +4,7 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; import * as bedrock from 'aws-cdk-lib/aws-bedrock'; import * as path from 'path'; @@ -13,6 +14,8 @@ export interface BedrockAgentProps { region: string; knowledgeBaseId: string; knowledgeBaseArn: string; + vpc: ec2.IVpc; + lambdaSecurityGroup: ec2.ISecurityGroup; } export class BedrockAgentConstruct extends Construct { @@ -51,6 +54,9 @@ export class BedrockAgentConstruct extends Construct { timeout: cdk.Duration.seconds(30), memorySize: 256, environment: { QBO_SECRET_ARN: qboSecret.secretArn }, + vpc: props.vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, + securityGroups: [props.lambdaSecurityGroup], bundling, }); qboSecret.grantRead(this.qboLambda); diff --git a/lib/constructs/slack-handler.ts b/lib/constructs/slack-handler.ts index 634d212..651f4a6 100644 --- a/lib/constructs/slack-handler.ts +++ b/lib/constructs/slack-handler.ts @@ -5,6 +5,7 @@ import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2'; import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; import * as route53 from 'aws-cdk-lib/aws-route53'; @@ -19,6 +20,8 @@ export interface SlackHandlerProps { agentAliasId: string; conversationTable: dynamodb.Table; wildcardCertArn: string; + vpc: ec2.IVpc; + lambdaSecurityGroup: ec2.ISecurityGroup; } export class SlackHandlerConstruct extends Construct { @@ -124,6 +127,9 @@ export class SlackHandlerConstruct extends Construct { QBO_CLIENT_SECRET: '{{resolve:secretsmanager:seahaven/qbo/oauth:SecretString:clientSecret}}', REDIRECT_URI: 'https://bot.seahaven.com/qbo/callback', }, + vpc: props.vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, + securityGroups: [props.lambdaSecurityGroup], bundling: { externalModules: ['@aws-sdk/*'], minify: true, diff --git a/lib/seahaven-slack-bot-stack.ts b/lib/seahaven-slack-bot-stack.ts index d7814f2..9884996 100644 --- a/lib/seahaven-slack-bot-stack.ts +++ b/lib/seahaven-slack-bot-stack.ts @@ -1,4 +1,5 @@ import * as cdk from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; import { ConversationLogConstruct } from './constructs/conversation-log'; import { KnowledgeBaseConstruct } from './constructs/knowledge-base'; @@ -21,6 +22,16 @@ export class SeahavenSlackBotStack extends cdk.Stack { ); } + // ── VPC (existing) — QBO Lambdas run here for static outbound IP ────────── + const vpc = ec2.Vpc.fromLookup(this, 'SeahavenVpc', { vpcId: 'vpc-0d3d4b67bd0cf8a68' }); + + const lambdaSecurityGroup = new ec2.SecurityGroup(this, 'QBOLambdaSG', { + vpc, + securityGroupName: 'seahaven-qbo-lambda', + description: 'QBO Lambdas - outbound HTTPS only', + allowAllOutbound: true, + }); + // ── Conversation history (DynamoDB) ─────────────────────────────────────── const conversationLog = new ConversationLogConstruct(this, 'ConversationLog'); @@ -36,6 +47,8 @@ export class SeahavenSlackBotStack extends cdk.Stack { region: this.region, knowledgeBaseId: knowledgeBase.knowledgeBase.knowledgeBaseId, knowledgeBaseArn: knowledgeBase.knowledgeBase.knowledgeBaseArn, + vpc, + lambdaSecurityGroup, }); // ── Notion → KB daily sync (EventBridge + Lambda) ──────────────────────── @@ -70,6 +83,8 @@ export class SeahavenSlackBotStack extends cdk.Stack { agentAliasId: bedrockAgent.agentAlias.attrAgentAliasId, conversationTable: conversationLog.table, wildcardCertArn, + vpc, + lambdaSecurityGroup, }); // ── Stack outputs ─────────────────────────────────────────────────────────