Lambda (Account A) → API Gateway (Account B) — Zero public internet exposure
443 → sg-vpce-endpoint only.
All other egress denied. No inbound rules required.
443 from sg-lambda-caller only.
Creates a precise allow-list — no other source can reach the ENIs.
aws:sourceVpce matches the
specific Endpoint ID in Account A. Locks the API to a single ingress point.
execute-api:Invoke
via cross-account IAM or resource policy.
execute-api:Invoke. The Lambda role can optionally be
scoped to specific routes (/prod/GET/items).
sg-lambda-caller can reach the VPC Endpoint ENIs on port 443.
No IP-range rules — group-to-group reference ensures only the Lambda SG qualifies.
Eliminates lateral movement from within the VPC.
Project = [PROJECT]).
Prevents endpoint re-use to reach unintended APIs even within the same region.
aws:sourceVpce
to match Account A's endpoint ID. Even with valid SigV4 credentials, requests
not originating from that endpoint are rejected at the API layer.
authorizationType: AWS_IAM on every route.
Requests must carry a valid HMAC-SHA256 signature derived from Account A's Lambda
execution role. Replay protection via timestamp skew window (±5 min).
Cross-account Invoke grant scoped to specific routes.
// Attach to the Interface VPC Endpoint { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowSpecificAPI", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::ACCT-A::role/lambda-role" }, "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:REGION:ACCT-B:API-ID/*", "Condition": { "StringEquals": { "aws:resourceTag/Project": "[PROJECT]" } } }, { "Sid": "DenyAll", "Effect": "Deny", "Principal": "*", "Action": "execute-api:*", "Resource": "*", "Condition": { "StringNotEquals": { "aws:resourceTag/Project": "[PROJECT]" } } } ] }
// Applied to Private API Gateway in Account B { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyPublicAccess", "Effect": "Deny", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:REGION:ACCT-B:API-ID/*", "Condition": { "StringNotEquals": { "aws:sourceVpce": "vpce-0abc1234def56789" } } }, { "Sid": "AllowFromVPCEndpoint", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::ACCT-A::role/lambda-role" }, "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:REGION:ACCT-B:API-ID/prod/*", "Condition": { "StringEquals": { "aws:sourceVpce": "vpce-0abc1234def56789" } } } ] }
# Uses boto3 AWSRequest + SigV4Auth — no manual HMAC import boto3, requests from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import get_credentials session = boto3.Session() credentials = session.get_credentials() region = "us-east-1" url = "https://API-ID.execute-api.REGION.amazonaws.com/prod/items" req = AWSRequest(method="GET", url=url) SigV4Auth(credentials, "execute-api", region).add_auth(req) response = requests.get( url, headers=dict(req.headers) # Authorization, X-Amz-Date, X-Amz-Security-Token )
// Add inline policy to Lambda role in Account A { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowAPIInvoke", "Effect": "Allow", "Action": "execute-api:Invoke", "Resource": [ // Scope tightly — method and route, not wildcard "arn:aws:execute-api:REGION:ACCT-B:API-ID/prod/GET/items", "arn:aws:execute-api:REGION:ACCT-B:API-ID/prod/POST/items" ] } ] } // Never use "Resource": "*" for execute-api:Invoke // Scope to the minimum set of stage/method/route combos