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
| Configuration | Ecouteur | Ce qu’il sert | Runtime |
|---|---|---|---|
| sans API | listen | console, /admin/*, /metrics, sondes | admin (deux fils admin-rt) |
API, sans control_listen | listen | tout, et l’API | principal, partage avec les transferts |
control_listen | control_listen | /admin/*, /, /ui/*, /metrics, /health, /livez, /readyz | admin |
listen | API de fichiers, explorateur, /api/docs et /api/openapi.json | principal | |
[server] probes_listen | probes_listen | /livez, /readyz, /health et /metrics, en HTTP simple, et rien d’autre ; ils quittent listen et control_listen | admin |
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>.
| Jeton | Identite | Permissions |
|---|---|---|
un jeton de session, rendu par POST /admin/login (Session de console) | le compte local | celles des roles admin que nomment ses authorities, relues a chaque requete |
un JWT verifie par [auth.jwt] | son nom | celles 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).
| Permission | Routes |
|---|---|
| aucune (tout jeton admis) | GET /admin/me, POST /admin/logout ; POST /admin/session/renew (jeton de session) |
overview | GET /admin/resources, GET /admin/status, GET /admin/health, GET /admin/doors |
sessions | GET /admin/sessions ; GET /admin/events (evenements de session) |
kick | DELETE /admin/sessions/{id} |
config | GET /admin/config, GET /admin/sessions/{id}/roles |
bans | GET /admin/bans ; GET /admin/events (evenements de ban) |
unban | DELETE /admin/bans/{protocol}/{ip} |
logs | GET /admin/logs/app, GET /admin/logs/app/stream |
audit | GET /admin/logs/audit, GET /admin/logs/audit/stream (chaque lecture est auditee) |
revoke | GET /admin/revocations, POST /admin/revocations |
grant | GET, 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) |
| publiques | POST /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"}]}
| Champ | Sens |
|---|---|
mounts | un objet par montage : mount_path, backend (son nom), home_dir developpe, acl relative au montage |
last_sftp_op_at | la derniere requete SFTP, refus compris, keepalive non compris |
last_traffic_at | les derniers octets du client, keepalives compris ; la coupure par inactivity_timeout_secs part de la |
has_active_transfer | un fichier ouvert ; retient l’arret pendant le delai de grace |
user_key | la cle SSH : key_type, fingerprint, signature_algorithm |
weak_algorithms | les 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).
| Requete | Effet |
|---|---|
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}/roles | les 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/doors | les 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 :
| Flux | Evenements |
|---|---|
GET /admin/events | session.opened, session.closed, session.kicked, session.revoked, ban.added, ban.lifted, lagged |
GET /admin/logs/{app|audit}/stream | entry, 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
| Route | Repond | Usage |
|---|---|---|
/livez | 200 {"status": "alive"} tant que le processus vit | sonde de vie ; craft-file-gate healthcheck |
/readyz | 200 si le port SFTP accepte et que le runtime principal prend une tache en 500 ms, 503 sinon et des le signal d’arret | sonde de disponibilite |
/health | 200 toujours ; status ok ou degraded (service d’autorisation injoignable), checks.auth_service, checks.data_plane (verdict de /readyz), rien d’autre | supervision |
/admin/health | /health, avec la version, les sessions actives et les backends configures ; permission overview | la console |
/admin/status | ready, degraded ou not_ready, avec chaque verification (sftp, data_runtime, auth_service, jwks) et son detail | le 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.
| Onglet | Contenu | Permission |
|---|---|---|
| Overview | statut, version, sessions, service d’autorisation | overview |
| Metrics | bandeau de /admin/status, tuiles sur cinq minutes, backends, top users | overview (top users : sessions) |
| Instances | avec [cluster], chaque instance : statut, version, sessions, bans, fin du certificat, sante | overview |
| Sessions | une 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 |
| Configuration | backends (secrets masques, horloge du stockage), roles, cles d’hote | config |
| Bans | bans en cours, levee ; avec un pair, chaque instance (colonne Instance) | bans, unban |
| Logs | journal et piste d’audit, filtres, suivi en direct ; avec [log] dir | logs, audit |
| Revocations | revoquer les sessions d’un compte, celles en vigueur ; quand la porte connecte des comptes | revoke |
| Temporary access | accorder 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 session | grant |
| Hash tools | hash-password et verify-password en WebAssembly dans la page : le mot de passe ne quitte pas le navigateur | aucune |
- 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
| Quand | Ligne |
|---|---|
| demarrage | INFO 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 |