Authentication
Two sources, three doors
| Door | Credential | Source |
|---|---|---|
| SFTP | password | local account, or JWT token under the sentinel name |
| SFTP | public key | local account |
| File API | Authorization: Basic | local account, password |
| File API | Authorization: Bearer | JWT token |
| Admin console and admin API | Authorization: Bearer | session token (local account, password), JWT token, or static token (see Admin API and console) |
A connection is authenticated by a single source, which gives its roles.
A local account alice and the sub alice of a token are two distinct
users.
Choosing the methods
[auth.methods]
jwt = { enabled = false }
local = { enabled = true, password = true, pubkey = true }
A keys-only deployment turns the password off:
local = { enabled = true, password = false, pubkey = true }
The keys: [auth.methods].
Local accounts
[[users]]
username = "alice"
password_hash = "$argon2id$v=19$m=19456,t=2,p=1$..." # craft-file-gate hash-password
authorized_keys = ["ssh-ed25519 AAAA... alice@poste"]
authorities = ["utilisateurs"] # their roles
The keys: [[users]]. In users_file,
they change without a restart.
Hashes
echo -n "mot-de-passe" | craft-file-gate hash-password # the hash only
echo -n "mot-de-passe" | craft-file-gate hash-password --user alice # a [[users]] block
The password is read on stdin, never as an argument. The hash goes to stdout, its parameters to stderr. The Hash tools tab of the admin console does the same in the browser, without sending the password.
Profile (--profile) | argon2id parameters | Memory per check |
|---|---|---|
owasp-min (default) | m=19456,t=2,p=1 | 19 MiB |
rfc9106-low-mem | m=65536,t=3,p=4 | 64 MiB |
A check costs what the stored hash says: the server never re-hashes. Keep a single profile per file: an unknown name then costs exactly the time of a wrong password, and cannot be guessed from the response time.
The argon2 formats ($argon2id$, $argon2i$, $argon2d$) are accepted.
To migrate from another server without knowing the passwords,
allow_sha512_crypt = true or allow_bcrypt = true under [auth.methods] local
also accepts these formats: Reference [auth.methods].
Each time the file is loaded, the line password hashes in use counts the
accounts per format, and so does the metric
craftfilegate_local_users{hash_format}: when a format drops to zero,
remove its flag.
local.max_argon2_memory_kib (19456: the m of the owasp-min profile)
names at load time every account whose hash asks for more; that account
still connects: re-hashing it with the default profile brings it under the
budget.
The check runs on a bounded thread pool, outside the runtime: see Password hashing.
The public key
The client offers a key, then signs if it is in its authorized_keys.
Offered keys do not count: an SSH agent that tries five before the right
one is not banned; a connection that ends without authenticating, after a
declined key, counts one failure.
The signature algorithms of a key
[sftp.algorithms] user_key sets the algorithms allowed to everyone (see
The SFTP door); ssh-rsa (SHA-1) is not in it by
default. A role can allow others to its users:
[[roles]]
name = "partenaire-ancien"
user_key_algorithms = ["ssh-rsa"] # in addition to user_key
[[roles.mounts]]
backend = "disque"
home_dir = "/partenaire"
The exception applies to every user who holds this role, never to a single user. Only the roles of the server’s files grant it, not the roles reached through the authorization service.
JWT
The jwt method is enabled by default.
[auth.jwt]
secret = "remplacez-moi" # or CRAFT_FILE_GATE_JWT_SECRET_FILE
algorithm = "HS256"
username_path = "/sub" # the user's name
authorities_path = "/groups" # their roles
| Door | Where to put the token |
|---|---|
| SFTP | login name jwt (auth.jwt_sentinel_username), the token as password |
| File API, admin API | Authorization: Bearer <token> |
One key source, your choice: secret (HS256, HS384, HS512),
public_key_file (the issuer’s PEM public key: RS256, RS384,
RS512, ES256, ES384) or jwks_url (the key set of an identity
provider, each key stating its algorithm). The keys:
[auth.jwt].
A token is accepted if its signature verifies, if exp is present and in
the future, and if nbf, when present, is past (60 s of leeway for both).
With issuer or audience, the iss or aud claim must be present and
equal. The user name is read at username_path, the roles at
authorities_path (an array of strings).
Plugging in an identity provider (jwks_url)
[auth.jwt]
jwks_url = "https://idp.example.com/.well-known/jwks.json"
algorithm = "RS256"
jwks_refresh_interval_secs = 3600
issuer = "https://idp.example.com/"
| Moment | What the server does |
|---|---|
| startup | validates the file, opens the log, then loads the key set, before opening the doors |
| each request | verifies the token against the key its kid header names |
every jwks_refresh_interval_secs | reloads the set; a key the provider added is learned at that moment |
| refresh with no answer | keeps the cached keys, which go on verifying tokens |
The cache age is the gauge craftfilegate_jwks_cache_age_seconds: alert
on it (see Metrics).
What you will see
| When | Line |
|---|---|
| each load of the accounts | INFO password hashes in use, one field per format |
| JWKS set loaded | INFO JWKS cache refreshed, field keys; then JWKS background refresh started |
| connection accepted | audit connection_accepted, auth_method = password, pubkey, jwt, basic or bearer; per key, signature_algorithm |