Place QBO Lambdas in VPC for static outbound IP

Puts qbo-lookup and qbo-oauth Lambdas in seahaven-vpc private subnets
so all outbound traffic routes through NAT Gateway (52.202.83.13).
Required for Intuit app listing IP allowlist.
This commit is contained in:
Adam Moussa 2026-04-13 19:51:00 -04:00
parent 266fe833fd
commit 066ff59d22
4 changed files with 72 additions and 0 deletions

View file

@ -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"
}
]
}
]
}
}

View file

@ -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);

View file

@ -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,

View file

@ -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 ─────────────────────────────────────────────────────────