← all lessons

· 2026-07-13 · authenticationawselasticsearchsigv4sts

AWS SigV4 request signing

The idea

Many AWS services — a managed Elasticsearch/OpenSearch cluster, for example — don't accept a plain 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, and puts the result in an Authorization header. The server recomputes the same hash on its side. If anything about the request changes after signing — a header gets rewritten, the body gets re-encoded — the hashes don't match and the request is rejected with a signature-mismatch error. Nothing was hacked; the request was just changed in transit.

On servers running inside AWS (EC2, ECS, Lambda), the signer doesn't use a long-lived access key. It asks STS (Security Token Service) for temporary credentials tied to the instance or 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 travel 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 automatically:

$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 has to cover the exact bytes you send, sign the request last — right before it goes out — not earlier in the pipeline that builds it.

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