Self-hosted IAM is the part of the AWS stack that most bare-metal alternatives leave out. They'll give you virtual machines and maybe object storage, but they hand-wave the access control layer, which means engineers fall back to baking credentials into images, rotating static access keys manually, or running without meaningful multi-tenancy at all. Spinifex implements the full surface, including users, groups, policies, roles, instance profiles, and STS credential delivery, and your existing tooling works against it without modification.
What Spinifex IAM actually covers
The IAM implementation in Spinifex covers the primitives you'd reach for in AWS:
- Users and access keys for human operators and service accounts, with the same key pair format the CLI and SDKs expect
- Managed and inline policies in the same JSON format as AWS, with
AllowandDenyeffects, action patterns, and resource ARNs - Groups for bundling shared permissions across users without per-user policy management
- Roles with trust policies that specify which principals are allowed to
assume them, either AWS principals like specific users or service principals like
ec2.amazonaws.com - Instance profiles that bind a role to an EC2 instance so the instance itself can call AWS APIs without any static credentials in the guest environment
- STS AssumeRole for programmatic role assumption, returning short-lived credentials that expire and auto-rotate
All IAM resources are scoped to the account they're created in, matching AWS account isolation semantics.
Roles and trust policies
A role is an IAM identity that has no long-lived credentials. Instead of an access key pair, it has a trust policy that declares who may assume it, and whoever assumes it receives short-lived credentials from STS. No static secret is ever distributed to the service that needs access, which makes this the right model for service-to-service auth.
Creating a role requires writing a trust policy first. For a role that EC2 instances will use,
trust the ec2.amazonaws.com service principal:
cat > /tmp/ec2-trust.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role --role-name app-server \
--assume-role-policy-document file:///tmp/ec2-trust.json \
--description "Role for app servers"
To trust a specific user principal instead, swap Service for AWS and
supply the user ARN:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::000000000001:user/deploy-bot" },
"Action": "sts:AssumeRole"
}
]
}
Spinifex validates trust policies at write time, rejecting NotPrincipal,
NotAction, empty Principal blocks, and empty-string
Action values with MalformedPolicyDocument.
Condition blocks are accepted only in the StringEquals form used for
web identity federation, so the validation surface is deliberately narrower than full AWS IAM
while still covering the common patterns.
Attaching permissions to a role
A freshly created role can do nothing. Grant it permissions by attaching a managed policy, exactly as you would with a user:
aws iam attach-role-policy --role-name app-server \
--policy-arn arn:aws:iam::000000000001:policy/S3ReadOnly
aws iam list-attached-role-policies --role-name app-server
Or embed an inline policy directly on the role, which travels with it and is deleted when the role is deleted:
aws iam put-role-policy --role-name app-server \
--policy-name ec2-describe \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["ec2:DescribeInstances"],"Resource":"*"}]}'
Both approaches produce the same evaluation result. Managed policies are useful when the same permission set applies to multiple roles or users; inline policies are useful when the permission is specific to that one role and you want it to go away if the role does.
Instance profiles and IMDS
The instance profile is what gets role credentials into a running EC2 instance. A profile is a
container that binds exactly one role to a set of EC2 instances, and the instances get their
credentials delivered through the Instance Metadata Service at 169.254.169.254,
the same address AWS uses. The AWS CLI and every AWS SDK check IMDS for credentials
automatically, so an instance launched with a profile needs no credentials configured inside
the guest at all.
Creating a profile and adding the role to it:
aws iam create-instance-profile --instance-profile-name app-server-profile
aws iam add-role-to-instance-profile \
--instance-profile-name app-server-profile --role-name app-server
Then launch an instance with the profile attached:
aws ec2 run-instances \
--image-id ami-0dd52c90440ff4150 \
--instance-type t3.micro \
--subnet-id subnet-a0e5fc381376d82a1 \
--iam-instance-profile Name=app-server-profile
Inside that instance, any code running with the AWS CLI or an AWS SDK will pick up the role's credentials from IMDS without any configuration. The credentials rotate automatically and are never written to disk.
Assuming a role programmatically
Services and CI systems can also assume a role directly using STS, provided the trust policy allows their principal:
aws sts assume-role \
--role-arn arn:aws:iam::000000000001:role/deploy \
--role-session-name release-42
STS returns a temporary credential set with an expiry; the caller exports it as environment variables for the duration of the operation. The same call works from inside a guest using the instance role, from a CI runner with its own user credentials, or from any other principal allowed by the trust policy.
Why this matters for air-gapped and on-premise deployments
IAM solves the same problem whether you're on AWS or on your own hardware: services need to call APIs, and those calls need to be authorised without distributing long-lived secrets. On AWS, IAM and instance profiles handle this automatically. On bare metal without Spinifex, the typical answer is a static access key that lives in the environment, gets committed to config files, and has no expiry.
Spinifex brings the same credential delivery model to hardware you own. Instances get roles, roles have policies, and credentials arrive through IMDS, expire after the session duration, and rotate without operator involvement. Engineers who already understand IAM don't need to learn a different access control system when the compute moves to on-premise hardware.
Get started
IAM configuration is covered in the IAM roles and instance profiles guide and the IAM users and policies guide in the Spinifex documentation. Source is at GitHub, AGPL-3.0 licensed and written in Go. Or sign up for our free sandbox to explore the IAM surface before deploying on your own hardware.