Consulting and Craft · Hands On

How to Turn Private Certificates Into AWS Credentials

· 5 min read

If the machines holding your certificates need AWS access, IAM Roles Anywhere turns a certificate into temporary credentials, and no access key ever exists. This follows on from the two-tier CA build, and any CA that issues client certificates will do; the wiring is the same three objects either way.

A trust anchor holding your root certificate, so AWS knows which CA to believe:

aws rolesanywhere create-trust-anchor --name ironworks-root-g1 --enabled \
  --source 'sourceType=CERTIFICATE_BUNDLE,sourceData={x509CertificateData='"$(cat root.crt)"'}'

Anchor at the root, not the issuing CA. Then renewing the issuing CA changes nothing in AWS, which is the payoff for having two tiers.

A profile listing the roles certificate-holders may assume, with a session policy as a second ceiling and a duration matched to the work:

aws rolesanywhere create-profile --name workload --enabled \
  --duration-seconds 3600 \
  --role-arns arn:aws:iam::111122223333:role/docs-worker

A role whose trust policy pins the certificate subject, the issuer, and the anchor:

{
  "Effect": "Allow",
  "Principal": {"Service": "rolesanywhere.amazonaws.com"},
  "Action": ["sts:AssumeRole", "sts:TagSession", "sts:SetSourceIdentity"],
  "Condition": {
    "StringEquals": {
      "aws:PrincipalTag/x509Subject/CN": "docs-worker-01.factory.internal",
      "aws:PrincipalTag/x509Issuer/CN": "Ironworks Issuing CA G1"
    },
    "ArnEquals": {"aws:SourceArn": "arn:aws:rolesanywhere:ap-southeast-2:111122223333:trust-anchor/EXAMPLE"}
  }
}

On the machine, the credential helper does the rest, and every SDK on the box picks it up:

[default]
credential_process = aws_signing_helper credential-process \
  --certificate /etc/pki-leaf/leaf.crt \
  --private-key /etc/pki-leaf/leaf.key \
  --trust-anchor-arn arn:aws:rolesanywhere:...:trust-anchor/EXAMPLE \
  --profile-arn      arn:aws:rolesanywhere:...:profile/EXAMPLE \
  --role-arn         arn:aws:iam::111122223333:role/docs-worker

On Windows, swap the two file paths for --cert-selector Key=x509Subject,Value=CN=docs-worker-01.factory.internal: the helper finds the certificate in the machine store and signs through the TPM, so there is no private-key file to point at.

The revocation catch

Roles Anywhere checks only revocation lists you import into it. It does not fetch your distribution point and it does not speak OCSP. Import once, then update on every publish:

aws rolesanywhere import-crl --name issuing-g1 --enabled \
  --trust-anchor-arn arn:aws:rolesanywhere:...:trust-anchor/EXAMPLE \
  --crl-data fileb://issuing.der.crl

That import is now a step your revocation procedure depends on, which is exactly the sort of step humans forget. The next post removes the human.

Cutting sessions that already exist

Revoking a certificate stops the next CreateSession; credentials already minted keep working until they expire, which by default means up to one profile duration of continued access by a host you have just declared compromised. AWS does let you recall those, and it is worth wiring the command into your procedure before you need it. A deny keyed on aws:TokenIssueTime invalidates every session issued before a moment you choose, on its next API call:

{
  "Effect": "Deny", "Action": "*", "Resource": "*",
  "Condition": {
    "DateLessThan": {"aws:TokenIssueTime": "2026-08-14T09:15:00Z"},
    "StringEquals": {"aws:PrincipalTag/x509Subject/CN": "docs-worker-01.factory.internal"}
  }
}

That is what the IAM console’s “Revoke active sessions” button writes as an inline policy, minus the second condition. The second condition is the refinement this design earns you: because Roles Anywhere puts the certificate subject into the session tags, the deny can name a single host, so cutting one compromised machine does not knock over the other nine sharing the role. Drop the tag condition and you have the blunt version, which is the right choice when you do not yet know how far the compromise reached. The same shape works as an SCP when the blast radius spans accounts.

Two operational notes. Order the drill: revoke the certificate and push the CRL first, then cut the sessions, because a host that still holds a valid certificate will simply authenticate again a second later. And clean up afterwards: the deny stays until you remove it, and a stale TokenIssueTime deny left on a role is a confusing thing to inherit six months later, though it is harmless once every session predating it has expired anyway.

These posts are LLM-aided. Backbone, original writing, and structure by Craig. Research and editing by Craig + LLM. Proof-reading by Craig.