Intermediate
Introduction: The Problem with Long-Term Credentials for On-Premises Workloads
If you’ve ever managed on-premises servers, CI/CD agents, or edge devices that need to call AWS APIs, you know the drill: create an IAM user, generate an access key pair, store the credentials somewhere on the machine, and then lose sleep over the security implications. Those long-term credentials don’t rotate themselves. They get committed to Git repos. They get copied to machines you forgot about. They become the skeleton key that haunts your next security audit.
AWS IAM Roles Anywhere, launched in July 2022, solves this problem by letting workloads outside of AWS obtain temporary AWS credentials using X.509 certificates issued by your own Certificate Authority (CA). It extends the same temporary credential model that EC2 instance roles and ECS task roles use — but to any machine, anywhere.
This article is for intermediate-level DevOps engineers and cloud architects who already understand IAM roles and policies, and want a practical, step-by-step guide to eliminating long-term access keys from their on-premises infrastructure. By the end, you’ll have a working setup with a trust anchor, a profile, and a real workload obtaining temporary credentials.
Prerequisites
- An AWS account with permissions to manage IAM and IAM Roles Anywhere resources
- AWS CLI v2 installed and configured (v2.7.0 or later recommended)
- OpenSSL installed for certificate generation
- Basic understanding of X.509 certificates and Public Key Infrastructure (PKI)
- An on-premises server or VM to test credential retrieval
How IAM Roles Anywhere Works: Architecture Overview
Before diving into commands, let’s understand the components and the authentication flow. IAM Roles Anywhere introduces three key resources:
- Trust Anchor: A reference to your Certificate Authority (CA). This tells AWS which CA certificates to trust. You can use your own private CA certificate, or integrate with AWS Private Certificate Authority (AWS Private CA).
- Profile: Defines which IAM roles the authenticated workload can assume, along with optional session policies that scope down permissions further.
- Subject: Represents the authenticated entity (your workload). Subjects are created automatically when a certificate authenticates for the first time.
The authentication flow works as follows:
- 1. Your on-premises workload presents its X.509 client certificate and signs a request using its private key.
- 2. The IAM Roles Anywhere endpoint validates the certificate against the trust anchor’s CA certificate, checking the chain of trust, expiration, and optional revocation status.
- 3. If valid, IAM Roles Anywhere calls AWS STS on your behalf and returns temporary credentials (access key ID, secret access key, and session token) with a configurable duration.
- 4. Your workload uses those temporary credentials to make AWS API calls like any other IAM role session.
This is conceptually similar to how an EC2 instance’s metadata service provides temporary credentials — but instead of the hypervisor being the trust boundary, your PKI certificate chain is.
Step 1: Set Up Your Certificate Authority and Issue Certificates
You have two options for your CA: use AWS Private CA (managed, costs $400/month per CA) or use your own CA. For this guide, we’ll create a self-signed CA with OpenSSL to keep things straightforward and cost-free for testing. In production, use a proper CA — either AWS Private CA or your organization’s existing PKI.
Create a Private CA (Self-Signed for Testing)
# Generate the CA private key
openssl genrsa -out ca-key.pem 4096
# Generate the CA certificate (valid for 10 years)
openssl req -new -x509 -days 3650 \
-key ca-key.pem \
-out ca-cert.pem \
-subj "/C=US/ST=California/L=SanFrancisco/O=MyOrg/OU=DevOps/CN=MyOrg Root CA"
# Verify the CA certificate
openssl x509 -in ca-cert.pem -text -noout | head -20
Issue a Client Certificate for Your Workload
# Generate a private key for the on-premises workload
openssl genrsa -out workload-key.pem 2048
# Create a Certificate Signing Request (CSR)
openssl req -new \
-key workload-key.pem \
-out workload-csr.pem \
-subj "/C=US/ST=California/L=SanFrancisco/O=MyOrg/OU=OnPrem/CN=onprem-server-01"
# Sign the CSR with your CA (valid for 1 year)
openssl x509 -req -days 365 \
-in workload-csr.pem \
-CA ca-cert.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out workload-cert.pem
# Verify the certificate chain
openssl verify -CAfile ca-cert.pem workload-cert.pem
# Expected output: workload-cert.pem: OK
Important: The CN (Common Name) and other fields in your client certificate’s subject become important later — you can write IAM Roles Anywhere condition keys against them to control which certificates can assume which roles.
Step 2: Create the Trust Anchor, IAM Role, and Profile in AWS
Create the Trust Anchor
The trust anchor tells IAM Roles Anywhere to trust certificates signed by your CA. You provide the CA certificate (the public certificate, never the private key).
# Create the trust anchor using the CA certificate
aws rolesanywhere create-trust-anchor \
--name "onprem-ca-trust-anchor" \
--source "sourceType=CERTIFICATE_BUNDLE,sourceData={x509CertificateData=$(cat ca-cert.pem)}" \
--enabled
Note the trustAnchorId and trustAnchorArn from the output — you’ll need them later.
Create an IAM Role with a Trust Policy for Roles Anywhere
The IAM role needs a trust policy that allows the IAM Roles Anywhere service principal to assume it. Create a file called trust-policy.json:
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rolesanywhere.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession",
"sts:SetSourceIdentity"
],
"Condition": {
"StringEquals": {
"aws:PrincipalTag/x509Subject/CN": "onprem-server-01"
},
"ArnEquals": {
"aws:SourceArn": "arn:aws:rolesanywhere:us-east-1:123456789012:trust-anchor/YOUR-TRUST-ANCHOR-ID"
}
}
}
]
}
EOF
Replace 123456789012 with your account ID and YOUR-TRUST-ANCHOR-ID with the actual trust anchor ID. The condition on x509Subject/CN restricts which certificates can assume this role — only ones with the Common Name onprem-server-01.
# Create the IAM role
aws iam create-role \
--role-name OnPremS3ReadRole \
--assume-role-policy-document file://trust-policy.json
# Attach a permissions policy (example: read-only S3 access)
aws iam attach-role-policy \
--role-name OnPremS3ReadRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Create the Profile
The profile links the trust anchor to the role(s) your workloads can assume. You can optionally include a session policy to further restrict permissions beyond what the role allows.
# Get the role ARN
ROLE_ARN=$(aws iam get-role --role-name OnPremS3ReadRole --query 'Role.Arn' --output text)
# Create the profile
aws rolesanywhere create-profile \
--name "onprem-s3-read-profile" \
--role-arns "$ROLE_ARN" \
--duration-seconds 3600 \
--enabled
Note the profileId from the output. The --duration-seconds controls how long the temporary credentials are valid (minimum 900, maximum 43200 seconds / 12 hours).
Step 3: Obtain Temporary Credentials from Your On-Premises Workload
AWS provides a credential helper tool called aws_signing_helper that handles the cryptographic signing and credential exchange. This is the recommended approach.
Install the AWS Signing Helper
# Download the signing helper (Linux x86_64)
curl -o aws_signing_helper https://rolesanywhere.amazonaws.com/releases/1.4.0/X86_64/Linux/aws_signing_helper
# Make it executable
chmod +x aws_signing_helper
# Move it to a directory in your PATH
sudo mv aws_signing_helper /usr/local/bin/
# Verify
aws_signing_helper version
Check the AWS documentation for the latest version and download URLs for other platforms (macOS, Windows, ARM64).
Retrieve Temporary Credentials
# Replace these with your actual values
TRUST_ANCHOR_ARN="arn:aws:rolesanywhere:us-east-1:123456789012:trust-anchor/abc12345-def6-7890-ghij-klmnopqrstuv"
PROFILE_ARN="arn:aws:rolesanywhere:us-east-1:123456789012:profile/fed98765-cba4-3210-hijk-lmnopqrstuvw"
ROLE_ARN="arn:aws:rolesanywhere:us-east-1:123456789012:role/OnPremS3ReadRole"
# Get temporary credentials
aws_signing_helper credential-process \
--certificate workload-cert.pem \
--private-key workload-key.pem \
--trust-anchor-arn "$TRUST_ANCHOR_ARN" \
--profile-arn "$PROFILE_ARN" \
--role-arn "$ROLE_ARN"
This outputs JSON in the credential_process format that the AWS SDK and CLI understand:
{
"Version": 1,
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"SessionToken": "...",
"Expiration": "2024-01-15T12:00:00Z"
}
Integrate with AWS CLI via credential_process
The cleanest integration is to configure the AWS CLI to call the signing helper automatically. Add this to ~/.aws/config:
[profile onprem]
credential_process = aws_signing_helper credential-process \
--certificate /etc/aws/workload-cert.pem \
--private-key /etc/aws/workload-key.pem \
--trust-anchor-arn arn:aws:rolesanywhere:us-east-1:123456789012:trust-anchor/abc12345-def6-7890-ghij-klmnopqrstuv \
--profile-arn arn:aws:rolesanywhere:us-east-1:123456789012:profile/fed98765-cba4-3210-hijk-lmnopqrstuvw \
--role-arn arn:aws:iam::123456789012:role/OnPremS3ReadRole
Now you can use it seamlessly:
# List S3 buckets using temporary credentials from IAM Roles Anywhere
aws s3 ls --profile onprem
# Verify the identity
aws sts get-caller-identity --profile onprem
Using Credentials in Python (Boto3)
For application code, you can use the credential_process config shown above (Boto3 respects AWS CLI profiles), or use subprocess to invoke the helper directly:
import json
import subprocess
import boto3
from botocore.credentials import RefreshableCredentials
from botocore.session import get_session
def get_roles_anywhere_credentials():
"""Retrieve temporary credentials using aws_signing_helper."""
result = subprocess.run(
[
"aws_signing_helper", "credential-process",
"--certificate", "/etc/aws/workload-cert.pem",
"--private-key", "/etc/aws/workload-key.pem",
"--trust-anchor-arn", "arn:aws:rolesanywhere:us-east-1:123456789012:trust-anchor/abc12345",
"--profile-arn", "arn:aws:rolesanywhere:us-east-1:123456789012:profile/fed98765",
"--role-arn", "arn:aws:iam::123456789012:role/OnPremS3ReadRole",
],
capture_output=True,
text=True,
check=True,
)
creds = json.loads(result.stdout)
return creds
def main():
creds = get_roles_anywhere_credentials()
# Create a Boto3 session with the temporary credentials
session = boto3.Session(
aws_access_key_id=creds["AccessKeyId"],
aws_secret_access_key=creds["SecretAccessKey"],
aws_session_token=creds["SessionToken"],
region_name="us-east-1",
)
s3 = session.client("s3")
buckets = s3.list_buckets()
for bucket in buckets["Buckets"]:
print(f"Bucket: {bucket['Name']}")
if __name__ == "__main__":
main()
For production workloads, implement credential caching and refresh logic so you’re not calling the signing helper on every API request. The credential_process integration in the AWS SDK