Introduction
IAM Roles Anywhere extends AWS's temporary credential model to workloads outside of AWS — including pods running on Crusoe Managed Kubernetes (CMK). Instead of embedding long-lived IAM access keys in your cluster, you use X.509 certificates as the identity anchor. AWS trusts the certificate authority (CA) you register, issues short-lived credentials via STS, and your pods authenticate without any static secrets in your codebase or Kubernetes Secrets.
The setup involves three moving parts.
- cert-manager running on your CMK cluster acts as an in-cluster CA, issuing and rotating TLS certificates for your workloads.
- You register your root CA with AWS IAM Roles Anywhere as a Trust Anchor — this is what tells AWS which certificates to trust.
- The AWS Signing Helper binary runs as an init container alongside your application, performing the actual SPIFFE-style certificate exchange to obtain temporary STS credentials via
credential_process.
The result is a workload identity pattern that integrates cleanly with the AWS SDK, requires no code changes to your application, and rotates credentials automatically via cert-manager's renewal cycle.
Prerequisites
- Access to a CMK Cluster with
kubectlConfigured - AWS Console Access with IAM and IAM Roles Anywhere Permissions
-
opensslInstalled on Your Local Machine
Instructions
Step 1: Install cert-manager on Your CMK Cluster
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yamlStep 2: Generate the Root CA Locally
Generate the root key and self-signed CA certificate. The certificate (root-ca.crt) is what you'll upload to AWS IAM Roles Anywhere as the Trust Anchor in a later step.
# Generate the Root Key
openssl genrsa -out root-ca.key 4096
# Generate the Root Certificate (Valid for 60 Days)
openssl req -x509 -new -nodes -key root-ca.key -sha256 -days 60 \
-out root-ca.crt \
-subj "/CN=CrusoeClusterRoot" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash"Step 3: Store the CA in Your CMK Cluster as a Secret
kubectl create secret tls crusoe-root-ca-secret \
--cert=root-ca.crt \
--key=root-ca.key \
--namespace=cert-managerStep 4: Create the ClusterIssuer
ClusterIssuers are resources within cert-manager that allows for signing the certificate with the Certificate Authority you provide.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: crusoe-iam-issuer
spec:
ca:
secretName: crusoe-root-ca-secretStep 5: Create the Certificate
This Certificate resource instructs cert-manager to issue a signed leaf certificate using the ClusterIssuer above. The certificate will be stored in the Kubernetes Secret named aws-iam-certs, which your pod will mount. cert-manager will automatically rotate it 24 hours before expiry.
Note: the leaf certificate (duration: 720h, 30 days) is intentionally shorter-lived than the root CA (60 days). The root CA lifetime sets the outer bound for trust; leaf certs rotate frequently within that window. Plan to rotate and re-register your root CA before its 60-day expiry.
The commonName you set here (my-workload-pod) will appear as the principal name in AWS CloudTrail session logs — useful for audit traceability.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: aws-iam-client-cert
namespace: default
spec:
secretName: aws-iam-certs
duration: 720h
renewBefore: 24h
subject:
organizations:
- YourOrganization
commonName: my-workload-pod
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
- digital signature
- key encipherment
- client auth
issuerRef:
name: crusoe-iam-issuer
kind: ClusterIssuer
group: cert-manager.ioStep 6: Create a Trust Anchor in AWS IAM Roles Anywhere
- Navigate to IAM Roles Anywhere in the AWS Console (adjust region as needed).
- Select Trust Anchors → Create a Trust Anchor.
- Provide a name and select External certificate bundle.
- Paste the contents of
root-ca.crtinto the certificate bundle field. - Click Create. Copy the Trust Anchor ARN — you'll need it in the next step.
Step 7: Create the IAM Role
- Navigate to the IAM Console and create (or edit) a role.
- Select Custom trust policy and paste the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rolesanywhere.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession",
"sts:SetSourceIdentity"
],
"Condition": {
"ArnEquals": {
"aws:SourceArn": "<YOUR_TRUST_ANCHOR_ARN>"
}
}
}
]
}- Attach the permissions your workload needs (e.g.,
s3:ListBuckets). - Save the role and copy the Role ARN.
Step 8: Create an IAM Roles Anywhere Profile
- In the IAM Roles Anywhere console, select Profiles → Create a Profile.
- Provide a name and select the role you created in Step 7.
- Click Create and copy the Profile ARN.
Step 9: Create the ConfigMap
The credential_process entry in this ConfigMap instructs the AWS SDK to call the signing helper binary at /usr/bin/aws_signing_helper (placed there by the init container) to exchange the pod's certificate for temporary STS credentials.
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-config
data:
config: |
[default]
credential_process = /usr/bin/aws_signing_helper credential-process \
--certificate /aws-certs/tls.crt \
--private-key /aws-certs/tls.key \
--trust-anchor-arn <YOUR_TRUST_ANCHOR_ARN> \
--profile-arn <YOUR_PROFILE_ARN> \
--role-arn <YOUR_ROLE_ARN>Step 10: Deploy Your Application
The init container downloads the AWS Signing Helper binary and places it at /usr/bin/aws_signing_helper via a subPath volumeMount. The main container references that path via the AWS_CONFIG_FILE config above.
Architecture note: The example below uses the ARM64 binary for aarch64 (e.g., GB200 nodes). If your Crusoe VM uses x86_64, replace the download URL with the appropriate release from the AWS Signing Helper releases page.
Production note: The example installs
aws-cliat container start viadnffor demonstration purposes only. In production, build your own imageFROM public.ecr.aws/amazonlinux/amazonlinux:2023with the CLI pre-installed.
apiVersion: apps/v1
kind: Deployment
metadata:
name: crusoe-aws-app-arm64
spec:
replicas: 1
selector:
matchLabels:
app: crusoe-aws
template:
metadata:
labels:
app: crusoe-aws
spec:
volumes:
- name: aws-certs
secret:
secretName: aws-iam-certs
- name: aws-config
configMap:
name: aws-config
- name: shared-bin
emptyDir: {}
initContainers:
- name: install-helper
image: curlimages/curl
command:
- /bin/sh
- -c
- |
curl -fsSL https://rolesanywhere.amazonaws.com/releases/1.7.2/Aarch64/Linux/Amzn2023/aws_signing_helper \
-o /shared-bin/aws_signing_helper
chmod +x /shared-bin/aws_signing_helper
volumeMounts:
- name: shared-bin
mountPath: /shared-bin
containers:
- name: my-app
image: public.ecr.aws/amazonlinux/amazonlinux:2023
command:
- /bin/sh
- -c
- |
dnf install -y aws-cli && \
aws s3 ls
env:
- name: AWS_CONFIG_FILE
value: /aws-config/config
- name: AWS_PROFILE
value: default
- name: AWS_SDK_LOAD_CONFIG
value: default
volumeMounts:
- name: aws-certs
mountPath: /aws-certs
readOnly: true
- name: aws-config
mountPath: /aws-config
- name: shared-bin
mountPath: /usr/bin/aws_signing_helper
subPath: aws_signing_helperOnce the pod is running, verify the integration by inspecting the container logs:
kubectl logs <YOUR_POD_NAME> -c my-app
You should see your S3 buckets listed:
2025-11-27 00:35:10 my-example-bucket
If you see an AccessDenied error, verify that the IAM Role has the correct permissions attached and that the Trust Anchor ARN in the ConfigMap matches the one registered in IAM Roles Anywhere.
Example
A common use case for this pattern is a machine learning inference service running on CMK that needs to read model artifacts from S3 — for example, loading model weights at startup from a private bucket managed by your team's AWS account.
Rather than embedding a long-lived IAM access key as a Kubernetes Secret (which requires manual rotation and creates a static credential that can leak), you issue the pod a short-lived X.509 certificate via cert-manager. The pod presents that certificate to AWS IAM Roles Anywhere, receives temporary STS credentials scoped to exactly the permissions your IAM Role defines, and reads from S3 — all without a static secret anywhere in the cluster.
This same pattern extends to any AWS resource your workload needs to access: DynamoDB tables, SQS queues, Secrets Manager, ECR — anything an IAM Role can be scoped to.