Skip to content

IAM Authentication Flow in Detail

Scope: VPC Lattice service/resource APIs and AWS Gateway API Controller; verify the selected release and installed CRDs. Last Updated: September 13, 2026

What This Document Covers

  • The four stages a request passes through under Lattice IAM Auth — credential acquisition, request signing, Lattice verification, policy evaluation
  • Distinguish credential, signing, policy and network failures using evidence; no failure-frequency ranking is claimed
  • What it means to move from connection-scoped mutual authentication to request-scoped signature verification

Why Request Signing?

App Mesh's mTLS checks each side's certificate once, when the connection is established, and trusts that connection from then on. Identity is bound to the connection.

Lattice made a different choice: sign every request and verify every request. Understanding why makes the later constraints follow naturally.

IAM request signing is one documented identity mechanism shared by AWS compute clients. Lambda can also use certificates with appropriate secret/rotation handling, so do not claim that client certificates there are impossible or use that as an explanation of undocumented AWS design decisions.

So Lattice chose to reuse "the identity system every AWS compute platform already has," and that system operates per API request, not per connection. Everything else in this document follows from that choice.

The Four-Stage Sequence

Mapping where a 403 can originate at each stage:

Stage 1 — Credential Acquisition

SigV4 signing needs an access key, a secret key, and a session token. A Pod obtains these in one of two ways.

EKS Pod Identity vs IRSA

ItemEKS Pod Identity (recommended)IRSA
Trust relationship setupMediated by the EKS Auth API. Role trust policy references the pods.eks.amazonaws.com service principalRegister a per-cluster OIDC provider in IAM and write OIDC conditions into the Role trust policy
Work per additional clusterRoles can be reusedRegister an OIDC provider and amend trust policies for every cluster
Credential delivery pathPod Identity Agent (a node DaemonSet) serves them on a link-local addressProjected service account token → SDK calls AssumeRoleWithWebIdentity
Binding mechanismServiceAccount ↔ Role association managed via the EKS APIServiceAccount annotation eks.amazonaws.com/role-arn
Session tagsPod/cluster context can be passed as session tags → usable in conditional authorizationLimited
PrerequisitesPod Identity Agent add-on installed + node Role has AssumeRoleForPodIdentityOIDC provider association

The practical reason to prefer Pod Identity is multi-cluster. One of the main motivations for adopting Lattice is cross-cluster communication, and IRSA requires registering an OIDC provider per cluster and managing Role trust policies for as many clusters as you have. Pod Identity does not carry that burden.

The STS temporary credential dependency

Both approaches ultimately arrive at temporary credentials issued by STS. This is an important property of the architecture.

  • Credentials expire. The SDK caches them and refreshes before expiry, but the refresh path must be alive.
  • If the configured credential provider cannot refresh, signing can fail locally; an already-signed expired request can also be rejected. IRSA uses STS, while Pod Identity obtains credentials through the Agent/EKS Auth path.
  • In other words, STS becomes a dependency of the East-West data path. This mirrors where SPIRE Server sat in AS-IS, but the owner shifts from the customer to AWS (documents 05 and 06).

The latency implication was covered in document 02 — if refresh blocks the request path, it appears in the p99 tail.

Stage 2 — Request Signing

Canonical request → signing key → Authorization header

SigV4 signing proceeds in three steps.

First, build the canonical request. Canonicalize method, path, query and signed headers. For Lattice, include x-amz-content-sha256: UNSIGNED-PAYLOAD; payload signing is not supported. Use HTTPS to protect the body in transit and add application-level integrity when required.

Second, derive the signing key. Starting from the secret key, apply HMAC-SHA256 four times in sequence: date → region → service name → terminating string. The service name for Lattice is vpc-lattice-svcs.

Because this service name is an input to the signature itself, getting it wrong means the signature will not verify. It is easy to confuse with vpc-lattice (the service name for the Lattice control plane API), but data plane requests must be signed with vpc-lattice-svcs. This is consistent with the service DNS name itself, which takes the form <service>-<id>.<hash>.vpc-lattice-svcs.<region>.on.aws.

Third, attach the headers. The Authorization header carries the algorithm, credential scope, the list of signed headers (SignedHeaders), and the signature value; x-amz-date carries the request time; and when using temporary credentials, x-amz-security-token carries the session token.

Three practical pitfalls

① The Host header is signed — beware with custom domains

In SigV4, the Host header is always included in the signature. Which host the request is addressed to is bound into the signature.

This becomes a problem with custom domains. If you attach a customer domain (api.internal.example.com) to a Lattice service, the client sends requests to that domain and therefore signs with Host: api.internal.example.com. If the value the verifying side expects differs, the signature does not match. Conversely, if you signed with the Lattice-generated domain but the actual request's Host is the custom domain, it also does not match.

The core rule: the Host value used when signing must match the actual request's Host header. When introducing a custom domain, explicitly confirm which value your signing logic uses. This problem surfaces at the moment you attach the custom domain, not at the start of migration, which is why it is easy to miss.

② x-amz-date clock skew

