Network Topology
This page covers the network topology decisions set out in RAG001, RAG010, and RAG011.
VPC layout
QA and Prod each own one VPC in us-east-1. The two VPCs are structurally identical and not peered. Aurora Serverless v2 places a cluster instance in each AZ covered by the subnet group.
| Environment | VPC block | Subnet A (us-east-1a) | Subnet B (us-east-1b) |
|---|---|---|---|
| QA | 10.10.0.0/16 | 10.10.1.0/24 | 10.10.2.0/24 |
| Prod | 10.20.0.0/16 | 10.20.1.0/24 | 10.20.2.0/24 |
Blocks are non-overlapping RFC 1918 /16 prefixes. Specific values are set in each environment’s Terraform root (infra/environments/qa/ and infra/environments/prod/).
Each subnet has a local route entry only (10.x.0.0/16 local). There is no 0.0.0.0/0 or ::/0 entry. No Internet Gateway is attached. No NAT Gateway or VPC endpoint of any kind is provisioned.
| Resource type | Count | Notes |
|---|---|---|
aws_vpc | 1 per env | DNS hostnames enabled; DNS support enabled |
aws_subnet (private) | 2 per env | One per AZ; no map_public_ip_on_launch |
aws_db_subnet_group | 1 per env | Spans both subnets; used by the data module |
aws_security_group (Aurora) | 1 per env | Inbound from VPC CIDR on 5432 |
Resources absent from the module: aws_internet_gateway, aws_nat_gateway, aws_eip, aws_route_table with a public default route, aws_vpc_endpoint of any kind. The absence is intentional per RAG010.
Aurora security group
| Direction | Protocol | Port | Source | Purpose |
|---|---|---|---|---|
| Inbound | TCP | 5432 | VPC CIDR (intra-VPC) | PostgreSQL access from within the VPC |
| Outbound | All | All | 0.0.0.0/0 | AWS default; no in-VPC destination exists to reach |
The inbound rule admits nothing today because no other resource runs inside the VPC. Aurora is also reachable via the RDS Data API service endpoint, an AWS-managed HTTPS path that does not traverse the security group’s inbound rules. That path is governed by IAM (rds-data:ExecuteStatement on the cluster ARN) per RAG011.
No custom NACLs are provisioned. The default NACL applies; given the route tables carry no public routes, the NACL layer adds no meaningful control.
Service placement
Aurora is the only resource inside the VPC. Every other service runs outside the VPC and is reached from Lambda over public AWS endpoints. Lambda functions have no VPC attachment (vpc_config is not set).
Inside VPC
| Service | How reached |
|---|---|
| Aurora Serverless v2 | Subnet group spanning two AZs. RDS Data API (HTTPS, SigV4) from outside VPC; TCP/5432 unreachable from Lambda |
Outside VPC
| Service | How reached |
|---|---|
| API Lambda | Lambda execution environment. Invoked by API Gateway over AWS-managed integration |
| Authorizer Lambda | Lambda execution environment. Invoked by API Gateway on every authenticated request |
| Ingestion Lambda | Lambda execution environment. Invoked by operator or CI |
| API Gateway HTTP API | AWS-managed regional. Public HTTPS endpoint; TLS terminated here |
| CloudFront + S3 (SPA) | AWS-managed global CDN + regional object store. No VPC association; S3 access via Origin Access Control |
| AWS Bedrock (Titan, Haiku-class) | AWS-managed regional endpoint bedrock-runtime.us-east-1.amazonaws.com; IAM-authed HTTPS. The request is issued to that regional endpoint, but generation runs through a cross-region inference profile, which may execute it in any region in the us. group. The Lambda’s IAM policy grants foundation-model ARNs for every region the profile can route to (RAG007). Prompt content is therefore not confined to us-east-1 |
| Secrets Manager | AWS-managed regional. secretsmanager.us-east-1.amazonaws.com; IAM-authed HTTPS; read at cold start |
| CloudWatch Logs and Metrics | AWS-managed regional. Public endpoint; Lambda Powertools emits structured logs and EMF metrics |
| X-Ray | AWS-managed regional. Public endpoint; Lambda Powertools instruments traces |
| RDS Data API endpoint | AWS-managed service plane. HTTPS; IAM-authed; application path into Aurora that requires no TCP from inside the VPC |
Auth and authz boundary. The Authorizer Lambda creates an authorization boundary at the API Gateway layer. API Gateway invokes it before routing any request to the API Lambda. The Authorizer Lambda calls Aurora via the RDS Data API to validate the bearer token against the key store. If the token is absent, expired, or invalid, API Gateway returns 401 or 403 and the API Lambda is never invoked. This boundary is documented as B1 in the trust boundary inventory.
On “public endpoints.” Every AWS service listed above (Bedrock, Secrets Manager, CloudWatch, X-Ray, RDS Data API) is reached by Lambda via an HTTPS endpoint with a public DNS name. These calls travel over the AWS networking fabric; they do not traverse a home-internet path, but they do use public IP space. No VPC gateway endpoint and no PrivateLink interface endpoint is provisioned. The cost rationale is in RAG010.
Data-flow paths
Query path
LikeC4 dynamic view could express this, but a sequence diagram makes the Authorizer interception and the two-Bedrock-hop order clearer in a single diagram. Using mermaid sequenceDiagram as the LikeC4 fallback per the diagram rule.
TLS is terminated at API Gateway for the API call and at the CloudFront edge for SPA asset delivery. Lambda-to-service calls use HTTPS with SigV4 request signing. The API Gateway-to-Lambda invocation is AWS-managed and does not pass through a user-controlled network path.
The RDS Data API hop: Lambda sends an HTTPS ExecuteStatement request to rds-data.us-east-1.amazonaws.com. AWS routes that call to the Aurora cluster through its own service plane. The call does not originate from inside the VPC, and the Aurora security group’s TCP inbound rules are not involved. Authorization is IAM (rds-data:ExecuteStatement on the cluster ARN), enforced by AWS before the call reaches the cluster.
Ingestion path
LikeC4 dynamic view could express this. Using mermaid sequenceDiagram for the same reason as the query path: the local-vs-Lambda conditional and the external Hugging Face hop are cleaner in a sequence diagram than in a LikeC4 dynamic view.
Ingestion runs outside the VPC whether executed locally or as a Lambda invocation. CI runners are GitHub-hosted and reach AWS via an OIDC-federated IAM role; they never enter the VPC. See RAG011 for the data-access contract difference between operator tooling and the application Lambda path.
Observability path
All observability traffic leaves Lambda directly to public AWS service endpoints. No VPC-side flow logs and no network tap are provisioned. The absence of VPC flow logs follows from RAG010: no ENIs emit traffic inside the VPC for flow logs to capture.
Cross-environment isolation
QA and Prod are separate VPCs in a single AWS account.
| Isolation property | Mechanism | Level |
|---|---|---|
| Network-layer isolation between QA and Prod | Separate VPCs; no peering; no Transit Gateway | Network |
| Data isolation | Separate Aurora clusters, Secrets Manager ARNs, S3 buckets | Resource |
| IAM isolation | Separate Lambda execution roles scoped to per-env resource ARNs | Policy |
| Terraform state isolation | Separate S3 state keys; separate S3-native lock files (per-environment) | Operational |
| Deployment gate | QA auto-deploys; Prod requires manual approval | Process |
What is not isolated at the network layer, because separation is only account-level:
- IAM namespace: a misconfigured IAM policy could grant cross-environment access. Per-env role ARNs and resource ARN conditions are the control, not network separation.
- Account-level service quotas: Lambda concurrency, Bedrock model RPM, and API Gateway throttles are account-level. A runaway QA workload can consume quota that Prod depends on.
- Bedrock model access: model availability and regional quotas are account-level, not per-VPC.
- CloudFront ACM certificate ARNs: if a wildcard certificate covers both environments, its ARN is shared across them.
- AWS Budgets alarms: billing is account-wide, and per-environment attribution depends on cost allocation tags.
The migration path to per-account isolation is documented in RAG001.
Failure modes
| Failure | Affected path | Unaffected | Mitigation / notes |
|---|---|---|---|
| Single-AZ Aurora subnet loss | RDS Data API calls during failover (~30-60 s) | Lambda, API Gateway, CloudFront, Bedrock | Aurora Serverless v2 auto-fails over to second AZ; Lambda-layer retries absorb brief window |
| Both AZs fail (regional Aurora outage) | All /query requests return 500 | API Gateway, Lambda, CloudFront + S3 (SPA still served) | No cross-region failover configured; accepted risk at demo scale |
| CloudFront edge PoP issue | SPA asset load for affected users | API path (API Gateway is regional, not edge-distributed) | Recovery outside operator control; no topology change resolves it |
| API Gateway throttle (burst limit) | 429 before Lambda is invoked | Aurora, Lambda execution env | 4XXError metric on API GW stage captures spike; POST /query has a route-level throttle at 1 req/s steady-state, burst 5, while ANY /{proxy+} is unthrottled, and it is recorded under Controls in place in the security assessment |
| Bedrock regional outage (us-east-1) | Embedding and generation; all /query requests fail | Aurora, API Gateway, Lambda invocation, CloudFront | No cross-region failover; accepted risk |
| RDS Data API unavailability | All database calls from Lambda regardless of Aurora health | Aurora TCP/5432 (unreachable from Lambda anyway) | Known constraint of the chosen data-access contract per RAG011 |
Operational surface
Inspecting the VPC
# List VPCs by environment tag
aws ec2 describe-vpcs \
--filters "Name=tag:Environment,Values=qa" \
--query 'Vpcs[*].{VpcId:VpcId,CIDR:CidrBlock,State:State}'
# List subnets in a VPC
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=<vpc-id>" \
--query 'Subnets[*].{SubnetId:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock}'
# Confirm no Internet Gateway is attached
aws ec2 describe-internet-gateways \
--filters "Name=attachment.vpc-id,Values=<vpc-id>"
# Expected: empty Attachments array
# Confirm no NAT Gateways in the VPC
aws ec2 describe-nat-gateways \
--filter "Name=vpc-id,Values=<vpc-id>"
# Expected: empty, or only entries in deleted state
# Inspect Aurora security group inbound rules
aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=<vpc-id>" \
--query 'SecurityGroups[*].{GroupId:GroupId,Name:GroupName,Ingress:IpPermissions}'
# Confirm no VPC endpoints provisioned
aws ec2 describe-vpc-endpoints \
--filters "Name=vpc-id,Values=<vpc-id>"
# Expected: empty
To confirm that no public path exists to the Aurora cluster: in the AWS Console, go to VPC > Reachability Analyzer. Because no Internet Gateway is attached, an analysis sourced from an internet gateway cannot be constructed. The more informative check is to verify that Lambda has no ENI inside the VPC:
aws lambda get-function-configuration \
--function-name <env>-rag-api \
--query 'VpcConfig'
# Expected: null or empty SubnetIds and SecurityGroupIds
Log groups
| Log group | Fields of interest |
|---|---|
/aws/lambda/<env>-rag-api | cold_start, xray_trace_id, RDS Data API client errors |
/aws/lambda/<env>-rag-authorizer | cold_start, RDS Data API error on token lookup |
/aws/lambda/<env>-rag-ingestion | RDS Data API and Bedrock call durations |
| API Gateway access logs (if enabled) | sourceIp, requestId, status, responseLatency |
CloudWatch Logs Insights query to surface RDS Data API errors in the API Lambda log group:
fields @timestamp, @message
| filter @message like /rds-data|RDS Data API|ExecuteStatement/
| sort @timestamp desc
| limit 50
VPC Flow Logs are not enabled. No ENIs emit traffic inside the VPC today; see RAG010. If troubleshooting intra-VPC connectivity becomes necessary after a future architecture change, enable VPC Flow Logs on the VPC and publish to CloudWatch Logs or S3 before diagnosing.
When this page changes
This page must be updated when any of the following occur:
- The reversal procedure in RAG010 fires: NAT Gateway, PrivateLink endpoints, or a new in-VPC workload is introduced. The subnet layout, route tables, security group rules, and data-flow paths all change.
- RAG011 is superseded: the application data-access path to Aurora changes, which changes the query-path diagram and the service placement table.
- A new AWS service is added to the application. Add it to the service placement table.
- A second region is added. The single-region assumption in this document is pervasive.
- The AWS account separation described as a migration path in RAG001 is executed. The cross-environment isolation section changes substantially.
Changes arising from RAG010 reversal must also update architecture/cost and trigger a re-review of the security assessment and trust boundaries.
© 2026 Benjamin Arunski