5232fcb808
R-023: Zero-trust enforcement operationally wired. ACL enforcement (C-45 staged rollout): - acl.Check wired into all 5 daemon handlers (dispatch/jobs/nodes/tasks) - health endpoints exempt (liveness probes not gated) - ACL log-only mode default (config acl.enforce=false); enforce after bootstrap ACL verified - sshpush auth: ORCA_OIDC_TOKEN validated against JWKS before apply - txn apply: Authorize hook validates OIDC token before running pull - acl.json mode 0600 (was 0644) - flock on acl.json for concurrent grant/revoke - bootstrap ACL: init grants cluster-admin to orca-admins group + SVID Audit actor identity: - currentActor reads OIDC sub from credentials.json (was hardcoded "cli") - threaded through all audit.Record calls via context WebAuthn registration auth: - BeginRegistration/FinishRegistration require authenticated session - fail-closed 401 when no authFunc configured New files: internal/daemon/acl.go, internal/cli/authactor.go, internal/engine/actor.go, internal/identity/authtoken.go, internal/sshpush/auth.go, internal/txn/auth_test.go ---ci--- project: orca phase: 4 milestone: v0.13 status: complete requirements: covered: [153] ---/ci---
72 lines
2.7 KiB
Go
72 lines
2.7 KiB
Go
// 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)
|
|
})
|
|
}
|