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.
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.
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.