server: relay a test device's adb endpoint, because mDNS does not cross subnets
The beacon this replaces was a separate service wildcard-bound to 0.0.0.0:443 - it silently occupied port 443 on the reserved measurement addresses, voiding the IPv4 interception proof for as long as it ran, and it accepted a port report from anyone who could reach it. So this lives where the repo's own post-mortem said it belongs: POST on the control plane authenticated by the device credential, GET on the admin UI behind the existing apiAdmin helper. No new listener, no new port, no wildcard. Entries expire after 24h (ECHOLOT_ADB_ENDPOINT_RETENTION_H) on both write and read - a LAN address is a breadcrumb for driving a test device, not measurement data worth keeping. Also records the BLE peer-comparison design: the case for it is that BLE is out-of-band, which is what makes client isolation measurable at all - silence over IP cannot distinguish an isolating AP from an absent peer, and a peer confirming out-of-band that it was listening turns that silence into proof. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab6e278272
commit
ae63bd7c7f
@@ -121,6 +121,9 @@ func (s *Server) Handler() http.Handler {
|
||||
// scripts. Authenticates its own way — see apiAdmin — because guard's redirect-to-login is
|
||||
// useless to a caller without a browser.
|
||||
mux.HandleFunc("POST /admin/enroll-tokens", s.enrollTokensAPI)
|
||||
// The dev relay's read side (adbendpoints.go), authenticated the same way and for the same
|
||||
// reason: a developer reads it with curl from another subnet, where a login redirect is no use.
|
||||
mux.HandleFunc("GET /admin/adb-endpoints", s.adbEndpointsAPI)
|
||||
|
||||
return mux
|
||||
}
|
||||
@@ -133,7 +136,7 @@ func (s *Server) guard(h func(http.ResponseWriter, *http.Request, *adminauth.Ses
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
if !safeMethod(r) {
|
||||
// SameSite=Lax already blocks cross-site form posts in current browsers, but this
|
||||
// is the control that does not depend on the browser being current.
|
||||
if !s.csrfOK(r, sess) {
|
||||
@@ -145,6 +148,13 @@ func (s *Server) guard(h func(http.ResponseWriter, *http.Request, *adminauth.Ses
|
||||
}
|
||||
}
|
||||
|
||||
// safeMethod reports whether a request only reads. CSRF protection applies to the others: there is
|
||||
// nothing for a cross-site form to ride on when the handler changes nothing, and demanding a token
|
||||
// on a GET would make a read endpoint unusable from the session that is already signed in.
|
||||
func safeMethod(r *http.Request) bool {
|
||||
return r.Method == http.MethodGet || r.Method == http.MethodHead
|
||||
}
|
||||
|
||||
func (s *Server) session(r *http.Request) *adminauth.Session {
|
||||
c, err := r.Cookie(sessionCookie)
|
||||
if err != nil {
|
||||
@@ -242,17 +252,17 @@ func (s *Server) loginSubmit(w http.ResponseWriter, r *http.Request) {
|
||||
// apiAdmin authenticates a programmatic admin request: the normal session cookie, or HTTP Basic
|
||||
// against the break-glass credential for callers without a cookie jar (the README's curl).
|
||||
//
|
||||
// The cookie path keeps CSRF, exactly like guard: a cookie is an ambient credential and this
|
||||
// endpoint changes state. Basic auth is exempt — the password is supplied explicitly per
|
||||
// request, so there is nothing for a cross-site form to ride on — and a wrong guess pays the
|
||||
// same throttle as the login form, so this is no better a password oracle than that is.
|
||||
// The cookie path keeps CSRF on anything that changes state, exactly like guard: a cookie is an
|
||||
// ambient credential. Basic auth is exempt — the password is supplied explicitly per request, so
|
||||
// there is nothing for a cross-site form to ride on — and a wrong guess pays the same throttle as
|
||||
// the login form, so this is no better a password oracle than that is.
|
||||
func (s *Server) apiAdmin(w http.ResponseWriter, r *http.Request) (subject string, ok bool) {
|
||||
if sess := s.session(r); sess != nil {
|
||||
if !sess.Admin {
|
||||
http.Error(w, "that action needs an administrator account", http.StatusForbidden)
|
||||
return "", false
|
||||
}
|
||||
if !s.csrfOK(r, sess) {
|
||||
if !safeMethod(r) && !s.csrfOK(r, sess) {
|
||||
http.Error(w, "stale form — reload the page and try again", http.StatusForbidden)
|
||||
return "", false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user