Home / AWS

AWSHow-toAdvanced

Cross-account service-to-service networking with VPC Lattice: service networks, IAM auth policies, SigV4 clients and the security group rules people miss

Connect services across accounts and overlapping VPC CIDRs with VPC Lattice. Set up a shared service network, IAM auth policies at two layers, SigV4-signed clients and the target and association security groups.

Deepak Bhardwaj6 min read

Applies toAmazon VPC Lattice, AWS RAM, IAM, EC2/ECS/EKS/Lambda targets, AWS CLI v2, Python (botocore)

Goal

Team A runs an orders HTTP API in account 111111111111. Services in other accounts need to call it. The VPC CIDRs overlap, so Transit Gateway or peering would need NAT, and nobody wants to run a PrivateLink endpoint service and NLB for every API. VPC Lattice gives each service a DNS name that any associated VPC can reach. Access is controlled by IAM auth policies on the service network and on the service.

When Lattice fits, and when it doesn't

Need Lattice Alternative
HTTP/HTTPS/gRPC between many services and accounts, overlapping CIDRs Good fit PrivateLink per service
Per-request IAM authorisation (principal, org, method, path) Built in App-level auth
Path or header routing, weighted canaries across target groups Built in ALB per service
Raw TCP, very high throughput, non-HTTP protocols Check Lattice TLS passthrough/TCP listener support for your case NLB + PrivateLink
Full network reachability between VPCs Not the goal Transit Gateway / Cloud WAN

Check the current Lattice quotas (services per network, associations per VPC, request and bandwidth limits) and pricing before committing a large estate.

How the pieces fit

  • Service network: the shared boundary. VPCs and services associate with it.
  • Service: a listener plus rules that forward to target groups. It gets a generated DNS name (or a custom domain).
  • VPC association: makes every service in the network reachable from a VPC. It can carry security groups.
  • Auth policies: IAM resource policies at the network level and the service level. When AWS_IAM auth is on at both levels, both have to allow the request.

Step 1: producer account, target group and service

aws vpc-lattice create-target-group --name orders-tg --type INSTANCE \
  --config '{"port":8080,"protocol":"HTTP","vpcIdentifier":"vpc-0prod","healthCheck":{"enabled":true,"path":"/healthz","protocol":"HTTP"}}'

aws vpc-lattice register-targets --target-group-identifier tg-0abc \
  --targets id=i-0aaa,port=8080 id=i-0bbb,port=8080

aws vpc-lattice create-service --name orders --auth-type AWS_IAM \
  --query "{Id:id,Arn:arn,Dns:dnsEntry.domainName}"

aws vpc-lattice create-listener --service-identifier svc-0abc --name https \
  --protocol HTTPS --port 443 \
  --default-action '{"forward":{"targetGroups":[{"targetGroupIdentifier":"tg-0abc","weight":100}]}}'

Target groups can also be IP (pods, ECS tasks), LAMBDA or ALB. On EKS, the AWS Gateway API Controller creates the target groups and services from HTTPRoute objects.

The target security group rule

Lattice traffic reaches targets from link-local source addresses, not from the client's IP. The target security group has to allow the VPC Lattice managed prefix list:

PL=$(aws ec2 describe-managed-prefix-lists \
  --filters Name=prefix-list-name,Values=com.amazonaws.eu-west-1.vpc-lattice \
  --query "PrefixLists[0].PrefixListId" --output text)

aws ec2 authorize-security-group-ingress --group-id sg-0targets \
  --ip-permissions "IpProtocol=tcp,FromPort=8080,ToPort=8080,PrefixListIds=[{PrefixListId=$PL}]"

If you use IPv6, there's a separate IPv6 prefix list. Missing this rule is the most common reason targets show as unhealthy.

Tip

The client IP never reaches the target as the TCP source. If the service needs caller identity, read the headers Lattice adds for HTTP (for example x-amzn-source-vpc and the x-amzn-lattice-identity details when IAM auth is on). Don't use IP allow lists on the target.

Step 2: the service network and sharing it

The platform team usually owns the network, in a networking account:

aws vpc-lattice create-service-network --name prod-sn --auth-type AWS_IAM

