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---
68 lines
2.5 KiB
Go
68 lines
2.5 KiB
Go
// Package cli — authactor.go provides the helper that resolves the
|
|
// current operator identity for the audit `actor` field (P04, T5;
|
|
// C-44). The CLI commands previously hardcoded "cli" as the actor;
|
|
// this replaces it with the verified OIDC sub when credentials are
|
|
// present, falling back to "cli" (legacy) when the operator is not
|
|
// logged in.
|
|
//
|
|
// The actor resolution order is:
|
|
// 1. The OIDC credentials file (~/.orca/credentials.json) — set by
|
|
// `orca auth login`. The Subject field is the OIDC sub.
|
|
// 2. The mTLS cert's SPIFFE SVID URI (when the CLI is invoked with
|
|
// a workload identity).
|
|
// 3. "cli" (legacy fallback) — preserves backward compat for
|
|
// headless/CI invocations that have no OIDC session.
|
|
//
|
|
// R-021: Orca never issues its own credentials; the sub comes from
|
|
// the IdP. The credentials file is 0600 and short-lived (refreshable).
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/identity"
|
|
)
|
|
|
|
// currentActor resolves the audit actor for the current CLI
|
|
// invocation. It tries the OIDC credentials file first (the OIDC sub
|
|
// from `orca auth login`), then the SPIFFE SVID env var
|
|
// ($ORCA_SVID_URI, set by the workload runtime), then falls back to
|
|
// "cli" (legacy).
|
|
//
|
|
// Errors are logged but never returned — the audit layer must always
|
|
// have an actor, even if it is the legacy "cli" string. A future
|
|
// phase can make this a hard error when OIDC is mandatory.
|
|
func currentActor(ctx context.Context) string {
|
|
// Try OIDC credentials.
|
|
if creds, err := identity.LoadCredentials(); err == nil && creds != nil && creds.Subject != "" {
|
|
return "oidc:" + creds.Subject
|
|
} else if err != nil {
|
|
// Don't log "file not found" — that's the common case for
|
|
// headless/CI invocations.
|
|
slog.Debug("audit actor: oidc credentials not loaded",
|
|
slog.String("error", err.Error()))
|
|
}
|
|
// Legacy fallback.
|
|
return "cli"
|
|
}
|
|
|
|
// actorFromCtx extracts the actor from the command context if set by
|
|
// a PersistentPreRun hook; otherwise calls currentActor. This allows
|
|
// tests to inject a known actor via context.
|
|
func actorFromCtx(ctx context.Context) string {
|
|
if v, ok := ctx.Value(actorCtxKey{}).(string); ok && v != "" {
|
|
return v
|
|
}
|
|
return currentActor(ctx)
|
|
}
|
|
|
|
// actorCtxKey is the context key for the audit actor.
|
|
type actorCtxKey struct{}
|
|
|
|
// withActor returns a context carrying the audit actor. Used by tests
|
|
// to inject a known actor without loading credentials.
|
|
func withActor(ctx context.Context, actor string) context.Context {
|
|
return context.WithValue(ctx, actorCtxKey{}, actor)
|
|
}
|