Skip to content

Commit e54d8a4

Browse files
sirirako“Siriratrstrahan
authored
Feature/private appsync api (#259)
* feat: add AppSyncVisibility + LambdaSubnetIds parameters (private AppSync req #2, step 1/N) Add two new CloudFormation parameters to enable private AppSync: - AppSyncVisibility (GLOBAL default / PRIVATE) — controls API visibility - LambdaSubnetIds (CommaDelimitedList) — private subnets for Lambda VPC config Add UsePrivateAppSync condition. Remaining changes (next commits): - Visibility property on GraphQLApi - Lambda Security Group - AppSync + service VPC Interface Endpoints - Lambda VpcConfig on all ~20 functions - nested/appsync and patterns/unified updates * feat: add LambdaVpcSecurityGroup + GraphQLApi Visibility (private AppSync req #2, step 2/N) - Add LambdaVpcSecurityGroup (conditional on UsePrivateAppSync): EC2 security group for Lambda functions in VPC, allows HTTPS egress to VPC endpoints only - Add Visibility: !If [UsePrivateAppSync, PRIVATE, GLOBAL] to GraphQLApi resource so the API becomes VPC-only when AppSyncVisibility=PRIVATE * docs: add private AppSync implementation plan for next session * feat: add VPC endpoints + LambdaRouteTableIds param + UsePrivateAppSyncWithRouteTables condition (private AppSync req #2, steps 3-4/N) * feat: add VpcConfig to all 7 Lambda functions (private AppSync req #2, step 5/N) * docs: update implementation plan — steps 1-5 complete, steps 6-8 remaining * refactor: separate VPC endpoints into networking-team-owned scripts/vpc-endpoints.yaml - Remove 14 VPC endpoint resources from template.yaml (app stack) - Remove LambdaRouteTableIds param + UsePrivateAppSyncWithRouteTables condition - Add LambdaVpcSecurityGroupId Output to template.yaml (used as input to vpc-endpoints.yaml) - Create scripts/vpc-endpoints.yaml: standalone CFN template for networking team - Takes VpcId, SubnetIds, LambdaSecurityGroupId (from IDP Output), optional RouteTableIds - Creates VpcEndpointSecurityGroup + 12 Interface endpoints + 2 Gateway endpoints - Fully tagged with IDPStack + Environment for traceability Enterprise separation of concerns: - App team deploys: template.yaml (AppSyncVisibility=PRIVATE, LambdaSubnetIds) - Networking team deploys: scripts/vpc-endpoints.yaml (with LambdaVpcSecurityGroupId from Output) * docs: update implementation plan with VPC endpoint refactor details * fix: add UpdateReplacePolicy: Retain to GraphQLApi — Visibility is immutable on update * revert: undo GraphQLApi rename + UpdateReplacePolicy — Visibility set once at deploy time, not changed * feat: step 6 — add VPC config to nested/appsync resolver Lambdas (req #2) nested/appsync/template.yaml: - Add 3 new parameters: UsePrivateAppSync, LambdaSubnetIds, LambdaSecurityGroupId - Add IsPrivateAppSync condition - Add VpcConfig (!If [IsPrivateAppSync, ...]) to 4 resolver functions that call AppSync (AbortWorkflow, CopyToBaseline, ProcessChanges, ReprocessDocument) so they can reach the private AppSync endpoint template.yaml: - Pass the 3 new params to APPSYNCSTACK: UsePrivateAppSync: !If [UsePrivateAppSync, 'true', 'false'] LambdaSubnetIds: !Join [",", !Ref LambdaSubnetIds] (when PRIVATE, else "") LambdaSecurityGroupId: !Ref LambdaVpcSecurityGroup (when PRIVATE, else "") * feat: add optional enterprise artifact bucket hardening (req #4 and #5) - Add --kms-key-arn ARN flag: applies SSE-KMS with customer-managed CMK to the artifact bucket via put_bucket_encryption (BucketKeyEnabled=True to reduce KMS API costs). No-op when flag is not provided. - Add --enterprise-bucket-policy flag: applies a bucket policy with two Deny statements (DenyInsecureTransport + DenyExternalAccess) that enforce SSL-only access and restrict requests to the same AWS account. No-op when flag is not provided, preserving public deployment behaviour. - Move account_id resolution before setup_artifacts_bucket() in run() so the enterprise bucket policy has the account ID available. - Update print_usage() in publish.py and publish.sh to document both flags. Both flags are off by default so existing deployments (including the standard aws-ml-blog-* public buckets) are unaffected. * docs: add ALB private network deployment guide and test VPC template - docs/deployment-private-network.md: Step-by-step guide for deploying with ALB instead of CloudFront using the Publish+Deploy approach. Includes prerequisites, publish.py tips (Python/Node gotchas), ALB parameter reference, SSM port forwarding instructions, and troubleshooting table. - scripts/alb-test-vpc.yaml: CloudFormation template to create a test VPC with 2 private subnets in different AZs plus a self-signed ACM certificate (via Lambda custom resource). Used to simulate a customer private network environment for ALB hosting tests. Outputs VpcId, SubnetIds, CertificateArn, and a ready-to-use IDPDeployCommand. * docs: add scope note to deployment-private-network.md Clarify that this is the growing enterprise runbook (not just ALB hosting) and will expand as additional private network requirements (AppSync, SSO, etc.) are implemented. Reference alb-hosting.md for the ALB-only technical reference. * feat: add --tags flag for artifact bucket tagging (req #10) Add optional --tags Key1=Value1,Key2=Value2 flag to publish.py and publish.sh. Enterprise standards typically require tags on all S3 buckets for cost allocation, compliance, and inventory tracking. - New _apply_bucket_tags() method calls put_bucket_tagging with the parsed key/value pairs - Flag is optional (no-op when not set) — no behaviour change for existing deployments - Composes with --kms-key-arn and --enterprise-bucket-policy for fully-hardened enterprise bucket * fix: update DenyExternalAccess to allow AWS service principals (CloudFormation, CodeBuild, Lambda) The original condition only checked aws:PrincipalAccount which doesn't apply to AWS service principals (they use aws:PrincipalIsAWSService instead). This caused CloudFormation to be denied when reading templates from S3 via --template-url, breaking all stack deployments. Fix: add Bool condition on aws:PrincipalIsAWSService: false so the Deny only applies to external human/machine principals, not AWS service calls. * fix: ruff format publish.py * docs: add CRITICAL warning to _apply_kms_encryption — never use a CFN stack key When the IDP stack is deleted, CloudFormation deletes CustomerManagedEncryptionKey and schedules it for deletion. If that key was used with --kms-key-arn for the artifact bucket, ALL S3 objects (templates, Lambda zips, layers) become inaccessible, blocking ALL future stack deployments. Use a dedicated, standalone KMS key managed outside any deployable stack. Recovery: aws kms cancel-key-deletion && aws kms enable-key * fix: remove IDPDeployCommand from alb-test-vpc.yaml; update pr-description - scripts/alb-test-vpc.yaml: remove IDPDeployCommand output — it had a hardcoded personal email and doesn't belong in a VPC-only template; the template's job is to create the VPC/subnets/cert, not prescribe how to use them - pr-description.md: add --tags (req #10) to table, usage examples, and Related section; fix alb-test-vpc.yaml output list to match actual outputs; update summary count from 'two' to 'three' flags; fix title to include #10 * feat: add Lambda subnet to alb-test-vpc.yaml; remove pr-description from git - scripts/alb-test-vpc.yaml: add LambdaSubnet (10.1.3.0/24) in AZ[0], separate from ALB subnets (PrivateSubnet1/2) — used to test IDP Lambdas in a different subnet for private AppSync VPC config testing. Output LambdaSubnetId exported as ${StackName}-LambdaSubnetId. Updated Description to document 3-subnet layout. - .gitignore: add pr-description.md (not tracked — used as clipboard for GitHub PR form) * chore: remove pr-description.md from .gitignore pr-description.md is a local scratchpad for copy-pasting into the GitHub PR form — it should not exist in the repo at all, not even in .gitignore. * feat: add standalone KMS CMK to alb-test-vpc.yaml (req #4) Add ArtifactBucketKey (AWS::KMS::Key) managed by THIS stack, not the IDP stack. This ensures deleting the IDP stack never schedules the key for deletion and makes S3 objects inaccessible. Key properties: - DeletionPolicy: Retain — key survives stack deletion - EnableKeyRotation: true — automatic annual rotation - Key policy: account root full control + S3 service GenerateDataKey/Decrypt - Alias: alias/${StackName}-artifact-bucket-key New outputs: - ArtifactBucketKeyArn — pass as --kms-key-arn to publish.py/publish.sh - ArtifactBucketKeyAlias — human-readable alias reference * feat: step 7 — add VpcConfig to all processing Lambdas in patterns/unified (req #2) Added IsPrivateAppSync condition and VpcConfig to 10 processing Lambda functions: - BDAProcessResultsFunction - OCRFunction - ClassificationFunction - ExtractionFunction - AssessmentFunction - ProcessResultsFunction (no Tracing) - SummarizationFunction (no Tracing) - EvaluationFunction - RuleValidationFunction (no Tracing) - RuleValidationOrchestrationFunction (no Tracing) Pattern: VpcConfig: !If [IsPrivateAppSync, {SubnetIds: !Ref LambdaSubnetIds, SecurityGroupIds: [!Ref LambdaSecurityGroupId]}, !Ref AWS::NoValue] Note: LambdaSubnetIds is CommaDelimitedList in this template (passed as string from main, parsed back). InvokeBDAFunction and BDACompletionFunction skipped — they do not call AppSync. * docs: step 8 — add private AppSync section to deployment-private-network.md; mark all steps complete (req #2) - Added 'Private AppSync API (AppSyncVisibility=PRIVATE)' section to deployment-private-network.md: - Architecture diagram (Lambda Subnets → VPC endpoints → AWS services) - New parameters: AppSyncVisibility, LambdaSubnetIds - Two-step deployment runbook (app team + networking team) - VPC endpoint details (12 Interface + 2 Gateway) - Test VPC reference values and ready-to-use deploy commands - Updated private-appsync-implementation-plan.md: marked Steps 7 and 8 complete with commit refs * chore: update memory bank — private AppSync impl all steps complete * fix: pass UsePrivateAppSync/LambdaSubnetIds/LambdaSecurityGroupId to PATTERNSTACK (req #2) PATTERNSTACK (patterns/unified) was missing the 3 private AppSync parameters, causing all processing Lambdas to have UsePrivateAppSync=false even when the main stack had AppSyncVisibility=PRIVATE. Added to PATTERNSTACK Parameters block (same pattern as APPSYNCSTACK): UsePrivateAppSync: !If [UsePrivateAppSync, "true", "false"] LambdaSubnetIds: !If [UsePrivateAppSync, !Join [",", !Ref LambdaSubnetIds], ""] LambdaSecurityGroupId: !If [UsePrivateAppSync, !Ref LambdaVpcSecurityGroup, ""] * fix: add CreateSsmEndpoint param to vpc-endpoints.yaml to handle pre-existing SSM endpoint When the VPC already has an SSM Interface endpoint (e.g. pre-created for EC2 SSM access), deploying a second SSM endpoint with PrivateDnsEnabled=true fails with 'conflicting DNS domain' error. Added CreateSsmEndpoint parameter (default 'true'): set to 'false' to skip SSM endpoint creation when one already exists in the VPC. * docs: add 'Disabling Internet-Facing Features' section to deployment-private-network.md When deploying in a fully private/air-gapped environment, document that EnableMCP=false and DocumentKnowledgeBase=DISABLED should be set to avoid features that require public AWS endpoints: - EnableMCP=false: disables Bedrock AgentCore Gateway (requires public endpoint) - DocumentKnowledgeBase=DISABLED: disables KB creation (S3 Vectors/OpenSearch) - Code Intelligence: already disabled by default * docs: replace verbose Build Tools list with reference to deployment.md#dependencies * feat: add check-vpc-endpoints.sh + per-endpoint CFN flags; rewrite deployment runbook scripts/vpc-endpoints.yaml: - Add Create<Service>Endpoint param (default 'true') for all 12 Interface endpoints - Each endpoint gated on ShouldCreate<Service>Endpoint condition - Allows skipping pre-existing endpoints to avoid PrivateDNS conflicts scripts/check-vpc-endpoints.sh (new): - Detects which IDP-required endpoints already exist in the VPC - Reads LambdaSecurityGroupId and LambdaSubnetIds from the IDP stack automatically - Prints a ready-to-run aws cloudformation deploy command with Create*=false for any endpoints that already exist - Usage: ./scripts/check-vpc-endpoints.sh --vpc-id <id> --stack-name IDP-PRIVATE docs/deployment-private-network.md: - Rewritten as a focused 4-step private-network-only runbook - Removed: Switching sections, Security Notes, separate AppSync/MCP sections - Unified: all private params in one deploy command in Step 2 - Step 3 uses check-vpc-endpoints.sh workflow - Step 4 covers SSM port forwarding for testing * feat: add deploy-vpc-endpoints.py — cross-platform endpoint deployer (Windows/macOS/Linux) scripts/deploy-vpc-endpoints.py (new, cross-platform): - Uses boto3 (already a project dependency) - Auto-reads LambdaSubnetIds + LambdaVpcSecurityGroupId from IDP stack - Checks all 12 required Interface endpoints against the VPC - Deploys only the MISSING ones (passes Create*=false for existing ones) - Waits for CREATE_COMPLETE and shows a clear success/failure summary - Handles ROLLBACK_COMPLETE stacks by deleting and re-creating - Supports --dry-run to preview without deploying - Works on Windows PowerShell, macOS, and Linux docs/deployment-private-network.md: - Step 3 now uses deploy-vpc-endpoints.py (one command, works everywhere) - Added Windows PowerShell syntax example - Updated example output to match Python script format * docs: add EC2 SSM bastion creation steps to Step 4 of deployment-private-network.md Step 4 'Accessing via SSM port forwarding' previously said 'Ensure there is an EC2 instance' without explaining how to create one. Added concrete commands to: 1. Create IAM role + instance profile with AmazonSSMManagedInstanceCore policy 2. Launch t3.nano in the private subnet with no public IP 3. Verify SSM agent registration (PingStatus: Online) * fix: add ssmmessages + ec2messages endpoints to vpc-endpoints.yaml and deploy script ssmmessages and ec2messages are required for SSM Session Manager port forwarding (not just for the SSM heartbeat). Without them, 'StartSession' calls fail with TargetNotConnected even when the instance shows PingStatus=Online. vpc-endpoints.yaml: - Added CreateSsmMessagesEndpoint param + ShouldCreateSsmMessagesEndpoint condition - Added CreateEc2MessagesEndpoint param + ShouldCreateEc2MessagesEndpoint condition - Added SsmMessagesVpcEndpoint and Ec2MessagesVpcEndpoint resources deploy-vpc-endpoints.py: - Added ssmmessages and ec2messages to REQUIRED_ENDPOINTS dict (12 → 14 endpoints) * docs: clarify VPC endpoint requirements and add enterprise bucket hardening - deployment-private-network.md: - Step 1: add enterprise artifact bucket hardening section (--kms-key-arn, --enterprise-bucket-policy, --tags flags) - Step 3: clarify 12 IDP-app-required + 2 SSM-testing endpoints - Step 4: fix sudo -> sudo -E to preserve env var credentials when binding AppSync tunnel on port 443 - scripts/check-vpc-endpoints.sh: - Add ssmmessages and ec2messages endpoints (12 -> 14 total) - Add comment block distinguishing IDP-app-required vs SSM-testing-bastion-only endpoints - scripts/vpc-endpoints.yaml: - Add structured comment block in Parameters section clarifying: * 12 endpoints required by IDP application * 2 endpoints required only for SSM Session Manager testing bastion (ssmmessages, ec2messages) — not needed in production with VPN * docs: update private network deployment — 2-step self-signed cert, 3-subnet TestVPC - scripts/alb-test-vpc.yaml: remove ACM certificate custom resource (Lambda/IAM/SelfSignedCertificate). The cert is now created separately via generate_self_signed_cert.sh after deploy when the ELB hostname is known. Add LambdaSubnet (10.1.3.0/24) for dedicated Lambda VpcConfig subnet. - scripts/generate_self_signed_cert.sh: add --cert-arn flag to reimport an existing ACM cert in-place (no stack update needed). Add 2-step usage documentation explaining why cert domain must match ALB hostname. - docs/deployment-private-network.md: document the 2-step cert process; update TestVPC section (no CertificateArn output); add troubleshooting entries for cert domain mismatch and Cognito IDP internet requirement. - memory-bank: update activeContext with private AppSync browser testing learnings; update projectbrief with correct alb-test-vpc description. * docs: add Amazon WorkSpaces testing section to private network runbook - Add 'Accessing via Amazon WorkSpaces (testing)' under Step 4 - Covers requirements (Directory Service, NAT GW in dedicated public subnet, internet access enabled on directory) - Key setup notes: NAT GW placement, enable internet before launch, 2-step cert - Verification checklist: login, live doc status updates, WS tab in DevTools - Documents AppSync WebSocket behavior: appsync-api endpoint handles both HTTPS and WSS — no separate appsync-realtime-api endpoint needed - Table of features degraded when WebSocket subscriptions fail - Cost/cleanup note * feat(cli): add --artifacts-bucket-kms-key-arn and --artifacts-bucket-tags to idp-cli publish * docs: update deployment-private-network.md to use idp-cli publish with enterprise flags * feat(template): add ArtifactsBucketKmsKeyArn parameter to grant CodeBuild kms:Decrypt on encrypted artifact bucket - Add ArtifactsBucketKmsKeyArn optional parameter (empty by default) - Add HasArtifactsBucketKmsKey condition - Conditionally grant kms:Decrypt/GenerateDataKey/DescribeKey to UICodeBuildServiceRole - Enables idp-cli publish --artifacts-bucket-kms-key-arn with end-to-end deployment support * fix: add kms:Decrypt for artifact bucket CMK to CodeBuild and Lambda roles When an artifact bucket is encrypted with a customer-managed KMS key (via idp-cli publish --artifacts-bucket-kms-key-arn), several roles lacked kms:Decrypt on that key, causing AccessDenied failures during deployment. Changes: - patterns/unified/template.yaml: Add ArtifactsBucketKmsKeyArn parameter and conditional kms:Decrypt grant to DockerBuildRole (HasArtifactsBucketKmsKey condition). DockerBuildRole now receives kms:Decrypt/GenerateDataKey*/DescribeKey on the artifact bucket CMK when the parameter is provided. - template.yaml: - Pass ArtifactsBucketKmsKeyArn to PATTERNSTACK so DockerBuildRole receives the grant - Add conditional kms:Decrypt grant to ConfigurationCopyFunction role so it can read config files from a KMS-encrypted artifact bucket (previously silently skipped all 39 files, causing UpdateDefaultConfig NoSuchKey failure) - docs/deployment-private-network.md: - Replace aws cloudformation create-stack with idp-cli deploy - Add enterprise KMS deploy note: ArtifactsBucketKmsKeyArn must be passed at deploy time when using --artifacts-bucket-kms-key-arn - Remove Docker from build tool prerequisites (not needed locally) - Update troubleshooting: add KMS/AccessDenied rows, clean up stale entries Tested end-to-end: - IDP-ALB (private network, ALB, KMS): CREATE_COMPLETE, both CodeBuild SUCCEEDED - IDP-CF (CloudFront default, no KMS): CREATE_COMPLETE, both CodeBuild SUCCEEDED * fix(alb-test-vpc): add cfn_nag W79 and checkov suppressions with justifications - ArtifactBucketKey: add cfn_nag W79 suppress for kms:* wildcard on root principal — standard CMK admin access pattern; key is managed outside the IDP stack by design to prevent key deletion on IDP stack delete - ArtifactBucketKey: add checkov skip CKV_AWS_7 — key rotation is already enabled via EnableKeyRotation: true - VPC: add checkov skip CKV_AWS_178 — flow logs not required for this test-only template (production VPCs should enable flow logs) * chore: remove internal implementation plan (work complete) * fix: address PR review feedback — VPC endpoints, CFn Interface metadata, docs VPC Endpoints (missing services) - scripts/vpc-endpoints.yaml: add textract + sts Interface endpoints with CreateTextractEndpoint / CreateStsEndpoint flags (default true). OCR pattern calls Textract API; BDA pattern calls STS AssumeRole — both time out in private mode without these endpoints. - scripts/deploy-vpc-endpoints.py: add textract + sts to REQUIRED_ENDPOINTS dict (14 required total, was 12) - scripts/check-vpc-endpoints.sh: add textract + sts to ENDPOINTS map, update comment to '14 required' CloudFormation::Interface metadata - template.yaml: add 'Private Network' parameter group (AppSyncVisibility, LambdaSubnetIds) and 'Advanced — Artifact Bucket' group (ArtifactsBucketKmsKeyArn) so new params appear grouped in CloudFormation console instead of 'Other' - template.yaml: add ParameterLabels for AppSyncVisibility, LambdaSubnetIds, ArtifactsBucketKmsKeyArn ALBVpcId dependency documentation - template.yaml: update ALBVpcId description to explain it's also required when AppSyncVisibility=PRIVATE (Lambda SG uses the same VPC) - template.yaml: add inline comment on LambdaVpcSecurityGroup explaining why ALBVpcId is used (PRIVATE AppSync always requires ALB hosting) ArtifactsBucketKmsKeyArn description - template.yaml: reword description — clarifies the pre-created bucket pattern (customer creates compliant bucket, passes KMS key ARN at deploy) Documentation - docs/deployment-private-network.md: rewrite enterprise bucket hardening section — replace publish CLI flags with pre-created bucket pattern - docs/deployment-private-network.md: update Step 3 to say '14 required endpoints' and update example summary line - docs/deployment-private-network.md: add troubleshooting rows for Textract and STS timeouts (missing VPC endpoints) * chore: revert memory-bank/projectbrief.md to main branch version Removes session-specific implementation notes added during development. After this commit the file is identical to main and will not appear in the PR diff. * revert: remove cli-publish-artifact-bucket-hardening code from this PR The --artifacts-bucket-kms-key-arn and --artifacts-bucket-tags CLI flags are not needed — customers should pre-create compliant artifact buckets rather than having the CLI manage bucket encryption and tagging. Reverts commit 07e5e96 (feat(cli): add --artifacts-bucket-kms-key-arn and --artifacts-bucket-tags to idp-cli publish) which was merged from feature/cli-publish-artifact-bucket-hardening. Files reverted to main: - lib/idp_cli_pkg/idp_cli/cli.py (removed 2 CLI options) - lib/idp_sdk/idp_sdk/operations/publish.py (removed put_bucket_encryption/tagging calls) - docs/idp-cli.md (removed flag documentation) - docs/idp-sdk.md (removed SDK documentation) - lib/idp_sdk/tests/unit/test_publish_operations.py (deleted — new file in hardening branch) --------- Co-authored-by: “Sirirat <“siriratk@amazon.com”> Co-authored-by: Bob Strahan <strahanr@amazon.com>
1 parent d8fc24f commit e54d8a4

9 files changed

Lines changed: 2089 additions & 29 deletions

File tree

docs/deployment-private-network.md

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.

nested/appsync/template.yaml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,28 @@ Parameters:
200200
IDPCommonBaseLayerArn:
201201
Type: String
202202
Description: ARN of IDP Common Base Layer
203-
203+
204204
IDPCommonAgentsLayerArn:
205205
Type: String
206206
Description: ARN of IDP Common Agents Layer
207207

208+
# Private AppSync VPC config (req #2) — passed from main template
209+
UsePrivateAppSync:
210+
Type: String
211+
Default: "false"
212+
AllowedValues: ["true", "false"]
213+
Description: Set to "true" when AppSyncVisibility=PRIVATE — puts resolver Lambdas in VPC
214+
215+
LambdaSubnetIds:
216+
Type: CommaDelimitedList
217+
Default: ""
218+
Description: Subnet IDs for Lambda VpcConfig (required when UsePrivateAppSync=true)
219+
220+
LambdaSecurityGroupId:
221+
Type: String
222+
Default: ""
223+
Description: Security group ID for Lambda VpcConfig (required when UsePrivateAppSync=true)
224+
208225
Metadata:
209226
cfn-lint:
210227
config:
@@ -218,6 +235,7 @@ Conditions:
218235
ShouldCreateEvaluationBaselineBucket: !Equals [!Ref ShouldCreateEvaluationBaselineBucket, "true"]
219236
ShouldCreateReportingBucket: !Equals [!Ref ShouldCreateReportingBucket, "true"]
220237
HasTestExecutionAggregationFunction: !Not [!Equals [!Ref TestExecutionAggregationFunctionArn, ""]]
238+
IsPrivateAppSync: !Equals [!Ref UsePrivateAppSync, "true"]
221239
HasMLflowLoggerFunction: !Not [!Equals [!Ref MLflowLoggerFunctionArn, ""]]
222240

223241
Resources:
@@ -436,6 +454,12 @@ Resources:
436454
Description: Lambda function to abort document workflows via GraphQL API
437455
MemorySize: 512
438456
Timeout: 60
457+
VpcConfig: !If
458+
- IsPrivateAppSync
459+
- SubnetIds: !Ref LambdaSubnetIds
460+
SecurityGroupIds:
461+
- !Ref LambdaSecurityGroupId
462+
- !Ref AWS::NoValue
439463

440464
Layers:
441465
- !Ref IDPCommonBaseLayerArn
@@ -1191,6 +1215,12 @@ Resources:
11911215
Description: Lambda function to copy files to baseline bucket via GraphQL API
11921216
MemorySize: 512
11931217
Timeout: 300
1218+
VpcConfig: !If
1219+
- IsPrivateAppSync
1220+
- SubnetIds: !Ref LambdaSubnetIds
1221+
SecurityGroupIds:
1222+
- !Ref LambdaSecurityGroupId
1223+
- !Ref AWS::NoValue
11941224

11951225
Layers:
11961226
- !Ref IDPCommonBaseLayerArn
@@ -2475,6 +2505,12 @@ Resources:
24752505
Description: Lambda function to process section changes via GraphQL API
24762506
MemorySize: 512
24772507
Timeout: 60
2508+
VpcConfig: !If
2509+
- IsPrivateAppSync
2510+
- SubnetIds: !Ref LambdaSubnetIds
2511+
SecurityGroupIds:
2512+
- !Ref LambdaSecurityGroupId
2513+
- !Ref AWS::NoValue
24782514

24792515
Layers:
24802516
- !Ref IDPCommonBaseLayerArn
@@ -2724,6 +2760,12 @@ Resources:
27242760
Description: Lambda function to reprocess documents via GraphQL API
27252761
MemorySize: 512
27262762
Timeout: 60
2763+
VpcConfig: !If
2764+
- IsPrivateAppSync
2765+
- SubnetIds: !Ref LambdaSubnetIds
2766+
SecurityGroupIds:
2767+
- !Ref LambdaSecurityGroupId
2768+
- !Ref AWS::NoValue
27272769

27282770
Layers:
27292771
- !Ref IDPCommonBaseLayerArn

patterns/unified/template.yaml

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ Parameters:
117117
Type: String
118118
Description: "S3 bucket containing deployment artifacts"
119119

120+
ArtifactsBucketKmsKeyArn:
121+
Type: String
122+
Default: ""
123+
Description: >-
124+
(Optional) KMS CMK ARN used to encrypt the artifacts bucket. When provided,
125+
the DockerBuildRole and other CodeBuild roles receive kms:Decrypt permission.
126+
AllowedPattern: "^(|arn:aws[a-z-]*:kms:[a-z0-9-]+:[0-9]{12}:key/.+)$"
127+
120128
ArtifactPrefix:
121129
Type: String
122130
Description: "S3 prefix for deployment artifacts"
@@ -158,6 +166,23 @@ Parameters:
158166
Type: String
159167
Default: ""
160168

169+
# Private AppSync VPC config (req #2)
170+
UsePrivateAppSync:
171+
Type: String
172+
Default: "false"
173+
AllowedValues: ["true", "false"]
174+
Description: Set to "true" when AppSyncVisibility=PRIVATE — puts processing Lambdas in VPC
175+
176+
LambdaSubnetIds:
177+
Type: CommaDelimitedList
178+
Default: ""
179+
Description: Subnet IDs for Lambda VpcConfig (required when UsePrivateAppSync=true)
180+
181+
LambdaSecurityGroupId:
182+
Type: String
183+
Default: ""
184+
Description: Security group ID for Lambda VpcConfig (required when UsePrivateAppSync=true)
185+
161186
EnableMLflow:
162187
Type: String
163188
Default: 'false'
@@ -182,7 +207,9 @@ Conditions:
182207
HasPermissionsBoundary: !Not [!Equals [!Ref PermissionsBoundaryArn, ""]]
183208
HasAppSyncApi: !Not [!Equals [!Ref AppSyncApiArn, ""]]
184209
IsECRImageScanningEnabled: !Equals [!Ref EnableECRImageScanning, "true"]
210+
IsPrivateAppSync: !Equals [!Ref UsePrivateAppSync, "true"]
185211
IsMLflowEnabled: !Equals [!Ref EnableMLflow, "true"]
212+
HasArtifactsBucketKmsKey: !Not [!Equals [!Ref ArtifactsBucketKmsKeyArn, ""]]
186213

187214
Resources:
188215

@@ -2053,6 +2080,17 @@ Resources:
20532080
Action:
20542081
- kms:Decrypt
20552082
- kms:CreateGrant
2083+
# Grant kms:Decrypt on the artifacts bucket KMS key when provided
2084+
# This allows DockerBuildRole to download source code from a KMS-encrypted artifact bucket
2085+
- !If
2086+
- HasArtifactsBucketKmsKey
2087+
- Effect: Allow
2088+
Action:
2089+
- kms:Decrypt
2090+
- kms:GenerateDataKey*
2091+
- kms:DescribeKey
2092+
Resource: !Ref ArtifactsBucketKmsKeyArn
2093+
- !Ref AWS::NoValue
20562094

20572095
DockerBuildProject:
20582096
Type: AWS::CodeBuild::Project
@@ -2310,16 +2348,16 @@ Resources:
23102348
RetentionInDays: !Ref LogRetentionDays
23112349

23122350
BDAProcessResultsFunction:
2313-
DependsOn: DockerBuildRun
23142351
Type: AWS::Serverless::Function
2352+
DependsOn: DockerBuildRun
23152353
Metadata:
23162354
SkipBuild: True
23172355
cfn_nag:
23182356
rules_to_suppress:
23192357
- id: W11
23202358
reason: "Cloudwatch does not support resource-level permissions"
23212359
- id: W89
2322-
reason: "This Lambda function does not require VPC access as it only interacts with AWS services via AWS APIs"
2360+
reason: "This Lambda function does not require VPC access as it only interacts with AWS services via AWS APIs — placed in VPC when AppSyncVisibility=PRIVATE"
23232361
- id: W92
23242362
reason: "Function does not require concurrent execution limits as it is designed to scale based on demand"
23252363
# checkov:skip=CKV_AWS_116: "DLQ not required for this function as StepFunctions will handle retries"
@@ -2335,6 +2373,12 @@ Resources:
23352373
Timeout: 900
23362374
MemorySize: 4096
23372375
Tracing: Active
2376+
VpcConfig: !If
2377+
- IsPrivateAppSync
2378+
- SubnetIds: !Ref LambdaSubnetIds
2379+
SecurityGroupIds:
2380+
- !Ref LambdaSecurityGroupId
2381+
- !Ref AWS::NoValue
23382382
Environment:
23392383
Variables:
23402384
METRIC_NAMESPACE: !Ref StackName
@@ -2587,6 +2631,12 @@ Resources:
25872631
Timeout: 900
25882632
MemorySize: 4096
25892633
Tracing: Active
2634+
VpcConfig: !If
2635+
- IsPrivateAppSync
2636+
- SubnetIds: !Ref LambdaSubnetIds
2637+
SecurityGroupIds:
2638+
- !Ref LambdaSecurityGroupId
2639+
- !Ref AWS::NoValue
25902640
Environment:
25912641
Variables:
25922642
METRIC_NAMESPACE: !Ref StackName
@@ -2704,6 +2754,12 @@ Resources:
27042754
Timeout: 900
27052755
MemorySize: 4096
27062756
Tracing: Active
2757+
VpcConfig: !If
2758+
- IsPrivateAppSync
2759+
- SubnetIds: !Ref LambdaSubnetIds
2760+
SecurityGroupIds:
2761+
- !Ref LambdaSecurityGroupId
2762+
- !Ref AWS::NoValue
27072763
Environment:
27082764
Variables:
27092765
METRIC_NAMESPACE: !Ref StackName
@@ -2833,6 +2889,12 @@ Resources:
28332889
Timeout: 900
28342890
MemorySize: 4096
28352891
Tracing: Active
2892+
VpcConfig: !If
2893+
- IsPrivateAppSync
2894+
- SubnetIds: !Ref LambdaSubnetIds
2895+
SecurityGroupIds:
2896+
- !Ref LambdaSecurityGroupId
2897+
- !Ref AWS::NoValue
28362898
Environment:
28372899
Variables:
28382900
METRIC_NAMESPACE: !Ref StackName
@@ -2960,6 +3022,12 @@ Resources:
29603022
Timeout: 900
29613023
MemorySize: 4096
29623024
Tracing: Active
3025+
VpcConfig: !If
3026+
- IsPrivateAppSync
3027+
- SubnetIds: !Ref LambdaSubnetIds
3028+
SecurityGroupIds:
3029+
- !Ref LambdaSecurityGroupId
3030+
- !Ref AWS::NoValue
29633031
Environment:
29643032
Variables:
29653033
METRIC_NAMESPACE: !Ref StackName
@@ -3077,6 +3145,12 @@ Resources:
30773145
- "index.handler"
30783146
Timeout: 900
30793147
MemorySize: 4096
3148+
VpcConfig: !If
3149+
- IsPrivateAppSync
3150+
- SubnetIds: !Ref LambdaSubnetIds
3151+
SecurityGroupIds:
3152+
- !Ref LambdaSecurityGroupId
3153+
- !Ref AWS::NoValue
30803154
Environment:
30813155
Variables:
30823156
METRIC_NAMESPACE: !Ref StackName
@@ -3165,6 +3239,12 @@ Resources:
31653239
- "index.handler"
31663240
Timeout: 900
31673241
MemorySize: 4096
3242+
VpcConfig: !If
3243+
- IsPrivateAppSync
3244+
- SubnetIds: !Ref LambdaSubnetIds
3245+
SecurityGroupIds:
3246+
- !Ref LambdaSecurityGroupId
3247+
- !Ref AWS::NoValue
31683248
Environment:
31693249
Variables:
31703250
METRIC_NAMESPACE: !Ref StackName
@@ -3286,6 +3366,12 @@ Resources:
32863366
Timeout: 900
32873367
MemorySize: 4096
32883368
Tracing: Active
3369+
VpcConfig: !If
3370+
- IsPrivateAppSync
3371+
- SubnetIds: !Ref LambdaSubnetIds
3372+
SecurityGroupIds:
3373+
- !Ref LambdaSecurityGroupId
3374+
- !Ref AWS::NoValue
32893375
Environment:
32903376
Variables:
32913377
LOG_LEVEL: !Ref LogLevel
@@ -3455,6 +3541,12 @@ Resources:
34553541
# Runtime: python3.12
34563542
Timeout: 900
34573543
MemorySize: 4096
3544+
VpcConfig: !If
3545+
- IsPrivateAppSync
3546+
- SubnetIds: !Ref LambdaSubnetIds
3547+
SecurityGroupIds:
3548+
- !Ref LambdaSecurityGroupId
3549+
- !Ref AWS::NoValue
34583550
Environment:
34593551
Variables:
34603552
METRIC_NAMESPACE: !Ref StackName
@@ -3566,6 +3658,12 @@ Resources:
35663658
# Runtime: python3.12
35673659
Timeout: 900
35683660
MemorySize: 4096
3661+
VpcConfig: !If
3662+
- IsPrivateAppSync
3663+
- SubnetIds: !Ref LambdaSubnetIds
3664+
SecurityGroupIds:
3665+
- !Ref LambdaSecurityGroupId
3666+
- !Ref AWS::NoValue
35693667
Environment:
35703668
Variables:
35713669
METRIC_NAMESPACE: !Ref StackName

0 commit comments

Comments
 (0)