Roles and Permissions
S4 uses a simple role-based access control model with three built-in roles.
Roles
| Role | S3 Read | S3 Write | Admin API | Description |
|---|---|---|---|---|
| Reader | Yes | No | No | Read-only access to S3 operations |
| Writer | Yes | Yes | No | Read and write access to S3 operations |
| SuperUser | Yes | Yes | Yes | Full access including user management |
Permission Details
Reader
- List buckets
- List objects in buckets
- Download (GET) objects
- HEAD requests on buckets and objects
Writer
All Reader permissions, plus:
- Create and delete buckets
- Upload (PUT) objects
- Delete objects
- Configure bucket settings (versioning, lifecycle, CORS, Object Lock)
- Multipart upload operations
SuperUser
All Writer permissions, plus:
- Access the Admin API
- Create, update, and delete users
- Generate and revoke S3 credentials
- View system statistics
Enabling IAM
IAM is activated by setting the S4_ROOT_PASSWORD environment variable:
export S4_ROOT_PASSWORD=your-strong-password
./target/release/s4-server
When IAM is enabled:
- The root user logs in with
S4_ROOT_USERNAME/S4_ROOT_PASSWORD - The root user has the SuperUser role
- All S3 API requests are checked against IAM permissions
- The Admin API becomes available at
/api/admin/
Root User
| Setting | Variable | Default |
|---|---|---|
| Username | S4_ROOT_USERNAME |
root |
| Password | S4_ROOT_PASSWORD |
(required to enable IAM) |
Root is configured by these variables and is not stored. To change its password, set a new
S4_ROOT_PASSWORD and restart the server: the new password works from that start, the old one is
refused, and root's sessions from before the change end. Root's id is derived from its name, so it
stays the same across restarts and password changes, and is the same on every node.
The Admin API cannot change, delete or issue S3 credentials to root: such requests answer 409
with code RootFromEnvironment. For S3 access with SuperUser rights, use S4_ACCESS_KEY_ID and
S4_SECRET_ACCESS_KEY (see Legacy Credentials). Nobody else can be created
under root's name.
Earlier releases stored root as a user. That stored user can no longer log in and is not listed;
S3 credentials issued to it keep working until it is deleted (DELETE
/api/admin/users/{id} with its old id). The server names it in the log at startup.
Authentication Methods
S4 supports two authentication methods simultaneously:
| Method | Used By | Mechanism |
|---|---|---|
| AWS Signature V4 | S3 API (AWS CLI, SDKs) | HMAC-SHA256 signature with access/secret keys |
| JWT Bearer Token | Admin API, Web Console | Authorization: Bearer <token> header |
Getting a JWT Token
TOKEN=$(curl -s -X POST http://localhost:9000/api/admin/login \
-H 'Content-Type: application/json' \
-d '{"username":"root","password":"your-password"}' | jq -r '.token')
The token is valid for 24 hours by default; S4_JWT_LIFETIME_HOURS sets another lifetime.
Sessions
A token refers to its account, and every admin request is checked against the account: the role is the one the account has now, not the one it had at login. A session ends at once when its user is deleted or deactivated, gets a new password or a new role, and when:
- a SuperUser ends all sessions of a user: POST
/api/admin/users/{id}/sessions/revoke(Admin API); the password, role and S3 credentials stay as they are; - the user logs out: POST
/api/admin/logoutends every session of the caller's account, on every device.
After either, the user logs in again for a new token. Tokens issued by releases before this check are refused: every user logs in again after the upgrade.
S3 requests are checked against the account at every request, so a deleted or deactivated user's S3 credentials stop working at once.
Cluster Mode
Users, their passwords, roles, active flag and S3 credentials are kept on every node of the cluster, so behind a load balancer a user logs in, uses an admin session and signs S3 requests through any node.
- A change counts once most nodes hold it. Creating, changing or deleting a user, issuing or deleting S3 credentials and revoking sessions answer success only after a majority of the cluster's nodes stored the change (two of three, four of six). The other nodes get it at once when they are up, and later otherwise.
- Without a majority, changes are refused with
503 ServiceUnavailable: retry when enough nodes are back. Each node checks logins, admin sessions and S3 signatures against its own copy, without asking another node, as long as most nodes of the cluster agree with that copy. - A node that was down catches up before it checks credentials. After a start, a node answers
the logins, admin sessions and S3 requests of IAM users with
503 ServiceUnavailableandRetry-Afteruntil it has compared its copy with most nodes of the cluster and taken every change it missed — usually within a second or two of joining. It never accepts a user or key deleted while it was down. A client behind a load balancer retries through another node. - A node cut off from most of the cluster stops checking IAM credentials after
S4_IAM_MAX_STALENESS_SECS(30 s) without most nodes agreeing with its copy, and answers503until they do again: a user deleted or a key removed through the rest of the cluster works on it for at most that long.0keeps it checking them for as long as it is cut off. - Root and the S3 credentials of the environment always work, on a node that is catching up or cut off too: they are the way in when IAM users cannot be checked.
- One user name, one user: creating a name that exists answers
409through every node, and of several creations of one name at the same moment through different nodes exactly one succeeds. - Changes take turns. One change of IAM runs at a time across the cluster: two changes of one
user at the same moment through different nodes — a new password through one, a new role through
another — both hold. A change that waits longer than
S4_IAM_LEASE_WAIT_SECS(10 s) for the one before it answers503 ServiceUnavailablewithRetry-After. A node that stalls or is cut off in the middle of a change holds the others up for at mostS4_IAM_LEASE_SECS(5 s), and whatever it writes after that is refused by every node. - Root is the same on every node: it logs in through any node, and its token is accepted by
every node with the same
S4_ROOT_PASSWORDandS4_JWT_SECRET. Revoking root's sessions or logging out as root ends them on every node. - Every node is configured alike.
S4_ROOT_PASSWORD,S4_JWT_SECRETandS4_IAM_ENCRYPTION_KEYmust be the same on every node. A node that differs is named in its log, in the cluster health —critical, with aniam_configsection listing the setting and the node — and ins4_iam_config_mismatch. A node without the key a user's S3 secret is encrypted with answers that user's S3 requests503; the others check them. - Every change is in the audit log of every node: who made it, what it did, to whom and through
which node — read it with
GET /api/admin/iam/audit. No password or secret key is ever written there. - Every node of the cluster must run this release. A node of an earlier release gets no IAM changes, and changes need a majority of nodes that do.
- How a node stands — whether it checks IAM credentials now, which nodes agree with its copy,
which node is making a change — is answered by
GET /api/admin/iam/statuson that node (see Admin API → IAM Status).
Upgrading a cluster. Earlier releases kept IAM users only on the node they were created through. Nothing in that data tells a user created only through node-2 from one deleted through node-1 and left on node-2, so the cluster does not merge it by itself: you decide.
- Upgrade every node. Until you decide, a node that holds such users keeps serving them as before,
only through that node, and every node refuses IAM changes with
409 IamAdoptionPending;/api/admin/cluster/healthanswersdegradedands4_iam_adoption_pendingis1. Root and the S3 credentials of the environment work throughout. Admin tokens of the earlier release are refused: log in again. - Read the report, through any node:
GET /api/admin/iam/adoption. It lists every user name with the nodes that hold it, each node's record of it — role, active flag, dates, access key — whether a majority of the nodes holds it, and its conflicts:ids(each node created its own user of that name),roles,active,passwords(copies of one user with different passwords; users of different ids are not compared — each node salted its own hash). Every node must answer. - Decide, naming the report you read:
POST /api/admin/iam/adoptionwith{"report_id": "…", "mode": "majority"}takes over the users whose name a majority of nodes holds;"union"takes every user;"users"with"users": ["<id>", …]takes exactly the listed ones. Of several users of one name the one created first keeps it; each other is renamed<name>~<first 8 characters of its id>and disabled, with its S3 credentials — enable or delete it yourself. Users left out are dropped. - Each node drops what it kept on its own once the decision reaches it, and IAM changes open once
every node has — usually within a few seconds.
GET /api/admin/iam/adoptionthen answers"state": "adopted"and shows the decision.
A cluster whose nodes hold no such users — a new one, or one with only the stored root of an
earlier release, which does not count — opens IAM changes by itself once every node is up and runs
this release. Going back to an earlier release after the decision brings no IAM user back: the
earlier release reads only root and the S3 credentials of the environment. The decision keeps the
report it was taken on, which GET /api/admin/iam/adoption shows.
A single node that becomes a cluster node brings its IAM users as its own: they appear in the report like those of an earlier release, and the cluster takes them over only as you decide.
Upgrading a single node. At its first start the node converts the IAM users it stores into the current format, with their passwords, roles and S3 credentials. An earlier release does not read the converted users: to go back, restore the data directory from a backup taken before the upgrade.
Legacy Credentials
When S4_ACCESS_KEY_ID and S4_SECRET_ACCESS_KEY are set alongside IAM, they continue to work as a fallback with SuperUser access. This ensures backward compatibility during migration to IAM.