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

Console et API d’administration

[admin]
listen = "0.0.0.0:8080"           # API de fichiers, explorateur
control_listen = "0.0.0.0:8081"   # console, /admin, /metrics, sondes
bearer_token = "changez-moi-en-production"

[api]
enabled = true
prefix = "/api/v1/files"

Les cles : reference.

Une porte ou deux

ConfigurationEcouteurCe qu’il sertRuntime
sans APIlistenconsole, /admin/*, /metrics, sondesadmin (deux fils admin-rt)
API, sans control_listenlistentout, et l’APIprincipal, partage avec les transferts
control_listencontrol_listen/admin/*, /, /ui/*, /metrics, /health, /livez, /readyzadmin
listenAPI de fichiers, explorateur, /api/docs et /api/openapi.jsonprincipal
[server] probes_listenprobes_listen/livez, /readyz, /health et /metrics, en HTTP simple, et rien d’autre ; ils quittent listen et control_listenadmin

Avec l’API de fichiers, posez control_listen : bans, kicks, /metrics et sondes restent joignables quand les transferts saturent le serveur. Le chart Helm le fait (service.control.port). probes_listen donne aux sondes et a Prometheus un port sans TLS ni console ; sans [admin], c’est le seul ecouteur HTTP (Kubernetes). La console lit sa sante sur /admin/health, servi a cote d’elle dans tous les cas.

S’authentifier

Chaque route protegee demande Authorization: Bearer <jeton>.

JetonIdentitePermissions
un jeton de session, rendu par POST /admin/login (Session de console)le compte localcelles des roles admin que nomment ses authorities, relues a chaque requete
un JWT verifie par [auth.jwt]son nomcelles des roles admin que nomme sa revendication de roles (authorities_path)
egal a bearer_token<static-token>toutes ; un secours, que allow_static_token = false ecarte (le chart Helm le pose)

Un role admin nomme des permissions ; une authority qui porte son nom les donne.

[[admin.roles]]
name = "support"
permissions = ["overview", "sessions", "logs"]

[[admin.roles]]
name = "admins"
permissions = ["overview", "sessions", "kick", "bans", "unban", "config", "logs", "audit", "revoke", "grant"]

Les noms des roles admin et ceux de [[roles]] sont distincts. Un compte qui porte les deux sortes ouvre la console et les portes de fichiers ; un compte qui ne porte que des roles admin n’ouvre que la console. Le service d’autorisation ne recoit jamais un nom de role admin, et aucun jeton d’administration n’ouvre un fichier. Les echecs comptent dans [admin.ban] (Bans).

PermissionRoutes
aucune (tout jeton admis)GET /admin/me, POST /admin/logout ; POST /admin/session/renew (jeton de session)
overviewGET /admin/resources, GET /admin/status, GET /admin/health, GET /admin/doors
sessionsGET /admin/sessions ; GET /admin/events (evenements de session)
kickDELETE /admin/sessions/{id}
configGET /admin/config, GET /admin/sessions/{id}/roles
bansGET /admin/bans ; GET /admin/events (evenements de ban)
unbanDELETE /admin/bans/{protocol}/{ip}
logsGET /admin/logs/app, GET /admin/logs/app/stream
auditGET /admin/logs/audit, GET /admin/logs/audit/stream (chaque lecture est auditee)
revokeGET /admin/revocations, POST /admin/revocations
grantGET, POST /admin/grants, DELETE /admin/grants/{id}, GET /admin/grants/candidates (les comptes et les montages demandent aussi config, les noms vus sessions) : un role de fichiers accorde pour un temps ([admin.grants], reference) ; a until ou a la revocation, les sessions SFTP ouvertes par l’acces et les transferts REST en cours sous son montage sont coupes (sur une autre instance : a sa relecture suivante) ; GET /admin/events (evenements d’acces)
publiquesPOST /admin/login, GET /admin/login, /, /ui/*, /health, /livez, /readyz, /metrics ; /api/docs, /api/openapi.json avec api.openapi

Sessions

{"total": 1, "offset": 0, "limit": 50, "server_time": "2026-10-07T14:42:10.125Z", "inactivity_timeout_secs": 600,
 "sessions": [{"session_id": "550e8400-e29b-41d4-a716-446655440000", "username": "alice", "remote_addr": "192.168.1.42:54321", "auth_method": "password",
   "mounts": [{"mount_path": "/", "backend": "disque", "home_dir": "/alice", "acl": [{"path": "/", "rights": ["read", "write", "list", "delete", "rename"], "recursive": true}]}],
   "connected_at": "2026-10-07T14:30:00Z", "idle_since": null, "last_sftp_op_at": "2026-10-07T14:41:25.402Z", "last_traffic_at": "2026-10-07T14:42:05.871Z",
   "bytes_read": 1048576, "bytes_written": 524288, "sftp_ops": 412, "has_active_transfer": false, "pod_name": "craft-file-gate-0", "authorities": ["utilisateurs"],
   "client_version": "SSH-2.0-OpenSSH_9.6", "algorithms": {"kex": "curve25519-sha256", "host_key": "ssh-ed25519", "cipher": "chacha20-poly1305@openssh.com",
     "mac_client_to_server": "hmac-sha2-256", "mac_server_to_client": "hmac-sha2-256"}, "user_key": null, "weak_algorithms": [], "type": "sftp"}]}
ChampSens
mountsun objet par montage : mount_path, backend (son nom), home_dir developpe, acl relative au montage
last_sftp_op_atla derniere requete SFTP, refus compris, keepalive non compris
last_traffic_atles derniers octets du client, keepalives compris ; la coupure par inactivity_timeout_secs part de la
has_active_transferun fichier ouvert ; retient l’arret pendant le delai de grace
user_keyla cle SSH : key_type, fingerprint, signature_algorithm
weak_algorithmsles algorithmes negocies juges faibles

La liste porte aussi les transferts REST (upload, telechargement) en cours depuis long_request_threshold_secs (30 s par defaut, 0 : tous, applique a chaud) : "type": "rest" (une session SFTP a "sftp"), session_id, username, remote_addr, method (PUT, GET), direction (upload, download), path (le chemin vu par l’utilisateur), mounts (mount_path, backend), connected_at (son debut), bytes, bytes_read, bytes_written, bytes_per_sec (la moyenne depuis le debut).

RequeteEffet
GET /admin/sessions?offset=&limit=&scope=limit 50 par defaut, 1000 au plus ; scope=cluster : aussi les sessions de chaque instance, champ instance, et peers (ce que chaque pair a repondu) ; local par defaut
GET /admin/sessions/{id}/rolesles montages tels que resolus a la connexion (effective.mounts, avec roles, max_file_mb, create_home, hidden_stores) et la definition actuelle de chaque role
DELETE /admin/sessions/{id}coupe la session ({"status": "disconnected"}) et la retire de la liste et du compte de max_sessions_per_user ; un transfert REST est coupe comme par un client parti, ligne d’audit reason="session ended: admin_kick" ; tenue par une autre instance, c’est elle qui la coupe (instance)
GET /admin/doorsles portes configurees : name, readiness (accepting, not_accepting, hosted), started, sessions_open, activity

Journaux et flux

Avec [log] dir, GET /admin/logs/{app|audit} rend les dernieres entrees du fichier courant, filtrees par le serveur. Voir Journaux.

Parametres : lines (500, 5000 au plus ; 4 Mio parcourus au plus), level (ERROR a TRACE), since et until (RFC 3339), q (texte, 256 octets), field=cle:valeur (16 au plus). 3 lectures simultanees au plus.

Les flux SSE (text/event-stream) suivent l’activite en direct :

FluxEvenements
GET /admin/eventssession.opened, session.closed, session.kicked, session.revoked, ban.added, ban.lifted, lagged
GET /admin/logs/{app|audit}/streamentry, une par nouvelle entree retenue ; suit une rotation

Au plus 8 flux ouverts, 2 par identite ; un flux finit a l’exp de son jeton ou a sa revocation, au plus tard apres 15 minutes.

Sondes et sante

RouteRepondUsage
/livez200 {"status": "alive"} tant que le processus vitsonde de vie ; craft-file-gate healthcheck
/readyz200 si le port SFTP accepte et que le runtime principal prend une tache en 500 ms, 503 sinon et des le signal d’arretsonde de disponibilite
/health200 toujours ; status ok ou degraded (service d’autorisation injoignable), checks.auth_service, checks.data_plane (verdict de /readyz), rien d’autresupervision
/admin/health/health, avec la version, les sessions actives et les backends configures ; permission overviewla console
/admin/statusready, degraded ou not_ready, avec chaque verification (sftp, data_runtime, auth_service, jwks) et son detaille bandeau de la console

La console

http://serveur:8081/ : une page qui appelle /admin/*. Elle ne charge rien d’un tiers. Sa connexion : Session de console.

OngletContenuPermission
Overviewstatut, version, sessions, service d’autorisationoverview
Metricsbandeau de /admin/status, tuiles sur cinq minutes, backends, top usersoverview (top users : sessions)
Instancesavec [cluster], chaque instance : statut, version, sessions, bans, fin du certificat, santeoverview
Sessionsune ligne par session SFTP et par transfert REST long, colonne Type (sftp, rest) ; montages, activite, kick ; avec un pair, chaque instance (colonne Instance)sessions, kick
Configurationbackends (secrets masques, horloge du stockage), roles, cles d’hoteconfig
Bansbans en cours, levee ; avec un pair, chaque instance (colonne Instance)bans, unban
Logsjournal et piste d’audit, filtres, suivi en direct ; avec [log] dirlogs, audit
Revocationsrevoquer les sessions d’un compte, celles en vigueur ; quand la porte connecte des comptesrevoke
Temporary accessaccorder un role de fichiers pour un temps, les acces vivants et finis depuis un jour, revocation ; Acces temporaire ; la vue Sessions montre les acces d’une sessiongrant
Hash toolshash-password et verify-password en WebAssembly dans la page : le mot de passe ne quitte pas le navigateuraucune
  • Les filtres et tris (ip:, user:, role:, backend:, type:) vivent dans l’URL : /#sessions?q=user:bob&sort=-last_op. Le jeton n’y figure jamais.
  • La page et ses fichiers (/ui/*) portent une CSP stricte (default-src 'none', tout de la meme origine, 'wasm-unsafe-eval') ; un reverse proxy qui pose la sienne permet au moins autant.

Ce que vous verrez

QuandLigne
demarrageINFO starting admin API server (HTTP) (ou (HTTPS))
demarrage avec [admin.tls]INFO admin TLS certificate is valid, champs not_after, remaining_days ; /metrics publie la date