From a74ac4945feeb816b487cbf176d3e9e3027e42e9 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Wed, 27 May 2026 17:21:02 -0400 Subject: [PATCH] fix: LAM-C1/INF-H1 require IAM auth on Function URL, INF-H3 restrict OpenSearch to VPC LAM-C1/INF-H1: Change Function URL authType from NONE to AWS_IAM and grant invokeUrl permission to all four caller Lambdas (suggestions, pdf-extract, pdf-generate, library-ingest). Lambda HTTP clients will need SigV4 signing as a follow-up. INF-H3: Create OpenSearch Serverless VPC endpoint in private subnets and update network policy from AllowFromPublic to SourceVPCEs, removing public internet access to the vector search collection. --- infra/lib/compute-stack.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/infra/lib/compute-stack.ts b/infra/lib/compute-stack.ts index 4cca936..450050f 100644 --- a/infra/lib/compute-stack.ts +++ b/infra/lib/compute-stack.ts @@ -58,6 +58,15 @@ export class ComputeStack extends cdk.Stack { }), }); + // Fix: INF-H3 — restrict OpenSearch Serverless to VPC (was AllowFromPublic: true). + // Create a VPC endpoint so Lambdas in private subnets can reach the collection. + const ossVpcEndpoint = new opensearchserverless.CfnVpcEndpoint(this, 'OssVpcEndpoint', { + name: 'proposal-system-kb-vpce', + vpcId: props.vpc.vpcId, + subnetIds: props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }).subnetIds, + securityGroupIds: [props.lambdaSecurityGroup.securityGroupId], + }); + const ossNetworkPolicy = new opensearchserverless.CfnSecurityPolicy(this, 'OssNetworkPolicy', { name: 'proposal-system-kb-net', type: 'network', @@ -65,9 +74,11 @@ export class ComputeStack extends cdk.Stack { Rules: [ { ResourceType: 'collection', Resource: ['collection/proposal-system-kb'] }, ], - AllowFromPublic: true, + AllowFromPublic: false, + SourceVPCEs: [ossVpcEndpoint.attrId], }]), }); + ossNetworkPolicy.addDependency(ossVpcEndpoint); const ossCollection = new opensearchserverless.CfnCollection(this, 'OssCollection', { name: 'proposal-system-kb', @@ -237,9 +248,12 @@ export class ComputeStack extends cdk.Stack { resources: [props.userPool.userPoolArn], })); - // Function URL for internal Lambda-to-API calls (bypasses API Gateway JWT authorizer) + // Fix: LAM-C1/INF-H1 — require IAM auth on Function URL (was authType NONE). + // NOTE: Lambda HTTP clients (suggestions, pdf-extract, pdf-generate, library-ingest) + // must use SigV4 signing when calling this URL. The API key header alone is no longer + // sufficient for authentication at the transport layer. const apiFunctionUrl = apiFunction.addFunctionUrl({ - authType: lambda.FunctionUrlAuthType.NONE, + authType: lambda.FunctionUrlAuthType.AWS_IAM, }); // API Gateway HTTP API @@ -338,6 +352,8 @@ export class ComputeStack extends cdk.Stack { }); internalApiKeySecret.grantRead(suggestionsFunction); + // Fix: LAM-C1/INF-H1 — grant Function URL invoke permission for IAM auth + apiFunctionUrl.grantInvokeUrl(suggestionsFunction); suggestionsFunction.addToRolePolicy(new iam.PolicyStatement({ actions: ['bedrock:InvokeModel'], resources: [`arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-*`], @@ -369,6 +385,8 @@ export class ComputeStack extends cdk.Stack { }); internalApiKeySecret.grantRead(pdfExtractFunction); + // Fix: LAM-C1/INF-H1 — grant Function URL invoke permission for IAM auth + apiFunctionUrl.grantInvokeUrl(pdfExtractFunction); props.uploadsBucket.grantRead(pdfExtractFunction); pdfExtractFunction.addToRolePolicy(new iam.PolicyStatement({ actions: ['bedrock:InvokeModel'], @@ -396,6 +414,8 @@ export class ComputeStack extends cdk.Stack { }); internalApiKeySecret.grantRead(pdfGenerateFunction); + // Fix: LAM-C1/INF-H1 — grant Function URL invoke permission for IAM auth + apiFunctionUrl.grantInvokeUrl(pdfGenerateFunction); props.generatedBucket.grantWrite(pdfGenerateFunction); // Python Lambda: Library Ingest @@ -421,6 +441,8 @@ export class ComputeStack extends cdk.Stack { }); internalApiKeySecret.grantRead(libraryIngestFunction); + // Fix: LAM-C1/INF-H1 — grant Function URL invoke permission for IAM auth + apiFunctionUrl.grantInvokeUrl(libraryIngestFunction); props.libraryBucket.grantWrite(libraryIngestFunction); libraryIngestFunction.addToRolePolicy(new iam.PolicyStatement({ actions: ['bedrock:StartIngestionJob'],