package cli import ( "crypto/rand" "encoding/hex" "errors" "fmt" "io/fs" "net" "os" "path/filepath" "strings" "gitea.mynodes.xyz/ilker/llmgw/internal/config" ) // The admin token: what the admin API's /api and /debug paths demand, kept // in a file only root and the server can read. // defaultAdminTokenFile is where the server writes the token under its // systemd unit — its state directory, the one place ProtectSystem=strict // leaves it to write — and where clients look for it when told nothing. const defaultAdminTokenFile = "/var/lib/llmgw/admin-token" // adminTokenPath is the token's file for a configuration: as it says; under // systemd, in the unit's state directory; else secrets/admin-token beside // the configuration, for a server run by hand. func adminTokenPath(cfgPath string, cfg config.Server) string { if cfg.AdminTokenFile != "" { return cfg.AdminTokenFile } if dir := os.Getenv("STATE_DIRECTORY"); dir != "" { return filepath.Join(strings.Split(dir, ":")[0], "admin-token") } return filepath.Join(filepath.Dir(cfgPath), "secrets", "admin-token") } // ensureAdminToken reads the token, making it when there is none. func ensureAdminToken(path string) (string, error) { if b, err := os.ReadFile(path); err == nil { //nolint:gosec // the configured token file if t := strings.TrimSpace(string(b)); len(t) >= 32 { return t, nil } } else if !errors.Is(err, fs.ErrNotExist) { return "", fmt.Errorf("reading the admin token %s: %w", path, err) } if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return "", fmt.Errorf("making %s: %w", filepath.Dir(path), err) } raw := make([]byte, 32) if _, err := rand.Read(raw); err != nil { return "", err } t := hex.EncodeToString(raw) if err := os.WriteFile(path, []byte(t+"\n"), 0o600); err != nil { return "", fmt.Errorf("writing the admin token %s: %w", path, err) } return t, nil } // readAdminToken is the token a client sends: LLMGW_ADMIN_TOKEN, else the // first of these files it can read. Empty when none: the request then goes // without, and the API says what is missing. func readAdminToken(paths ...string) string { if t := strings.TrimSpace(os.Getenv("LLMGW_ADMIN_TOKEN")); t != "" { return t } for _, p := range append(paths, defaultAdminTokenFile) { if p == "" { continue } if b, err := os.ReadFile(p); err == nil { //nolint:gosec // the admin token file if t := strings.TrimSpace(string(b)); t != "" { return t } } } return "" } // checkAdminListen refuses an admin listener off loopback unless the // configuration says it is meant. func checkAdminListen(cfg config.Server) error { host, _, err := net.SplitHostPort(cfg.AdminListen) if err != nil { return fmt.Errorf("admin_listen %q: %w", cfg.AdminListen, err) } if host == "localhost" { return nil } if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() { return nil } if cfg.AdminRemote { return nil } return fmt.Errorf("admin_listen %s is not loopback: the admin API adds models and upgrades nodes, and "+ "its token would cross the network in clear; keep it on 127.0.0.1 and reach it by ssh, or set "+ "admin_remote: true", cfg.AdminListen) }