AWS-Signature-V4

 view release on metacpan or  search on metacpan

eg/README.md  view on Meta::CPAN


A JSON API, where the operation is chosen by a header
(`X-Amz-Target`), signed like all the others. It lists the DynamoDB tables
of a region.

```shell
./eg/06-dynamodb-json-api.pl
./eg/06-dynamodb-json-api.pl eu-west-1
```


## 07-rolesanywhere-x509.pl

The X.509 variant: instead of a secret key, the request is signed with
the private key of a certificate, as IAM Roles Anywhere wants, to get
temporary credentials. `KEY_TYPE` is `RSA` (default) or `ECDSA`;
`CHAIN_FILE` (a PEM bundle of intermediate CAs) and `KEY_PASSWORD` (for
encrypted keys) are optional. `TRUST_ANCHOR_ARN`, `PROFILE_ARN` and
`ROLE_ARN` are required, except with `DRY_RUN=1`, where placeholders
stand in just to show the shape of the request. Beside the
`Authorization` header, the request carries the certificate in
`X-Amz-X509` and the chain, if any, in `X-Amz-X509-Chain`.

```shell
CERT_FILE=cert.pem KEY_FILE=key.pem \
   TRUST_ANCHOR_ARN=arn:aws:rolesanywhere:... PROFILE_ARN=arn:aws:rolesanywhere:... \
   ROLE_ARN=arn:aws:iam::... ./eg/07-rolesanywhere-x509.pl

DRY_RUN=1 KEY_TYPE=ECDSA CERT_FILE=cert.pem KEY_FILE=key.enc.pem \
   KEY_PASSWORD=secret CHAIN_FILE=ca-bundle.pem ./eg/07-rolesanywhere-x509.pl
```

The session JSON is the only thing on the standard output — the
algorithm, the status line, any error body and the `DRY_RUN=1` dump of
the request all go to the standard error — so the program can be used as
`> session.json` or piped into `jq`. A dry run obtains no session and so
writes nothing there either. A failed request prints nothing at all on
the standard output, rather than
an error body where a session was expected: `HTTP::Tiny` reports a
request that never reached AWS as status 599 with the reason, as plain
text, in the body, and that saved under the name of a session would be a
failure kept as if it were credentials. The exit status is non-zero
either way. Mind that what does get saved holds temporary credentials,
so the file deserves the same care as a private key.

`AWS_REGION` is checked here too, for the same reason as in 02 to 05 and
10: it lands in the host name, and a `/` would send the certificate and
its signature somewhere else, with whatever answered read back as a
session.

Check the shape of the `CreateSession` request (path and body) against
the current IAM Roles Anywhere API reference before relying on it: it was
written from memory and has not been tried against AWS.


## 08-x509-custom-signer.pl

With the X.509 variant the private key does not have to be given to the
module: a `signer` function receives the bytes to sign and returns the
signature. That is how to use keys that cannot leave an HSM or a KMS. Here
the function just uses CryptX by itself and logs what it does, to show
where the hook is.

```shell
CERT_FILE=cert.pem KEY_FILE=key.pem ./eg/08-x509-custom-signer.pl
```


## 09-inspect-a-signature.pl

No network, no secrets: it reproduces the example of the AWS documentation
and prints all the intermediate values (canonical request, string to sign,
authorization header), which is what helps when AWS answers
`SignatureDoesNotMatch`. The time is fixed, so the output is always the
same, and the program tells whether the signature matches the one in the
documentation.

```shell
./eg/09-inspect-a-signature.pl
```


## 10-s3-content-encoding-probe.pl

Not a usage example but a diagnostic probe, kept here because it is built
out of the same pieces as 05. With `streaming`, `sign` adds `aws-chunked`
to whatever `Content-Encoding` the caller already set, and the authorities
disagree on the order: this uploads a gzipped object and reports whether
S3 takes the one the module sends (see the note in `TODO.md`).

It writes two objects to the bucket it is given, a control without any
`Content-Encoding` and the real case with gzip, so that a rejection can
be told apart from a wrong bucket, region or set of credentials. It reads
both back, checks the bytes round-trip, and deletes them again unless
`KEEP=1`. The key prefix defaults to `aws-sigv4-probe/`.

Because it deletes what it uploads, it first checks that both keys hold
nothing and refuses to run otherwise, treating anything but a clear "not
there" as occupied. That check needs `s3:ListBucket` on the bucket:
without it S3 answers a HEAD on a key that does not exist with 403
rather than 404, a free key cannot be told from a forbidden one, and the
program stops and says so. The bucket, the region and the key prefix are also
checked before use: they are pasted into the URL, and a `/` in the bucket
or the region would move the host elsewhere and send the signed request,
session token included, to whatever is there.

```shell
./eg/10-s3-content-encoding-probe.pl my-bucket
AWS_REGION=eu-west-1 ./eg/10-s3-content-encoding-probe.pl my-bucket scratch/probe-
KEEP=1 ./eg/10-s3-content-encoding-probe.pl my-bucket
DRY_RUN=1 ./eg/10-s3-content-encoding-probe.pl my-bucket
```

A verdict is only reached when the run earns it. "Accepted" needs more
than a 200: S3 must also have stored the object as `Content-Encoding:
gzip`, having taken the `aws-chunked` token off, and have given the bytes
back unchanged. "Rejected", which tells you to change the order in the
signing code, needs S3 itself to have turned the upload down — a 4xx
carrying an S3 error code, and not one of the 401 and 403 that talk
about the credentials rather than the header. Everything else, a 503, a
dropped connection, a key that expired halfway through, is reported as



( run in 0.796 second using v1.01-cache-2.11-cpan-eca53f54451 )