The general AWS SigV4 guide says that requests in most cases must arrive within five minutes of their timestamp. Keep clocks synchronized and inspect the actual expiry/skew error; this is not a separately measured Lattice-specific guarantee.

That makes node clock synchronization a precondition for authentication. On EC2/EKS nodes using the Amazon Time Sync Service this is usually a non-issue, but it becomes a problem when:

  • Node or hybrid-host time synchronization is misconfigured
  • An application signs with an incorrect timestamp or timezone
  • The host resumes with clock drift

This failure is intermittent and node-scoped, making it awkward to diagnose. If "only Pods on one particular node get 403s," check clock synchronization first.

③ Intermediate proxies mutating headers — sign at the last hop

Because the signature is bound to request content, if anything modifies a signed element after signing, verification breaks.

Things that actually cause this:

  • Proxies that change signed paths or the actual Host value
  • Proxies that add/remove query parameters or alter values/encoding; merely reordering equivalent parameters does not necessarily change the canonical query
  • Body mutation is not detected by Lattice SigV4 when using the required UNSIGNED-PAYLOAD; protect it with TLS and application controls

The rule: sign at the last hop before Lattice. No layer that modifies the request may sit between signing and Lattice.

This matters especially when signing via an egress proxy. The aws-samples reference implementation demonstrates the pattern — a sigv4proxy sidecar listening on 8080, with an init container using iptables to redirect only traffic destined for 169.254.171.0/24 (the Lattice range) to local port 8080. The proxy signs and the request goes straight out to Lattice, so nothing sits in between to mutate it. Avoid configurations where a signed request is then handled by another proxy.

Stage 3 — Lattice Verification

On an HTTPS listener, Lattice terminates TLS, parses the headers, recomputes the signature in the Authorization header, and compares.

The single most important constraint of this architecture hides here.

Signature verification requires reading headers, and reading headers requires terminating TLS.

TLS Passthrough does not terminate TLS, so Lattice cannot see the Authorization header and cannot authenticate the caller's SigV4 request signature. This is the constraint discussed in document 06. It does not prohibit every auth policy: TLS listeners support policies limited to anonymous principals, which do not establish authenticated caller identity.

The controller documents policy attachments for Gateway, HTTPRoute and GRPCRoute; check the installed CRD for supported attachment targets. That controller restriction is not a statement that every VPC Lattice TLS auth policy is rejected.

Documented TLS behavior

AWS permits TLS-passthrough auth policies based on anonymous principals, without authenticated SigV4 identity or HTTP header/path inspection. TLS listeners require a custom domain matching plaintext SNI and TCP target groups; ECH/ESNI is not supported. This repository does not recommend wildcard-principal policy examples. See the TLS listener reference.

Authenticated request signing and anonymous network-context authorization are different controls. Design the endpoint authentication and the applicable service-network/service policies explicitly.

One more pitfall: auth policies are only active when authType is AWS_IAM. With NONE, an attached policy is inert. This is the most common cause of "I attached a policy but anyone can still get through."

Stage 4 — Policy Evaluation

For an authenticated principal, evaluate the caller permissions and each applicable Lattice resource policy. Only resources configured with AWS_IAM enforce their auth policy; NONE skips that resource policy. Explicit denies and other IAM controls still apply. Do not turn the three-policy diagram into a universal rule for anonymous or differently configured requests.

PolicyAttached toQuestion it answersOwnerGateway API resource
identity-based policyThe caller's IAM Role"Does this Role have permission to perform vpc-lattice-svcs:Invoke?"Application / platform team— (directly in IAM)
service network auth policyService Network"Is this principal allowed into this service network?" (coarse-grained)Network / cloud administratorIAMAuthPolicyGateway
service auth policyLattice Service"Is this principal allowed to call this service?" (fine-grained)Service-owning teamIAMAuthPolicyHTTPRoute/GRPCRoute

For service invocation auth policies, the action is vpc-lattice-svcs:Invoke. Resource configurations use a separate access model and do not inherit service-network auth policies.

Available condition keys

These keys can be used as conditions in auth policies. Which keys are present at evaluation time depends on the protocol and on whether the request was SigV4-signed.

Condition keyFilters by
vpc-lattice-svcs:PortThe service port the request was made to
vpc-lattice-svcs:RequestMethodThe request method
vpc-lattice-svcs:RequestPathThe path portion of the request URL
vpc-lattice-svcs:RequestHeader/<header-name>A header name-value pair in the request
vpc-lattice-svcs:RequestQueryString/<key-name>A query string key-value pair in the request URL
vpc-lattice-svcs:ServiceArnThe ARN of the target Lattice service
vpc-lattice-svcs:ServiceNetworkArnThe ARN of the service network
vpc-lattice-svcs:SourceVpcThe VPC the request originated from
vpc-lattice-svcs:SourceVpcOwnerAccountThe account owning the source VPC

IAM global condition keys such as aws:PrincipalOrgID and aws:PrincipalTag/<key> can also be used alongside these.

Needs verification

The list above is compiled from the service authorization reference and policy examples in the Gateway API Controller documentation. Lattice gains features over time, so confirm the current list in that reference before finalizing a design.

