# IAM Roles Anywhere: AWS Access With Zero Static Keys

Every environment I have audited has the same ghost.

An IAM user with an access key and a secret key. Created for a script, a cron job, a device that needed to reach AWS. It worked, so nobody touched it again.

Months pass. Sometimes years. The key never rotates. And when someone finally remembers it exists, the harder question shows up: where is it configured? On which server, in which container, in whose laptop, in which pipeline variable? Nobody knows. The person who created it left two roles ago.

That is the real problem with static credentials. Not that they get stolen in some dramatic breach. It's that they quietly outlive everyone's memory of them.

I did not want that ghost in my own house.

So when I built a small agent to run on a Raspberry Pi at home, one that reads my solar data and calls AWS Bedrock to write a daily report, I gave it zero static keys. No access key, no secret, nothing on disk that a `cat` could leak. The Pi authenticates to AWS with a certificate. AWS hands it temporary credentials that expire on their own.

This is how IAM Roles Anywhere works, and how I set it up.

---

## Why static keys are worse at the edge

A device outside AWS is the worst place to keep a long-lived key.

Inside AWS, a role attaches to the resource. An EC2 instance, a Lambda, an ECS task all get temporary credentials from the instance metadata service or the execution role. You never type a secret. The platform rotates it for you.

The moment you step outside, that convenience disappears. A Raspberry Pi, an on-prem server, an IoT gadget. The old answer was to create an IAM user, generate an access key, and paste it into a config file or an environment variable. Now you own a secret. Forever. You have to store it, protect it, remember where it is, and rotate it. Almost nobody does the last part.

IAM Roles Anywhere removes the secret entirely. Instead of proving who you are with a key you have to guard, you prove it with a certificate you can revoke.

---

## The three pieces

IAM Roles Anywhere has three parts, and it clicks once you see how they connect.

1. **Trust anchor.** This is your certificate authority, registered with AWS. It tells AWS: "any certificate signed by this CA is one I should trust." You can point it at AWS Certificate Manager Private CA, or at your own CA.
2. **Profile.** This defines which IAM roles a caller is allowed to assume, and can narrow the session permissions further.
3. **Role.** A normal IAM role, with one difference: its trust policy allows the `rolesanywhere.amazonaws.com` service to assume it, scoped to your trust anchor.

The device presents a certificate signed by the trust anchor. AWS checks the chain, looks at the profile, and issues temporary credentials for the role. That's the whole idea.

Here is the trust policy on my role. Notice it does not trust a user or an account. It trusts the service, and only when the request comes from my specific trust anchor:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "rolesanywhere.amazonaws.com" },
      "Action": [
        "sts:AssumeRole",
        "sts:TagSession",
        "sts:SetSourceIdentity"
      ],
      "Condition": {
        "ArnEquals": {
          "aws:SourceArn": "arn:aws:rolesanywhere:us-east-1:111122223333:trust-anchor/TRUST-ANCHOR-ID"
        }
      }
    }
  ]
}
```

The `aws:SourceArn` condition matters. Without it, the role trusts the Roles Anywhere service in general. With it, the role only accepts sessions that came through your trust anchor and nobody else's. `sts:SetSourceIdentity` and `sts:TagSession` let the certificate's identity flow into CloudTrail, so the session is traceable back to the cert that made it.

---

## The flow: certificate to temporary credentials

On the device, a small binary from AWS called `aws_signing_helper` does the handshake. You wire it into the AWS config with `credential_process`, and boto3 or the CLI call it automatically whenever they need credentials.

This is the entire AWS config on the Pi. There is no key in it, only paths to a certificate and a private key:

```ini
[default]
region = us-east-1
credential_process = /usr/local/bin/aws_signing_helper credential-process \
  --certificate /certs/client.pem \
  --private-key /certs/client.key \
  --trust-anchor-arn arn:aws:rolesanywhere:us-east-1:111122223333:trust-anchor/TRUST-ANCHOR-ID \
  --profile-arn arn:aws:rolesanywhere:us-east-1:111122223333:profile/PROFILE-ID \
  --role-arn arn:aws:iam::111122223333:role/solar-brief
