server: upstream trains, observed TTL/DSCP/ECN, rate limits, action ids
Types 0x03/0x04/0x05 land with a bounded columnar train buffer (head kept, truncation declared) and grant-free multi-part reports - a report row is smaller than the packet it answers, so $3.4 holds without a grant. The read loop now collects TTL/TOS cmsgs on Linux, replacing the 0xFF stubs in the observation block with what the kernel saw; downtrain gained a dscp parameter, so DSCP survival is measurable in both directions. Rate limiting ($2.5) exists now: per-credential AND per-source buckets, 429 on the control plane, silent drop on the data plane after the HMAC gate and before the replay window. UDP ceilings default above the largest legitimate run - a limit that clips a real measurement produces a confidently wrong number. Every granted packet carries its action_id at payload[8:16]; overlapping actions were unattributable before. Canary DNS logs now honor the stated 24h privacy default. /admin/enroll-tokens answers the spec's JSON shape. protocol_version 1.0.1 (additive). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f6849f8e6a
commit
8118e213ae
@@ -167,6 +167,41 @@ func (s *Server) mintToken(w http.ResponseWriter, r *http.Request, sess *adminau
|
||||
http.Redirect(w, r, "/devices?link="+url.QueryEscape(s.EnrollLink(tok)), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// enrollTokensAPI is POST /admin/enroll-tokens, the endpoint the spec's §2.1 example names.
|
||||
// Content-negotiated: Accept: application/json gets the spec shape {token, expires_in_s,
|
||||
// enroll_uri}; anything else (a browser) gets the same redirect-to-QR flow as the form above,
|
||||
// so the one path serves both audiences.
|
||||
func (s *Server) enrollTokensAPI(w http.ResponseWriter, r *http.Request) {
|
||||
subject, ok := s.apiAdmin(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
note := r.URL.Query().Get("note")
|
||||
if note == "" {
|
||||
note = "admin-api"
|
||||
}
|
||||
const ttl = 24 * time.Hour
|
||||
tok, err := s.Store.NewEnrollToken(ttl, note)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
slog.Info("enrolment token minted", "by", subject, "note", note)
|
||||
if !strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||
http.Redirect(w, r, "/devices?link="+url.QueryEscape(s.EnrollLink(tok)), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// The whole link, not the bare token (§2.1): the server is the only party holding URL, pin
|
||||
// and token at once, and a hand-assembled pin wrong by one character fails as an inscrutable
|
||||
// TLS error later rather than loudly here.
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"token": tok,
|
||||
"expires_in_s": int(ttl.Seconds()),
|
||||
"enroll_uri": s.EnrollLink(tok),
|
||||
})
|
||||
}
|
||||
|
||||
// EnrollLink is supplied by the caller so this package does not need the control server's pin.
|
||||
var _ = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user