Users, roles and mounts
user ──authorities──▶ role ──▶ mount ──▶ backend (storage)
│
└── ACL (rights per path)
No right is implicit: a backend that no role mounts is visible to nobody, and the admin token gives access to no file.
A complete example
[[backends]] # one storage, described once
name = "disque"
type = "local"
root = "/srv/sftp"
[[roles]]
name = "utilisateurs"
[[roles.mounts]] # at least one per role
backend = "disque" # a [[backends]] name
home_dir = "/{username}" # where the mount starts on the storage
create_home = true
[[roles.mounts.acl]] # the rights, relative to the mount
path = "/"
rights = ["read", "write", "list", "delete", "rename"]
recursive = true
[[users]]
username = "alice"
password_hash = "$argon2id$..." # craft-file-gate hash-password
authorities = ["utilisateurs"] # alice's roles
alice sees /; this / is /srv/sftp/alice on the disk.
The keys of a role and of a mount
All the keys, with their hot reload: Reference [[roles]],
[[roles.mounts]].
Where the files go
A client path goes to the mount whose mount_path is its longest prefix;
the rest is appended to home_dir, on the storage
(what home_dir points to on each type).
.. never climbs above home_dir.
{username}: one directory per user
home_dir = "/{username}" gives each user their own directory, with a
single role. The name, never sanitized, is made of A-Z a-z 0-9 . _ -, 1 to
64 characters, with no leading dot.
One mount or several
| Mounts | What the user sees |
|---|---|
one, at / | the backend tree, starting from home_dir |
several, under names (/disque, /archives) | / lists the mounts; each one shows its backend |
nested (/partenaires/acme) | / and /partenaires are intermediate directories |
The paths between mounts are synthetic directories. A rename stays within its mount.
Combining roles
A user often has several roles. Their session has the mounts of all of them.
| The mounts of two roles | In the session |
|---|---|
mount_path values none of which is a prefix of another (/a, /b) | they coexist |
identical: same mount_path, backend, home_dir, and same max_file_mb, create_home, hidden_stores | a single mount, ACLs merged |
Two different mounts of the same backend have disjoint home_dir values
(/a and /b, not / and /a): each file is reached under one ACL only.
The doctrine: a role at / is complete
- a role at
/is complete: on its own it gives everything its user sees, and it combines with no other; - a role meant to be combined mounts under a name (
/archives).
See the recipes.
Where roles come from
| Source | How | The roles |
|---|---|---|
| local user | authorities of their [[users]] entry | names of [[roles]] |
| JWT token | the authorities_path claim (default /groups) | names of [[roles]], or authorities to translate |
| authorization service | authz_base_url translates the unknown authorities | names of [[roles]] |
Roles are always defined in [[roles]] or roles_file. Only the
authorities that are not role names go to the service.
The authorization service contract
POST <authz_base_url>/authz/resolve
Content-Type: application/json
{"authorities": ["CN=ACME-Partners,OU=Groups"]}
Expected response: 200 and
{"roles": ["partenaire-acme"]}
authz_base_url and timeout_secs: Reference [auth].
The named roles are added to those found locally; a name that [[roles]]
does not define is ignored, and a service that is down leaves the local roles.
An SFTP session builds all its backends at authentication and keeps its mounts until it ends, even after a hot reload; a REST request builds the backend of the mount it reaches.
What you will see
| When | Line |
|---|---|
startup, role at {username} | INFO role gives each user their own home directory |
| startup, role that mounts several backends | INFO role mounts several backends: its users hold the credentials of all of them ..., fields role, backends, writable |
create_home creates a directory | INFO created missing home directory |
A role that mounts several backends holds their credentials: keep it read only, for a dedicated user authenticated by key.