aws ram create-resource-share --name prod-sn-share \
  --resource-arns arn:aws:vpc-lattice:eu-west-1:999999999999:servicenetwork/sn-0abc \
  --principals arn:aws:organizations::999999999999:ou/o-exampleorgid/ou-prod-abcd

The producer shares its service with the networking account the same way, and the networking account associates it:

aws vpc-lattice create-service-network-service-association \
  --service-network-identifier sn-0abc \
  --service-identifier arn:aws:vpc-lattice:eu-west-1:111111111111:service/svc-0abc

Step 3: consumer VPC association

In each consumer account, after accepting the RAM share (sharing within an AWS Organization with RAM trusted access needs no acceptance):

aws vpc-lattice create-service-network-vpc-association \
  --service-network-identifier arn:aws:vpc-lattice:eu-west-1:999999999999:servicenetwork/sn-0abc \
  --vpc-identifier vpc-0consumer --security-group-ids sg-0latticeassoc

The association security group controls which clients in this VPC can reach the service network. Allow inbound 443 from the client security groups or subnets. Once an association has security groups, it must keep at least one. To remove all of them, delete the association and create it again.

Warning

Changing or deleting a VPC association instantly changes reachability for every service in the network from that VPC. Treat associations as production changes and manage them in infrastructure as code with change review.

Step 4: auth policies at two layers

The network policy sets the coarse boundary, for example "only principals in our organisation, from associated VPCs":

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": "*",
    "Action": "vpc-lattice-svcs:Invoke",
    "Resource": "*",
    "Condition": {"StringEquals": {"aws:PrincipalOrgID": "o-exampleorgid"}}
  }]
}

The service policy sets the fine-grained rules: which roles may call which method and path:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": ["arn:aws:iam::222222222222:role/checkout-task"]},
    "Action": "vpc-lattice-svcs:Invoke",
    "Resource": "arn:aws:vpc-lattice:eu-west-1:111111111111:service/svc-0abc/*",
    "Condition": {"StringEquals": {"vpc-lattice-svcs:RequestMethod": ["GET","POST"]}}
  }]
}
aws vpc-lattice put-auth-policy --resource-identifier sn-0abc --policy file://sn-policy.json
aws vpc-lattice put-auth-policy --resource-identifier svc-0abc --policy file://svc-policy.json

Step 5: SigV4-signed clients

With AWS_IAM auth, every request has to be signed for the service name vpc-lattice-svcs. A request that isn't signed gets a 403.

import boto3, requests
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest

url = "https://orders-0abc.7d67968.vpc-lattice-svcs.eu-west-1.on.aws/orders/42"
session = boto3.Session()
req = AWSRequest(method="GET", url=url, headers={"x-amz-content-sha256": "UNSIGNED-PAYLOAD"})
SigV4Auth(session.get_credentials(), "vpc-lattice-svcs", "eu-west-1").add_auth(req)
resp = requests.get(url, headers=dict(req.headers), timeout=5)
print(resp.status_code, resp.text[:200])

For workloads you can't change, put a signing sidecar or proxy in front of them, or set the service to auth type NONE and rely on the network policy and security groups. Know that this trade-off removes per-caller authorisation.

Step 6: observability

aws vpc-lattice create-access-log-subscription --resource-identifier svc-0abc \
  --destination-arn arn:aws:logs:eu-west-1:111111111111:log-group:/lattice/orders

Access logs record the caller's identity, source VPC, response code and target. They're the quickest way to tell an auth denial apart from a target failure.

Failure mapping

Symptom Cause Fix
DNS name doesn't resolve VPC not associated, or the VPC doesn't use Route 53 Resolver Associate the VPC; forward from custom DNS to the VPC resolver
Connection timeout Association security group or client SG egress blocks 443 Allow client → association SG
403 on every request Request not signed, or wrong signing service name Sign for vpc-lattice-svcs
403 for some callers Network or service auth policy doesn't allow the principal Check both policies; both must allow
5xx, targets unhealthy Target SG missing Lattice prefix list, wrong health check path Add prefix list rule; fix health check

Prevent it next time

  • Put the service network, its auth policy and every association in one infrastructure-as-code stack owned by the platform team.
  • Ship a standard signing client library, or a sidecar, so service teams don't each write their own SigV4 code.
  • Turn on access logs for every service from the start, and alarm on 403 and 5xx rates for each service.
  • Add the Lattice prefix list rule to the default target security group module.