Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.