Something went wrong. Try again.
Cooperative email for PDS operators
Something went wrong. Try again.
5.5 kB · 141 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142// SPDX-License-Identifier: AGPL-3.0-or-later
package main
import ( "encoding/json" "net/http" "os")
// devStatusResponse is the JSON shape returned by /admin/dev/web-status.// Reports the wiring state of each cmd/web dep without exposing the// underlying secret values. Used by operators curling the endpoint from// a tailnet host to debug wiring regressions when SSH into the staging// microvm isn't available (e.g., internal ref — Phase 5c-1 broke// /enroll on staging because the adminClient was unwired and there was// no in-VM way to confirm).type devStatusResponse struct { // BinaryPath is the running cmd/web binary's absolute path // (resolved via /proc/self/exe). Confirms the running closure // matches the latest deploy. BinaryPath string `json:"binaryPath"`
// Wired reports the boolean status of each dep. Mirrors the // log line emitted at startup ("web.deps.5c-1_wired: …") so the // state is visible to a running diagnostic, not just journalctl. Wired struct { Funnel bool `json:"funnel"` DIDResolver bool `json:"didResolver"` LabelChecker bool `json:"labelChecker"` AdminClient bool `json:"adminClient"` SessionVerifier bool `json:"sessionVerifier"` } `json:"wired"`
EnrollMounted bool `json:"enrollMounted"` AccountMounted bool `json:"accountMounted"`
// SecretsCheck reports the on-disk state of each secret file that // cmd/web's wireFoundationDeps tries to read at startup. Whenever // a wired:false above is paired with the corresponding path being // missing/empty/unreadable, the diagnosis is at the deploy layer // (sops template not rendered, wrong path, wrong owner). When the // file looks fine but the dep is still nil, the issue is in the // loader (e.g., session secret < 32 bytes, base64 decode failed). // // Never includes the secret content — only path + existence + size // + (if any) the load error message. SecretsCheck struct { AdminTokenFile secretFileStatus `json:"adminTokenFile"` SessionSecretFile secretFileStatus `json:"sessionSecretFile"` } `json:"secretsCheck"`}
// secretFileStatus is the per-secret-file diagnostic block. Path is// always the configured value (or empty); Exists/Size/LoadError are// only populated when Path is non-empty.type secretFileStatus struct { Path string `json:"path"` Exists bool `json:"exists"` Size int64 `json:"size"` LoadError string `json:"loadError,omitempty"`}
// inspectSecretFile probes one secret file path: stats it, runs the// same loader cmd/web uses at startup, and reports any error string.// Never returns the loaded value — only the metadata that distinguishes// "file missing" from "file empty" from "loader rejected the format".func inspectSecretFile(path string, loader func(string) error) secretFileStatus { out := secretFileStatus{Path: path} if path == "" { return out } if info, err := os.Stat(path); err == nil { out.Exists = true out.Size = info.Size() } if err := loader(path); err != nil { out.LoadError = err.Error() } return out}
// registerDevEndpoints attaches staging-only debug routes when// cfg.EnableDevEndpoints is true. atmos-staging.nix sets this to true;// prod leaves it false so this handler is never registered in// production binaries.//// The endpoint is a JSON status surface, not a control surface — it// only reflects state, never mutates. Even so, gate it explicitly to// avoid information leak about the cmd/web internals on prod.func registerDevEndpoints(mux *http.ServeMux, cfg *WebConfig, deps *foundationDeps, enrollMounted, accountMounted bool) { if cfg == nil || !cfg.EnableDevEndpoints { return } mux.HandleFunc("/admin/dev/web-status", func(w http.ResponseWriter, r *http.Request) { resp := devStatusResponse{ BinaryPath: binaryPath(), EnrollMounted: enrollMounted, AccountMounted: accountMounted, } if deps != nil { resp.Wired.Funnel = deps.funnel != nil resp.Wired.DIDResolver = deps.didResolver != nil resp.Wired.LabelChecker = deps.labelChecker != nil resp.Wired.AdminClient = deps.adminClient != nil resp.Wired.SessionVerifier = deps.sessionVerifier != nil } // Probe the configured secret-file paths fresh on every request // (they're tiny stat + read calls; this is staging-only). This // is what distinguishes "wired:false because path empty" from // "wired:false because file missing" from "wired:false because // loader rejected the contents". resp.SecretsCheck.AdminTokenFile = inspectSecretFile(cfg.AdminTokenFile, func(p string) error { _, err := loadAdminToken(p) return err }) resp.SecretsCheck.SessionSecretFile = inspectSecretFile(cfg.SessionSecretFile, func(p string) error { _, err := loadSessionSecret(p) return err }) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(resp) })}
// binaryPath returns the absolute path of the running cmd/web binary// (resolved through /proc/self/exe on Linux). Lets the operator// confirm the running closure matches the latest deploy by comparing// against /var/lib/microvms/atmos-staging/booted's atmos-web ExecStart.// Falls back to os.Args[0] on platforms where /proc/self/exe doesn't// exist (macOS dev builds), which is fine — the binary path on dev is// less load-bearing than on staging.func binaryPath() string { if p, err := os.Readlink("/proc/self/exe"); err == nil { return p } if len(os.Args) > 0 { return os.Args[0] } return ""}