```

The flow, every time the app needs to talk to AWS:

1. boto3 needs credentials, so it runs the `credential_process` command.
2. `aws_signing_helper` reads the client certificate and private key.
3. It signs a request to IAM Roles Anywhere proving it holds the private key for that cert.
4. AWS validates the chain against the trust anchor, checks the profile, and returns temporary credentials.
5. boto3 uses them for Secrets Manager, Bedrock, whatever the role allows. They refresh on their own inside the session window.

Nothing static ever touches the app. The private key sits on a read-only mount, and the container runs as a non-root user. The key is the one local artifact you still have to protect, with file permissions and that read-only mount, but it never leaves the device and it is not an AWS credential. The certificate is not a secret in the usual sense either: on its own it cannot be replayed without the private key, and if either leaks, you revoke the cert at the CA and rotate.

---

## Setting it up

**1. The CA and the client certificate.** You need a CA whose certificate becomes the trust anchor, and a client certificate signed by it. This is where the "you need enterprise PKI" myth scares people off. You don't. For a project like this, OpenSSL is enough:

```bash
# Private key for the device
openssl genrsa -out client.key 2048

# Certificate signing request
openssl req -new -key client.key \
  -subj "/C=PA/O=HomeLab/CN=solar-daily-brief" \
  -out client.csr

# Sign it with your CA
openssl x509 -req -in client.csr \
  -CA HomeLab-Root-CA.pem -CAkey HomeLab-Root-CA.key -CAcreateserial \
  -days 365 -sha256 -out client.pem
```

One thing to get right: Roles Anywhere wants an end-entity X.509 certificate, not a CA certificate, and it has to chain cleanly to the trust anchor with the right key usage for signing. If the handshake fails, the certificate is usually the first place to look, not the IAM policy.

**2. The role and its permissions.** Create the role with the trust policy above, then attach a policy that grants only what the device needs. For my agent, that is reading one specific secret and invoking Claude on Bedrock. Nothing else. I will go deep on that policy in the next article, including the part where my own Bedrock statement is looser than it should be, because it deserves its own.

**3. Register the trust anchor and profile** pointing at your CA, and add the role to the profile.

**4. Install `aws_signing_helper` on the device.** It is a single binary. Match the architecture: the Pi is ARM64, so it needs the AArch64 build, not x86_64. I mount it into the container read-only from the host rather than baking it into the image, so the container itself carries nothing privileged.

Then verify. If `aws sts get-caller-identity` returns the role, you are done. No key was typed.

---

## What it costs you honestly

This is not free of effort, and pretending otherwise would be dishonest.

**Managing the CA is the real work.** The credentials are gone, but now you own a certificate authority, small as it is. If you lose the CA key or the certificates, you cannot sign or renew anything, and you rebuild the whole chain from scratch. Back up the CA key the way you would back up a root password. Treat losing it as the actual disaster, because it is.

**Certificates expire.** A static key fails when someone steals it. A certificate fails on a date you set. That is better, it forces rotation, but it means an expired cert will silently stop your device from reaching AWS until you renew. Put the expiry on a calendar.

**The certificate type trips people up.** As I said, an end-entity cert with the right key usage. Getting the OpenSSL flags wrong is the most common reason the first handshake fails.

**Sessions are time-boxed.** The temporary credentials live for the session window you configure, then refresh through the same certificate flow. For a long-running agent this is invisible. For a short script it is worth knowing the helper handles the refresh, not your code.

Is this enterprise PKI? No. A real enterprise wants a managed CA, hardware-backed keys, automated revocation and renewal. But for a homelab, a personal device, a small project that needs to reach AWS, this is genuinely good. It buys you the one thing that matters most: there is no long-lived secret to leak, forget, or find years later with nobody to claim it.

---

The best credential is the one that does not exist.

You cannot leak it, you cannot forget where you stored it, and no audit will ever find it unrotated in a config file two years from now. It expires on its own, and it was never a secret to begin with.

Next in this series: the IAM policy that role carries, and what "least privilege" actually looks like when you write it down.
