Repository navigation
Standalone IAM
versitygw iam runs an AWS-compatible IAM Query API as its own process, separate from the S3 gateway. It serves 41 actions — 39 IAM actions plus AssumeRoleWithWebIdentity and GetCallerIdentity from STS — over SigV4-authenticated HTTP, so it is driven with the ordinary aws iam and aws sts CLI commands and with any standard AWS SDK. It stores users, IAM roles, access keys, inline policies, OpenID Connect providers and STS sessions in either a file-backed directory or HashiCorp Vault. Permissions come from identity policies evaluated per request, credentials can be minted for a federated OIDC identity rather than handed out by an administrator, and the S3 gateway becomes a pure enforcement point that stores no credentials at all.
This is a different thing from the built-in IAM backends — --iam-dir, LDAP, Vault, FreeIPA and S3 (see Multi-Tenant). Those are account directories that the S3 gateway reads directly, in-process. They hold a flat list of accounts, each keyed by its access key and carrying a secret, a role tier and a POSIX uid/gid. They have no policy language of their own, no notion of an IAM role, no federation, and no API: accounts are created and listed through versitygw's own Admin APIs or the WebGUI.
Important
The standalone IAM service and the built-in IAM backends are mutually exclusive and share no data. There is no migration path or converter between them: the two schemas describe different object models. Choosing the standalone service means recreating your accounts through the IAM API.
The IAM service listens on two distinct interfaces, with different audiences, different transports and different authorization rules.
The public IAM API is the AWS Query API. It is configured with the global --port flag (default :7070), speaks SigV4 with the credential scope service iam for IAM actions and sts for STS actions, and is the interface administrators, scripts, SDKs and the WebGUI use. The signing region is fixed at us-east-1 regardless of --region.
The private endpoint is a small internal control channel that only the S3 gateway uses. It is enabled with --private-ports and must run over either a unix socket or mutual TLS — the service refuses to start if you point it at plain TCP. Every request on it is authenticated strictly as the IAM service's root credential; no IAM user, however privileged its policy, can reach it.
+-----------------------------+ +----------------------------+
| versitygw iam | | versitygw <backend> |
| standalone IAM service | | S3 gateway |
| | private | |
| users, roles, access keys, | endpoint | holds no user secrets |
| inline policies, OIDC |<-------------+ and no policy documents |
| providers, STS sessions | mTLS or a | |
| | unix socket | |
+--------------+--------------+ root-signed +----------------------------+
|
v
+---------------------+
| --dir (iam.json) |
| or Vault KV v2 |
+---------------------+
The S3 gateway never learns a user's secret access key and never sees a policy document. It makes five kinds of call on the private endpoint:
| Call | What the gateway sends | What it gets back |
|---|---|---|
derive-signing-key |
access key id, optional session token, request date, region, service | the SigV4 signing key for that one date/region/service — never the secret — plus the caller's principal ARN and, for a session, its role ARN |
evaluate-policy |
the actions and resource ARNs the request needs, the request's condition context, and the gateway's own region and service (s3) for last-used tracking |
an allow / deny / no_match decision matrix |
resolve-identity |
one or more access key ids | whether each exists, whether it is a user or a session, and its principal ARN; this is what backs ACL grantee and bucket-owner validation |
resolve-principals |
the strings a bucket policy names as principals | only the ones that do not resolve; this is what backs PutBucketPolicy principal validation |
version |
nothing | the service's private protocol version, checked at gateway startup, and the account id every ARN belongs to |
Last-used metadata — an access key's GetAccessKeyLastUsed, or, for a session, its role's RoleLastUsed — is recorded on evaluate-policy and deliberately not on derive-signing-key, which the gateway makes before it has verified the request's signature.
- Revocation is immediate. The standalone backend is never cache-wrapped, so deactivating a key, deleting it, or deleting the user takes effect on the caller's very next request.
-
The gateway fails closed. If the IAM service is unreachable, S3 requests return
InternalErrorrather than being allowed through.
There are three deployment shapes:
| Shape | Private endpoint |
|---|---|
| IAM service alone — a complete user and role directory with an API and a dashboard, which gateways can be pointed at later | none |
| Gateway and IAM service on one host | a unix socket; filesystem permissions are the access control |
| Gateway and IAM service on separate hosts | a TCP address with mutual TLS |
Commands, flags, storage backends, certificate generation, protocol versioning, WebGUI hosting and startup validation are all in Standalone IAM Setup.
| Capability | Standalone IAM service | Built-in backends (--iam-dir, LDAP, Vault, FreeIPA, S3) |
|---|---|---|
| API of its own | AWS IAM + STS Query API | No |
| Identity (inline) policies | Yes | No |
| IAM roles | Yes | No |
| Federation / OIDC | Yes, via AssumeRoleWithWebIdentity
|
No |
| Access key management | Keys are separate objects, up to 2 per user, rotated and deactivated independently | The account is the access key |
Account role tiers (admin / user / userplus) |
None — every account is a plain user | Yes |
| Bucket ownership | Fixed: every bucket is owned by root | Per creator |
| Admin API user management | Not supported | Yes, except FreeIPA (lookup only) |
| WebGUI | Yes — IAM users, roles and providers pages | Yes — admin API pages; FreeIPA partial |
The built-in Vault backend (--iam-vault-* on the gateway, an account directory) and the standalone service's Vault storage (--vault-* on the iam subcommand, the service's own persistence) are unrelated and share no data, even against the same Vault server. See IAM Vault.
The standalone service changes several behaviors that administrators coming from the built-in backends have learned to rely on. These are the ones that surprise people.
Everything the service knows lives in one AWS account, whose id is the constant 000000000000 — twelve zeros. There is no multi-account model, no way to change the id, and no cross-account anything; every ARN the service issues or accepts carries it. The full ARN shapes are tabulated in Identity Policies.
An assumed-role session ARN uses the sts service, not iam — arn:aws:sts::000000000000:assumed-role/<RoleName>/<RoleSessionName> — and that is the ARN that appears in GetCallerIdentity and in S3 access-denied messages for federated callers.
Every ARN you write into a policy must use 000000000000 too. An OIDC provider ARN naming a different account is rejected with AccessDenied; a role ARN naming a different account in AssumeRoleWithWebIdentity is indistinguishable from a nonexistent role and also yields AccessDenied. The condition key aws:PrincipalAccount is always 000000000000 for a non-root identity.
Every IAM account the S3 gateway resolves through the standalone service is a plain user. The admin, user and userplus tiers of the built-in backends do not exist — the IAM object model has no field for them, and the gateway hard-codes the plain-user role for every resolved identity. Privilege comes from the identity policy attached to the user or role, and from nowhere else. Concretely:
- Creating a bucket is not a property of an account class any more. It requires
s3:CreateBucketin an identity policy, evaluated againstarn:aws:s3:::<bucket-name>. Theuserplusshortcut can never fire. - The admin API is reachable only by root (see below).
- Every standalone-backed account, the root account included, gets one process-wide POSIX identity from
--iam-standalone-default-uid/-gid/-project-id(all default0); the IAM user model carries no per-user uid or gid, so per-user file ownership on the posix backend is unavailable. See Standalone IAM Setup.
Root is the only privileged identity, and it is not an IAM user at all. It is the --access/--secret pair given on the command line, matched by access-key equality before any store lookup, and never written to the storage backend. Root bypasses the IAM service's own policy middleware entirely, and on the S3 side it bypasses bucket policies, identity policies and ACLs alike. Both processes require one.
Every bucket is owned by root. The standalone IAM client implements a fixed bucket owner contract, and the owner it returns is always the gateway's root account, carrying the POSIX identity from the --iam-standalone-default-* options — the posix backend's --chuid/--chgid chowns a new bucket to that uid/gid — because there is no per-user ownership left to express: every account is a plain user and the gateway cannot enumerate accounts (ListUserAccounts is not supported).
What changes in practice:
-
Bucket creation stops resolving an owner. A bucket created by any caller stores root's access key as its ACL owner. The admin API's
x-vgw-ownerheader is not read at all. -
ACLs still work, but only root can set them. A fresh bucket grants an ordinary user nothing, so on a bucket with no explicit grants the identity policy is the whole decision. Root, as owner, can still call
PutBucketAcl, and grants to long-term access keys are honored as before — see ACL grantees for what the grantee string must be.
Because ownership no longer distinguishes buckets, ListBuckets returns every bucket on the gateway to every caller who is allowed to call it, with Owner.ID set to root's access key. There is no per-user filtering.
What gates the call instead is a new action, s3:ListAllMyBuckets, evaluated against the account-level ARN arn:aws:s3:::*. A grant scoped to one bucket — "Resource": "arn:aws:s3:::team-a" — does not work; "Resource": "*" does. Root bypasses the check — as would an admin-tier caller, which does not exist here — and a caller without the action gets AccessDenied on s3:ListAllMyBuckets against "arn:aws:s3:::*". The grant policy and the exact denial message are in Identity Policies.
- It is an on/off switch for the whole bucket inventory, not a per-user filter — grant it only to callers who may see every bucket name on the gateway.
- Narrowing what a user can do per bucket is separate:
s3:ListBucketand the object actions on specific bucket ARNs.
s3:ListAllMyBuckets is valid only in an identity policy. Putting it in a bucket policy is rejected with Policy has invalid action.
The versitygw Admin APIs manage accounts through the gateway's IAM backend. The standalone backend cannot serve those calls, so the user-management surface returns XAdminMethodNotSupported (HTTP 501, The method is not supported in single root user mode.):
| Admin command | Under standalone IAM |
|---|---|
create-user, update-user, delete-user, list-users, change-bucket-owner
|
XAdminMethodNotSupported |
list-buckets |
Works — every bucket, owner always root |
create-bucket |
Works — x-vgw-owner is ignored |
The 501 is returned before the command's arguments are parsed, so change-bucket-owner never looks at the bucket or the owner it was given; there is no supported way to move bucket ownership.
Use aws iam against the IAM service instead of the four user commands. And note that because every standalone account is a plain user, the admin API is reachable only by root in the first place; an IAM user cannot call it no matter what its identity policy says.
Bucket policy Principal values are AWS-style ARNs, validated against the IAM service when PutBucketPolicy stores the policy: "Principal": "arn:aws:iam::000000000000:user/alice" is accepted, while a bare user name, an access key id or a wildcard inside an ARN is rejected with MalformedPolicy / Invalid principal in policy. The accepted forms and the account-root delegation rule are on Bucket Policies.
ACL grantees are the other half, and they are unchanged: a grantee is an access key id, resolved against the IAM service by identity resolution, never a user name or an ARN.
Warning
Because an ACL grantee is the credential rather than the identity:
- Deleting or rotating an access key silently orphans every ACL grant that named it. There is no rename and no indirection; the reference simply stops matching. Bucket policies, which name ARNs, are unaffected.
- A user with two access keys is two distinct grantees — though still one bucket-policy principal.
-
A session credential can never be an ACL grantee or a bucket owner. An
ASIA…key is reported as nonexistent by identity resolution even while its session is live, deliberately: the key stops existing when the session ends. A session can be a bucket-policy principal, named either by its ownarn:aws:sts::000000000000:assumed-role/<role>/<session>ARN or by the role ARN, which covers every session of that role and is the only way to name sessions that do not exist yet.
Identity policies and role trust policies are unaffected by the grantee rule — those use real ARNs, as documented in Identity Policies.
Only inline policies exist: policy documents attached directly to one user or one role, managed with PutUserPolicy / GetUserPolicy / DeleteUserPolicy / ListUserPolicies and the matching …RolePolicy actions. There are no standalone policy objects to create, version, attach or reuse, and there are no IAM groups; every managed-policy and group action is absent from the router and answers with InvalidAction (the full list is in the IAM API Reference).
Plan for it: a permission set shared by many users has to be duplicated as an inline policy on each of them, or — better — expressed once on an IAM role that they assume. Inline policy documents are also capped in aggregate at 2048 bytes across all of a user's and 10240 bytes across a role's; see Identity Policies.
A file-backed IAM service and an S3 gateway on one host, a user with an access key and an inline policy, and a request made with it.
1. Start the IAM service with file storage and a unix-socket private endpoint.
mkdir -p /var/lib/versitygw/iam
versitygw --port 127.0.0.1:7071 --access admin --secret secret \
iam --dir /var/lib/versitygw/iam \
--private-ports /run/versitygw/iam.sock2. Start the S3 gateway pointed at that socket.
versitygw --port :7070 --access admin --secret secret \
--iam-standalone-endpoint /run/versitygw/iam.sock \
posix /data3. Point the AWS CLI at the IAM service as root. The signing region is fixed at us-east-1.
export AWS_ACCESS_KEY_ID=admin
export AWS_SECRET_ACCESS_KEY=secret
export AWS_DEFAULT_REGION=us-east-14. Create a user and an access key. The secret is returned once and never again, and a user may hold at most two keys.
aws --endpoint-url http://127.0.0.1:7071 iam create-user --user-name alice
aws --endpoint-url http://127.0.0.1:7071 iam create-access-key --user-name alice5. Attach an inline policy. This one lets Alice see the bucket list and use team-a, and nothing else.
cat > /tmp/alice-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "arn:aws:s3:::*" },
{ "Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::team-a", "arn:aws:s3:::team-a/*"] }
]
}
EOF
aws --endpoint-url http://127.0.0.1:7071 iam put-user-policy \
--user-name alice --policy-name team-a-access \
--policy-document file:///tmp/alice-policy.json6. Create the bucket as root. Alice's policy does not grant s3:CreateBucket, and all buckets are root-owned in any case.
aws --endpoint-url http://127.0.0.1:7070 s3api create-bucket --bucket team-a7. Use Alice's credentials against the S3 gateway, with the access key and secret from step 4.
echo hello > /tmp/hello.txt
aws --endpoint-url http://127.0.0.1:7070 s3api put-object \
--bucket team-a --key hello.txt --body /tmp/hello.txt
aws --endpoint-url http://127.0.0.1:7070 s3api list-bucketsListing works, and names root as the owner of every bucket:
"Owner": { "DisplayName": "", "ID": "admin" }Anything outside the policy is denied, and the message names the identity ARN and the missing action.
- Standalone IAM Setup — flags, storage, private endpoint, startup errors
- IAM API Reference — per-action reference for all 41 actions
- Identity Policies — policy format, S3 actions, evaluation order
- Policy Conditions — operators, keys, absence semantics
- STS & Web Identity — OIDC providers, trust policies, sessions
Multi-Tenant/IAM · Bucket Policies · IAM Vault · Admin APIs · WebGUI · Global Options · Differences from AWS S3 · Docker / Helm · Troubleshooting
Home · Quickstart · Configuration · Operations · Testing · Developer Guide · GitHub · Discussions · Issues
Apache 2.0 · @versitysoftware · LinkedIn · X · Facebook · Instagram
- Home
- Key Features
- User Guide
- Getting Started
- Networking and Deployment
- Access Control / IAM
- Features
- Backends
- Compatibility
- Operations
- Metrics
- Admin APIs
- Logging
- S3 RDMA
- Developer Guide
- Articles