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

Authentication

Two sources, three doors

DoorCredentialSource
SFTPpasswordlocal account, or JWT token under the sentinel name
SFTPpublic keylocal account
File APIAuthorization: Basiclocal account, password
File APIAuthorization: BearerJWT token
Admin console and admin APIAuthorization: Bearersession 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 parametersMemory per check
owasp-min (default)m=19456,t=2,p=119 MiB
rfc9106-low-memm=65536,t=3,p=464 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
DoorWhere to put the token
SFTPlogin name jwt (auth.jwt_sentinel_username), the token as password
File API, admin APIAuthorization: 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/"
MomentWhat the server does
startupvalidates the file, opens the log, then loads the key set, before opening the doors
each requestverifies the token against the key its kid header names
every jwks_refresh_interval_secsreloads the set; a key the provider added is learned at that moment
refresh with no answerkeeps 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

WhenLine
each load of the accountsINFO password hashes in use, one field per format
JWKS set loadedINFO JWKS cache refreshed, field keys; then JWKS background refresh started
connection acceptedaudit connection_accepted, auth_method = password, pubkey, jwt, basic or bearer; per key, signature_algorithm