Mount recipes
Each recipe assumes this backend:
[[backends]]
name = "disque"
type = "local"
root = "/srv/sftp"
1. One directory per user
[[roles]]
name = "utilisateurs"
[[roles.mounts]]
backend = "disque"
home_dir = "/{username}"
create_home = true
acl = [{ path = "/", rights = ["read", "write", "list", "delete", "rename"], recursive = true }]
alice sees / = /srv/sftp/alice, created at her first login. A role
at / does not combine.
2. A partner: inbound drop, outbound pickup
The partner drops into /in without being able to read back, and picks up
from /out.
[[roles]]
name = "partenaire-acme"
[[roles.mounts]]
backend = "disque"
home_dir = "/partenaires/acme"
max_file_mb = 500
hidden_stores = { enabled = true, prefix = ".in.", extension = "" }
acl = [
{ path = "/in", rights = ["write", "list"], recursive = true },
{ path = "/out", rights = ["read", "list", "delete"], recursive = true },
]
/in is /srv/sftp/partenaires/acme/in; an upload there is published at
the end of the transfer.
For ls / to show in and out, add a non-recursive entry:
path = "/", rights = ["list"].
3. The operations account: every storage, read only
An account that sees each backend at the root and cannot modify anything.
[[backends]]
name = "archives"
type = "s3"
bucket = "archives"
region = "eu-west-3"
prefix = "sftp/"
credentials = { type = "iam_role" }
[[backends]]
name = "acme-amont"
type = "sftp"
host = "sftp.acme.example"
host_key_fingerprint = "SHA256:2hZbXq1b5Xb3vN2mQ6w7l3Fv0tXk4yJ8aUeYp9rS0cE" # ssh-keygen -lf, checked out of band
auth = { type = "password", username = "relais", password = "changez-moi" }
[[roles]]
name = "exploitant"
[[roles.mounts]]
backend = "disque"
mount_path = "/disque"
acl = [{ path = "/", rights = ["read", "list"], recursive = true }]
[[roles.mounts]]
backend = "archives"
mount_path = "/archives"
acl = [{ path = "/", rights = ["read", "list"], recursive = true }]
[[roles.mounts]]
backend = "acme-amont"
mount_path = "/acme-amont"
home_dir = "/depot"
acl = [{ path = "/", rights = ["read", "list"], recursive = true }]
[[users]]
username = "exploitation"
password_hash = "$argon2id$..." # of a random, discarded password: only the key is used
authorized_keys = ["ssh-ed25519 AAAA... exploitation@poste"]
authorities = ["exploitant"]
ls / shows acme-amont archives disque; /archives/2024/x is the S3 key
sftp/2024/x.
At startup, INFO role mounts several backends: ... gives role=exploitant
and writable=[]: check it after each change to the role.
4. Roles that combine
[[roles]]
name = "equipe-a"
[[roles.mounts]]
backend = "disque"
mount_path = "/equipe-a"
home_dir = "/equipes/a"
acl = [{ path = "/", rights = ["read", "list"], recursive = true }]
[[roles]]
name = "equipe-b"
[[roles.mounts]]
backend = "disque"
mount_path = "/equipe-b"
home_dir = "/equipes/b"
acl = [{ path = "/", rights = ["read", "write", "list"], recursive = true }]
A token whose groups claim is ["equipe-a", "equipe-b"] opens a session
with /equipe-a (read) and /equipe-b (read and write).
Two roles on the same mount merge their ACLs: acme-depot (/in) and
acme-releve (/out) on the mount of case 2 give /in and /out.
5. Organized mounts: /partenaires/<name>
With acme-amont from case 3:
[[roles]]
name = "gestion-partenaires"
[[roles.mounts]]
backend = "disque"
mount_path = "/partenaires/acme"
home_dir = "/partenaires/acme"
acl = [{ path = "/", rights = ["read", "list"], recursive = true }] # "/": the root of the mount
[[roles.mounts]]
backend = "acme-amont"
mount_path = "/partenaires/amont"
home_dir = "/depot"
acl = [{ path = "/", rights = ["read", "list"], recursive = true }]
/ and /partenaires are synthetic; /partenaires/amont is the
upstream’s /depot.