Skip to main content
Crusoe Support Help Center home page
Crusoe

How-To Access AWS Resources from CMK Using IAM Roles Anywhere

Young Jeong
Young Jeong
Updated

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. 

  1. cert-manager running on your CMK cluster acts as an in-cluster CA, issuing and rotating TLS certificates for your workloads. 
  2. You register your root CA with AWS IAM Roles Anywhere as a Trust Anchor — this is what tells AWS which certificates to trust. 
  3. 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 kubectl Configured
  • AWS Console Access with IAM and IAM Roles Anywhere Permissions
  • openssl Installed 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.yaml

Step 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-manager

Step 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-secret

Step 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.io

Step 6: Create a Trust Anchor in AWS IAM Roles Anywhere

  1. Navigate to IAM Roles Anywhere in the AWS Console (adjust region as needed).
  2. Select Trust AnchorsCreate a Trust Anchor.
  3. Provide a name and select External certificate bundle.
  4. Paste the contents of root-ca.crt into the certificate bundle field.
  5. Click Create. Copy the Trust Anchor ARN — you'll need it in the next step.

Step 7: Create the IAM Role

  1. Navigate to the IAM Console and create (or edit) a role.
  2. 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>"
                }
            }
        }
    ]
}
  1. Attach the permissions your workload needs (e.g., s3:ListBuckets).
  2. Save the role and copy the Role ARN.

Step 8: Create an IAM Roles Anywhere Profile

  1. In the IAM Roles Anywhere console, select ProfilesCreate a Profile.
  2. Provide a name and select the role you created in Step 7.
  3. 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-cli at container start via dnf for demonstration purposes only. In production, build your own image FROM public.ecr.aws/amazonlinux/amazonlinux:2023 with 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_helper

Once 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.

Additional Resources

Related to

Was this article helpful?

0 out of 0 found this helpful

Still need help?

Our support team is ready to assist you with any questions.

Have more questions? Submit a request

Recently Viewed

Comments

0 comments

Article is closed for comments.