← all lessons

· 2026-07-13 · authenticationawselasticsearchsigv4sts

AWS SigV4 request signing

The idea

Many AWS services (like a managed Elasticsearch/OpenSearch cluster) don't accept a simple API key — they require every HTTP request to be signed with SigV4 (Signature Version 4). The signer hashes the method, URL, headers, and body together with a secret key and a timestamp, producing an Authorization header. The server independently recomputes the same hash; if anything about the request changes after signing (a header gets rewritten, the body gets re-encoded), the hashes no longer match and the request is rejected with a signature-mismatch error — even though nothing was "hacked," just mutated in transit.

On servers running inside AWS (EC2/ECS/Lambda), the signer doesn't use a long-lived access key. It uses STS (Security Token Service) to get temporary credentials from the instance/task's IAM role: an access key, a secret key, and a session token, all valid for a short window and refreshed automatically. The session token has to ride along in an X-Amz-Security-Token header — a correctly-signed request with a missing or expired token still gets rejected.

How it shows up

A typical signing hook, wired into WordPress's outgoing HTTP filter so any request to the AWS host gets signed transparently:

$signer = new SignatureV4( 'es', $region );
$credentials = call_user_func( CredentialProvider::defaultProvider() )->wait();
$signed_request = $signer->signRequest( $request, $credentials );
// $signed_request now carries Authorization, X-Amz-Date,
// and (for temporary creds) X-Amz-Security-Token headers.

Because signing must happen on the exact bytes that get sent, sign the request last — right before transmission — not earlier in the request-building pipeline.

Read more

Exercises

  1. Spot the signed headers — make any signed AWS SDK call in a language of your choice with request logging on (e.g. AWS_DEBUG=1 or an SDK's debug/logging option), and find the Authorization, X-Amz-Date, and (if using temporary creds) X-Amz-Security-Token headers in the outgoing request. Done when: you can point to all three headers in the raw request log.
  2. Break a signature on purpose — sign a request, then mutate one header (e.g. change a custom header's value) after signing, and send it. Done when: you get back a signature-mismatch error, confirming the signature covers headers.
  3. Trace a credential chain — read your SDK's default credential provider chain docs and list, in order, the sources it checks (env vars, shared config file, instance metadata, etc.). Done when: you can name the order without looking it up twice.

My notes