// Package daemon — dispatch_handler.go mounts the orca.v1.Dispatch // service on the daemon's HTTP server. The service is registered as // two handlers (POST /orca.v1.Dispatch/Submit and /Status) and is // gated on the mTLS state — if the server is in plaintext mode // (v0.1 compat), the handlers refuse to serve. package daemon import ( "net/http" "git.cloudinit.dev/coreci/orca/internal/acl" "git.cloudinit.dev/coreci/orca/internal/transport" ) // DispatchHandlers groups the Submit and Status handlers so they // can be registered as a unit on the daemon mux. type DispatchHandlers struct { Submit *transport.SubmitHandler Status *transport.StatusHandler } // NewDispatchHandlers builds the dispatch handler pair from a // transport.Dispatcher (the engine layer satisfies this). func NewDispatchHandlers(d transport.Dispatcher, dedupe *transport.IdempotencyStore) *DispatchHandlers { if dedupe == nil { dedupe = transport.NewIdempotencyStore() } return &DispatchHandlers{ Submit: transport.NewSubmitHandler(d, dedupe), Status: transport.NewStatusHandler(d), } } // Mount registers Submit and Status on the given mux. Called by the // daemon's mux builder. P04 wraps each handler in an ACL middleware // that calls s.acl.Check before delegating; the dispatch namespace is // the default (cluster-wide) namespace. Submit = write, Status = read. // When s.acl is nil (legacy/compat) the middleware is a no-op pass- // through. func (h *DispatchHandlers) Mount(mux *http.ServeMux) { mux.Handle("/orca.v1.Dispatch/Submit", h.Submit) mux.Handle("/orca.v1.Dispatch/Status", h.Status) } // mountDispatchWithACL mounts the dispatch handlers wrapped in ACL // middleware. P04: Submit requires write on "_defaults"; Status // requires read. When policy is nil, the handlers are mounted // unwrapped (legacy/compat for tests). func (h *DispatchHandlers) mountWithACL(mux *http.ServeMux, policy *aclPolicy) { if policy == nil { h.Mount(mux) return } mux.Handle("/orca.v1.Dispatch/Submit", aclMiddleware(policy, "_defaults", acl.PermWrite, h.Submit)) mux.Handle("/orca.v1.Dispatch/Status", aclMiddleware(policy, "_defaults", acl.PermRead, h.Status)) } // aclMiddleware wraps an http.Handler with an ACL check. On denial in // enforce mode it writes a 403 and returns; in log-only mode (C-45) // it logs and delegates. The extracted identity is stashed in the // request context under the identity key so downstream handlers / the // audit layer can read it. func aclMiddleware(policy *aclPolicy, ns string, perm acl.Permission, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if id, ok := policy.Check(r, ns, perm); !ok { deny(w, id, ns, perm) return } next.ServeHTTP(w, r) }) }