Having path, method, and header conditions is practically useful — you can enforce a rule like "only these Roles may call POST /refund on the payments service" outside application code. That said, putting path-based authorization into auth policies means API changes trigger policy changes, so decide deliberately which layer expresses authorization.

The dominant 403 failure pattern

A missing vpc-lattice-svcs:Invoke permission is one documented cause of an authenticated request failing. No incident-frequency dataset was supplied, so it is not ranked as the most common cause.

It is common because it is counterintuitive. It is natural to think "the service's auth policy allows this Role, so we're done" — but the calling Role itself also needs Invoke permission. A resource policy alone does not get you through.

Here is the actual error message from the reference implementation:

text
AccessDeniedException: User: arn:aws:sts::111122223333:assumed-role/eksctl-...-Role1-yz1hNJittmXj/1726632845600682009
is not authorized to perform: vpc-lattice-svcs:Invoke
on resource: arn:aws:vpc-lattice:us-west-2:111122223333:service/svc-0b13d4b53748cbdc7/catalogdetail
because no identity-based policy allows the vpc-lattice-svcs:Invoke action

The last clause — because no identity-based policy allows... — is the key to diagnosis. The message tells you which policy is missing, so read it first when you hit a 403.

403 diagnosis order

OrderWhat to checkHow
1The last clause of the error messageno identity-based policy → caller Role permissions; otherwise → auth policy
2Lattice access logs and the returned errorCorrelate request ID, caller logs, policy and network evidence
3Was the request actually signed?An unsigned request and a failed signature are different problems. Check egress proxy logs
4Is authType AWS_IAM?With NONE, policies are inert
5Node clockIf only one node fails, suspect x-amz-date skew
6Host headerIf you just introduced a custom domain, start here

An easily missed pitfall: calling the k8s Service DNS directly bypasses authorization

The AWS Gateway API Controller documentation states this explicitly:

IAMAuthPolicy can only perform authorization for traffic that travels through Gateways, HTTPRoutes, and GRPCRoutes. The authorization will not take effect if the client sends traffic directly to the k8s service DNS.

Calling http://proddetail.prodcatalog-ns.svc.cluster.local directly bypasses the Lattice data path, so its auth policy does not evaluate that request. This is a design boundary, not a measured ranking of incident causes.

During the migration window, when the AS-IS path (direct in-cluster calls) and the TO-BE path (via Lattice) coexist, there are simultaneously paths where authorization applies and paths where it does not. You need compensating controls such as NetworkPolicy to block direct in-cluster calls, and that belongs in the migration plan.

AS-IS Comparison

ItemAS-IS: App Mesh + SPIRE mTLSTO-BE: Lattice IAM Auth
Authentication scopeConnection — once at connection setupRequest — every request
DirectionalityBidirectional mutual authentication (both client and server prove identity)Unidirectional — the client proves itself. The server proves only via its TLS server certificate
Form of identitySPIFFE ID in an X.509 SVID (a URI)IAM Role ARN / assumed-role session ARN
Means of proofShort-lived X.509 certificate (proof of private key possession)SigV4 signature (proof of secret key possession)
Who verifiesThe peer workload's EnvoyLattice (AWS-managed infrastructure)
Root of trustA SPIRE Server CA operated by the customerAWS IAM / STS
Where authorization happensThe receiving Envoy's authorization filterLattice's triple policy evaluation
Where TLS terminatesThe receiving Pod's EnvoyLattice (HTTPS listener)
On credential expirySVID auto-renewal (SPIRE Agent)STS credential auto-refresh (SDK)
ObservabilityEnvoy metrics + logsLattice access logs (no spans)

The two most important rows

The "Directionality" row is the crux of the review board issue. mTLS had the server prove its identity too. Under Lattice IAM Auth, server-side identity proof is at the level of a TLS server certificate, and there is no step that confirms "is this really the service that team operates" within a workload identity system. Details are in document 05.

The client-to-Lattice HTTPS connection terminates at Lattice. The target protocol is a separate setting: HTTP is plaintext on that segment, and HTTPS adds encryption without Lattice validating the target certificate. Endpoint TLS/mTLS through passthrough changes the trust boundary and cannot expose encrypted HTTP SigV4 identity to Lattice.

Summary

  • Lattice chose request signing to reuse the IAM/STS foundation that EKS, ECS, EC2, and Lambda already share. That system operates per request, not per connection.
  • The signing service name is vpc-lattice-svcs, and since it is an input to the signature, getting it wrong means verification fails.
  • Three practical pitfalls: the Host header is signed (beware with custom domains), x-amz-date 5-minute skew (node clock sync), and sign at the last hop (no mutating proxies in between).
  • TLS passthrough cannot authenticate encrypted HTTP SigV4 headers, but AWS supports anonymous network-context auth policies for that path.
  • Missing Invoke permission is one possible 403 cause; use the returned reason and correlated logs rather than an unsupported frequency ranking.
  • Calling the k8s Service DNS directly bypasses auth policy evaluation. Compensating controls are needed during migration.

Next: Foundations — Link-Local and SNI goes one layer below "why you must terminate TLS to see headers."

References