Certificate-based AWS authentication for Nix hosts and Kubernetes using AWS IAM Roles Anywhere.
This flake provides three components:
Configures hosts to use IAM Roles Anywhere for AWS authentication:
aws-signing-helper- credential process for IAM Roles Anywhere~/.aws/config- configured withcredential_process- Multi-profile support - one host can assume multiple roles
- Secrets-manager agnostic - works with SOPS, agenix, or any secret source
A CLI for managing IAM Roles Anywhere infrastructure:
- Initialize AWS infrastructure (CloudFormation stacks)
- Create roles with Roles Anywhere profiles
- Onboard hosts (generate certificates, deploy stacks, create SOPS files)
- Onboard Kubernetes workloads (generate cert-manager manifests)
Generate manifests for Kubernetes workloads to use IAM Roles Anywhere:
- cert-manager Issuer and Certificate resources
- Sidecar-based credential delivery
- Works with any K8s cluster (on-prem, self-managed, EKS)
┌─────────────────────────────────────────────────────────────────┐
│ ADMIN WORKSTATION │
│ ┌───────────────┐ │
│ │ iam-ra CLI │ ← Manages AWS infrastructure │
│ │ │ - iam-ra init │
│ │ │ - iam-ra role create <name> │
│ │ │ - iam-ra host onboard <hostname> │
│ │ │ - iam-ra k8s setup <cluster> │
│ │ │ - iam-ra k8s onboard <workload> │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ NIX HOSTS │
│ ┌───────────────┐ │
│ │ Nix Modules │ ← Uses IAM Roles Anywhere │
│ │ │ - aws-signing-helper │
│ │ │ - ~/.aws/config (multi-profile) │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTERS │
│ ┌───────────────┐ │
│ │ cert-manager │ ← Issues certificates │
│ │ + sidecar │ ← aws_signing_helper serves credentials │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
nix run github:igorlg/iam-roles-anywhere -- initiam-ra role create admin --policy arn:aws:iam::aws:policy/AdministratorAccess
iam-ra role create readonly --policy arn:aws:iam::aws:policy/ReadOnlyAccessiam-ra host onboard myhost --role adminThis creates:
- Host CloudFormation stack with certificate in Secrets Manager
- SOPS-encrypted secrets file:
secrets/hosts/myhost/iam-ra-default.yaml
{ config, inputs, ... }:
{
imports = [ inputs.iam-roles-anywhere.nixosModules.default ];
# Configure secrets (example with SOPS)
sops.secrets."iam-ra/cert".sopsFile = ./secrets/hosts/myhost/iam-ra-default.yaml;
sops.secrets."iam-ra/key".sopsFile = ./secrets/hosts/myhost/iam-ra-default.yaml;
programs.iamRolesAnywhere = {
enable = true;
user = "alice";
# Single identity; for cross-account see examples/hosts/multi-identity.nix
identities.default = {
# Certificate (shared across all profiles in this identity)
certificate = {
certPath = config.sops.secrets."iam-ra/cert".path;
keyPath = config.sops.secrets."iam-ra/key".path;
};
# Identity-level AWS config
trustAnchorArn = "arn:aws:rolesanywhere:ap-southeast-2:123456789012:trust-anchor/...";
region = "ap-southeast-2";
# Multiple profiles - one host can assume different roles
profiles = {
admin = {
profileArn = "arn:aws:rolesanywhere:ap-southeast-2:123456789012:profile/admin";
roleArn = "arn:aws:iam::123456789012:role/admin";
makeDefault = true; # Also creates [default] profile
};
readonly = {
profileArn = "arn:aws:rolesanywhere:ap-southeast-2:123456789012:profile/readonly";
roleArn = "arn:aws:iam::123456789012:role/readonly";
};
};
};
};
}# Uses the default profile (admin in this example)
aws sts get-caller-identity
# Or specify a profile
aws sts get-caller-identity --profile admin
aws sts get-caller-identity --profile readonly{
inputs.iam-roles-anywhere.url = "github:igorlg/iam-roles-anywhere";
outputs = { self, nixpkgs, iam-roles-anywhere, ... }: {
# NixOS
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
iam-roles-anywhere.nixosModules.default
./configuration.nix
];
};
# Darwin
darwinConfigurations.myhost = darwin.lib.darwinSystem {
modules = [
iam-roles-anywhere.darwinModules.default
./configuration.nix
];
};
# Home Manager (standalone)
homeConfigurations.alice = home-manager.lib.homeManagerConfiguration {
modules = [
iam-roles-anywhere.homeModules.default
./home.nix
];
};
};
}# Run directly
nix run github:igorlg/iam-roles-anywhere -- --help
# Or add to devShell
nix develop github:igorlg/iam-roles-anywhere
iam-ra --helpiam-ra [OPTIONS] COMMAND
Commands:
init Initialize IAM Roles Anywhere infrastructure
destroy Tear down all infrastructure for a namespace
status Show current status
role Manage IAM roles
create Create role with Roles Anywhere profile
delete Delete role
list List all roles
host Manage hosts
onboard Onboard host (cert + stack + SOPS)
offboard Remove host
list List all hosts
k8s Manage Kubernetes integration
setup Set up cluster (CA + Issuer)
teardown Remove cluster from state
onboard Onboard workload (Certificate + Pod)
offboard Remove workload from state
list List clusters and workloads
# Initialize with self-signed CA (default)
iam-ra init
# Initialize with AWS Private CA
iam-ra init --ca-mode pca-new
# Create roles
iam-ra role create admin --policy arn:aws:iam::aws:policy/AdministratorAccess
iam-ra role create deploy --policy arn:aws:iam::123:policy/DeployPolicy --session-duration 7200
# Onboard hosts
iam-ra host onboard webserver --role admin
iam-ra host onboard ci-runner --role deploy --validity-days 90
# Check status
iam-ra status
iam-ra status --json
# Clean up
iam-ra host offboard webserver
iam-ra role delete deploy
iam-ra destroy --yes# Set up a K8s cluster (once per cluster)
iam-ra k8s setup prod-cluster | kubectl apply -f -
# Onboard workloads
iam-ra k8s onboard payment-service --role admin --cluster prod-cluster | kubectl apply -f -
iam-ra k8s onboard api-gateway --role readonly --cluster prod-cluster -k gateway | kubectl apply -f -
# List K8s resources
iam-ra k8s list
iam-ra k8s list --cluster prod-cluster
# Clean up
iam-ra k8s offboard payment-service
iam-ra k8s teardown prod-clusterSee docs/KUBERNETES.md for detailed Kubernetes documentation.
When initialized with --ca-mode pca-new or --ca-mode pca-existing, host
certificates are issued by an AWS Private CA rather than signed locally.
Because the CA's private key lives in AWS (in an HSM), iam-ra cannot sign
host certificates itself. Instead, host onboarding submits a CSR to the PCA:
iam-ra host onboardgenerates an EC P-256 keypair and a CSR locally- The CSR is submitted via
acm-pca:IssueCertificate - The signed certificate is retrieved via
acm-pca:GetCertificate - Certificate and private key are uploaded to S3 and wired into Secrets Manager
The CSR template used is
EndEntityClientAuthCertificate/V1,
which produces an end-entity certificate with the Client Authentication EKU
(required by IAM Roles Anywhere) and passes through the CSR's subject - so
the certificate CN will match the hostname you specify on the CLI.
If you point iam-ra init at an existing PCA (--pca-arn), that CA must be in
ACTIVE status. A new Private CA starts in PENDING_CERTIFICATE and must be
activated with a signed CA certificate before it can issue end-entity certs.
Check status:
aws acm-pca describe-certificate-authority \
--certificate-authority-arn arn:aws:acm-pca:...:certificate-authority/... \
--query 'CertificateAuthority.Status'Expected output: "ACTIVE". If it's anything else (e.g. PENDING_CERTIFICATE,
DISABLED), iam-ra host onboard will fail fast with a clear error message
before attempting to issue the certificate.
In addition to the base permissions, the admin running iam-ra host onboard
needs:
acm-pca:DescribeCertificateAuthorityacm-pca:IssueCertificateacm-pca:GetCertificate
programs.iamRolesAnywhere = {
enable = true;
user = "alice"; # Required: user to configure
# One or more identities. Each = one AWS account / trust anchor / cert.
# Most users have a single identity named `default`. For cross-account
# setups, see examples/hosts/multi-identity.nix.
identities.default = {
certificate = {
certPath = "/path/to/cert.pem"; # Any secrets manager path
keyPath = "/path/to/key.pem";
};
trustAnchorArn = "arn:aws:rolesanywhere:...";
region = "ap-southeast-2";
sessionDuration = 3600; # Optional: identity-level default
profiles = {
myprofile = {
profileArn = "arn:aws:rolesanywhere:...";
roleArn = "arn:aws:iam::...:role/...";
makeDefault = false; # Create [default] profile too?
awsProfileName = "myprofile"; # Must be unique across all identities
sessionDuration = 900; # Override identity-level default
output = "json"; # json, yaml, text, table
extraConfig = { # Additional AWS config
cli_pager = "";
};
};
};
};
};Same options, but without user:
programs.iamRolesAnywhere = {
enable = true;
identities.default = {
certificate = { ... };
trustAnchorArn = "...";
region = "...";
profiles = { ... };
};
};By default, each profile's credential_process in ~/.aws/config is a single
long line containing the full aws_signing_helper credential-process command
with all certificate paths, ARNs, region and session duration inline - easily
400+ characters per profile, making the config file hard to read or diff.
Set useCredentialProcessWrapper = true to have the module generate a
per-profile shell wrapper script at ~/.aws/iam-ra/<awsProfileName>.sh and
reference it by absolute path from ~/.aws/config instead:
programs.iamRolesAnywhere = {
enable = true;
useCredentialProcessWrapper = true; # Defaults to false (inline command)
identities.default = {
certificate = { ... };
trustAnchorArn = "...";
region = "...";
profiles.admin = { ... };
};
};With this enabled, ~/.aws/config becomes:
[profile admin]
credential_process=/home/alice/.aws/iam-ra/admin.sh
region=ap-southeast-2
output=jsonAnd ~/.aws/iam-ra/admin.sh (managed declaratively by home-manager - it's a
read-only symlink into /nix/store that gets regenerated on every
activation):
#!/usr/bin/env bash
# Generated by programs.iamRolesAnywhere (Nix module).
# DO NOT EDIT - regenerated by home-manager on every activation.
exec /nix/store/.../aws_signing_helper \
credential-process \
--certificate /run/secrets/iam-ra/cert \
--private-key /run/secrets/iam-ra/key \
--trust-anchor-arn arn:aws:rolesanywhere:... \
--profile-arn arn:aws:rolesanywhere:... \
--role-arn arn:aws:iam::...:role/admin \
--region ap-southeast-2Tradeoff: one extra file per profile under ~/.aws/iam-ra/ in exchange for a
readable ~/.aws/config and a scriptable/debuggable credential-process entry
point.
The module is secrets-manager agnostic. Just provide paths to certificate files.
sops.secrets."iam-ra/cert" = {
sopsFile = ./secrets/hosts/myhost/iam-ra-default.yaml;
key = "certificate";
};
sops.secrets."iam-ra/key" = {
sopsFile = ./secrets/hosts/myhost/iam-ra-default.yaml;
key = "private_key";
};
programs.iamRolesAnywhere.identities.default.certificate = {
certPath = config.sops.secrets."iam-ra/cert".path;
keyPath = config.sops.secrets."iam-ra/key".path;
};age.secrets.iam-ra-cert.file = ./secrets/iam-ra-cert.age;
age.secrets.iam-ra-key.file = ./secrets/iam-ra-key.age;
programs.iamRolesAnywhere.identities.default.certificate = {
certPath = config.age.secrets.iam-ra-cert.path;
keyPath = config.age.secrets.iam-ra-key.path;
};programs.iamRolesAnywhere.identities.default.certificate = {
certPath = "/etc/ssl/iam-ra/cert.pem";
keyPath = "/etc/ssl/iam-ra/key.pem";
};AWS Account
├── CloudFormation Stacks
│ ├── iam-ra-{namespace}-init (S3, KMS, Lambdas)
│ ├── iam-ra-{namespace}-rootca (Trust Anchor)
│ ├── iam-ra-{namespace}-role-* (IAM Roles + Profiles)
│ └── iam-ra-{namespace}-host-* (Host certificates)
├── S3 Bucket
│ ├── {namespace}/state.json
│ ├── {namespace}/ca/certificate.pem
│ └── {namespace}/hosts/{hostname}/*
├── SSM Parameters
│ └── /iam-ra/{namespace}/state-location
└── Secrets Manager
└── /iam-ra/{namespace}/hosts/{hostname}/*
Local
├── ~/.local/share/iam-ra/
│ └── {namespace}/ca-private-key.pem (self-signed CA only)
└── secrets/hosts/{hostname}/iam-ra-{namespace}.yaml (SOPS-encrypted)
Host
├── /run/secrets/iam-ra/* (deployed by secrets manager)
├── ~/.aws/config (credential_process for each profile)
└── ~/.aws/iam-ra/ (optional: per-profile wrapper scripts,
enabled by useCredentialProcessWrapper)
iam-roles-anywhere/
├── flake.nix
├── README.md
├── VERSION
├── docs/
│ └── KUBERNETES.md # K8s integration guide
├── examples/
│ ├── hosts/ # Nix host configurations
│ └── k8s/ # Kubernetes manifests
├── nix/
│ ├── package.nix # CLI package (uv2nix)
│ ├── module.nix # Module exports
│ ├── module-options.nix # Option definitions
│ ├── module-aws-profile.nix # Multi-identity AWS CLI config
│ ├── module-validation.nix # ARN validation
│ ├── module-packages.nix # Package installation
│ ├── lib.nix # Helper functions
│ └── checks.nix # Nix tests
├── src/iam_ra_cli/
│ ├── main.py # CLI entry point
│ ├── commands/ # CLI commands
│ ├── workflows/ # Orchestration logic
│ ├── operations/ # Atomic operations
│ ├── lib/ # Infrastructure helpers
│ ├── models/ # Data models
│ └── data/cloudformation/ # CFN templates
├── tests/ # Python tests
└── cloudformation/ # Standalone CFN templates
iam-ra initiam-ra role list
iam-ra role create myrole --policy arn:aws:iam::aws:policy/...# Check certificate validity
openssl x509 -in /run/secrets/iam-ra/cert -noout -dates -subject
# Verify trust anchor matches
aws rolesanywhere get-trust-anchor --trust-anchor-id ...The PCA you're using must be activated before it can issue host certificates.
A CA in PENDING_CERTIFICATE has been created but has no signed CA certificate
installed yet.
# If you created the CA via iam-ra init --ca-mode pca-new, CloudFormation
# should have activated it. Verify:
aws acm-pca describe-certificate-authority \
--certificate-authority-arn <arn> \
--query 'CertificateAuthority.Status'
# If pca-existing: you need to activate it yourself. For a ROOT CA:
aws acm-pca get-certificate-authority-csr \
--certificate-authority-arn <arn> \
--output text > ca.csr
# ... sign ca.csr with your root/parent CA, then:
aws acm-pca import-certificate-authority-certificate \
--certificate-authority-arn <arn> \
--certificate fileb://ca.crtaws_signing_helper credential-process \
--certificate /run/secrets/iam-ra/cert \
--private-key /run/secrets/iam-ra/key \
--trust-anchor-arn arn:aws:rolesanywhere:... \
--profile-arn arn:aws:rolesanywhere:... \
--role-arn arn:aws:iam::...:role/...MIT