d62cc08b2e
gates / gates (push) Successful in 6m44s
- Configuration → Access tokens: named, scoped (group read/write), expiring (≤365 d), shown once, stored SHA-256 only, every use logged by name. Bearer fhp_… on the web surface only; /api/v1 refuses it. - Route table = the code's single source (web/routes.go), pinned to ServeHTTP's switch; session-only: secrets shown/minted, deletes/resets, password, tokens. - R-952 closed: /api/v1 route table names each caller, pinned to the switch; no-credential refusal test. - Fail-open fixed: an empty operator password served the UI open (main skipped RequireAuth). Live hub never open. - 05 §17, 04 §6.2, 13, 09 §3 251; register 155→157 (+R-1054, R-1055, R-1056, −R-952); STATUS. - website: prowlarr-logo.svg (byte copy from felhom.eu-media) — the site gate refused every push. Not deployed — waits for the operator's yes. Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
84 lines
5.2 KiB
Go
84 lines
5.2 KiB
Go
package api
|
|
|
|
// The box-API route table (hub v0.152.0, R-952, `05` §17.6). EVERY case of ServeHTTP's route switch has one entry,
|
|
// in the same order and with the same match, naming WHO may call it. TestAPIRouteTable_MatchesSwitch reads the
|
|
// switch from handler.go's syntax tree and fails on a route added without an entry here; TestAPIRoutes_NoCredential
|
|
// sends every non-public route a request with no credential and requires a refusal. The table documents and pins;
|
|
// each handler still does its own check (the table is not consulted at run time).
|
|
//
|
|
// An operator access token (fhp_…) is refused on EVERY route here, before the switch (handler.go ServeHTTP).
|
|
|
|
type apiAuth string
|
|
|
|
const (
|
|
authPublic apiAuth = "public" // no credential by design — the reason is on the entry
|
|
authCustomer apiAuth = "customer" // the household's controller key (or the global key)
|
|
authHost apiAuth = "host" // the box's own host key, self-scoped (or the global key)
|
|
authGlobal apiAuth = "global" // the operator/global key only
|
|
authPassphrase apiAuth = "passphrase" // X-Retrieval-Password of that household
|
|
authAppliance apiAuth = "appliance" // the appliance's own bearer token
|
|
authRecovery apiAuth = "recovery" // only while the operator has armed recovery mode for the host
|
|
)
|
|
|
|
type apiRoute struct {
|
|
Method string
|
|
Exact, Prefix, Suffix, Contains string
|
|
Auth apiAuth
|
|
Why string // for authPublic
|
|
}
|
|
|
|
var apiRoutes = []apiRoute{
|
|
{Method: "POST", Exact: "/report", Auth: authCustomer},
|
|
{Method: "GET", Exact: "/wait", Auth: authCustomer},
|
|
{Method: "POST", Exact: "/appliance/register", Auth: authPublic, Why: "a secret-free ISO box registers itself; per-IP rate-limited, answers no enumeration"},
|
|
{Method: "GET", Exact: "/appliance/poll", Auth: authAppliance},
|
|
{Method: "POST", Exact: "/host-report", Auth: authHost},
|
|
{Method: "POST", Exact: "/host-enroll", Auth: authPassphrase},
|
|
{Method: "POST", Exact: "/admin/hosts", Auth: authGlobal},
|
|
{Method: "PUT", Prefix: "/hosts/", Suffix: "/escrow", Auth: authHost},
|
|
{Method: "GET", Prefix: "/hosts/", Suffix: "/escrow", Auth: authHost},
|
|
{Method: "GET", Prefix: "/hosts/", Suffix: "/escrow/retained", Auth: authHost},
|
|
{Method: "PUT", Prefix: "/hosts/", Suffix: "/recovery-credential", Auth: authHost},
|
|
{Method: "GET", Prefix: "/admin/hosts/", Suffix: "/recovery-credential", Auth: authGlobal},
|
|
{Method: "PUT", Prefix: "/admin/hosts/", Suffix: "/recovery-mode", Auth: authGlobal},
|
|
{Method: "DELETE", Prefix: "/admin/hosts/", Suffix: "/recovery-mode", Auth: authGlobal},
|
|
{Method: "POST", Prefix: "/hosts/", Suffix: "/re-enroll", Auth: authRecovery},
|
|
{Method: "GET", Prefix: "/hosts/", Suffix: "/restore-directive", Auth: authHost},
|
|
{Method: "POST", Prefix: "/hosts/", Suffix: "/wg", Auth: authHost},
|
|
{Method: "POST", Prefix: "/hosts/", Suffix: "/pbs/consume-token", Auth: authHost},
|
|
{Method: "POST", Prefix: "/hosts/", Suffix: "/os-report", Auth: authHost},
|
|
{Method: "GET", Prefix: "/hosts/", Suffix: "/desired-state", Auth: authHost},
|
|
{Method: "GET", Prefix: "/hosts/", Suffix: "/jobs", Auth: authHost},
|
|
{Method: "DELETE", Prefix: "/hosts/", Contains: "/jobs/", Auth: authHost},
|
|
{Method: "PUT", Prefix: "/admin/hosts/", Suffix: "/desired-state", Auth: authGlobal},
|
|
{Method: "POST", Prefix: "/admin/hosts/", Suffix: "/jobs", Auth: authGlobal},
|
|
{Method: "PUT", Exact: "/admin/wg/endpoint", Auth: authGlobal},
|
|
{Method: "GET", Exact: "/admin/wg/endpoint", Auth: authGlobal},
|
|
{Method: "POST", Exact: "/admin/wg/peers", Auth: authGlobal},
|
|
{Method: "DELETE", Exact: "/admin/wg/peers", Auth: authGlobal},
|
|
{Method: "GET", Exact: "/admin/wg/peers", Auth: authGlobal},
|
|
{Method: "PUT", Exact: "/admin/wg/operator-peer", Auth: authGlobal},
|
|
{Method: "GET", Exact: "/admin/wg/operator-peer", Auth: authGlobal},
|
|
{Method: "POST", Exact: "/claim/reset-request", Auth: authCustomer},
|
|
{Method: "POST", Exact: "/event", Auth: authCustomer},
|
|
{Method: "POST", Exact: "/mail", Auth: authCustomer},
|
|
{Method: "POST", Exact: "/notify", Auth: authCustomer},
|
|
{Method: "POST", Exact: "/preferences", Auth: authCustomer},
|
|
{Method: "GET", Exact: "/customers", Auth: authGlobal},
|
|
{Method: "GET", Prefix: "/customers/", Auth: authGlobal},
|
|
{Method: "GET", Prefix: "/recovery/", Auth: authPassphrase},
|
|
{Method: "GET", Prefix: "/config/", Auth: authPassphrase},
|
|
{Method: "POST", Prefix: "/offsite/consume-password/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/register-key/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/confirm-key/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/move-aside/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/window-open/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/window-close/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/abandon-request/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/abandon-cancel/", Auth: authCustomer},
|
|
{Method: "POST", Prefix: "/offsite/abandon-status/", Auth: authCustomer},
|
|
{Method: "GET", Prefix: "/artifacts/", Auth: authPassphrase},
|
|
{Method: "GET", Exact: "/assets/manifest", Auth: authCustomer},
|
|
{Method: "GET", Prefix: "/assets/file/", Auth: authCustomer},
|
|
}
|