Files
orca/internal/webauthn/connector_test.go
T
Jon Chery 5232fcb808 fix(P04): wire ACL enforcement + WebAuthn reg auth + audit actor (REQ-153)
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---
2026-08-07 20:33:39 +00:00

150 lines
5.1 KiB
Go

package webauthn
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
// TestConnectorBuild verifies a Connector can be built with a valid
// RP ID + origin (C-38).
func TestConnectorBuild(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, err := NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore: %v", err)
}
defer store.Close()
c, err := NewConnector(store, "cluster.example.com", "https://cluster.example.com")
if err != nil {
t.Fatalf("NewConnector: %v", err)
}
if c.RPID() != "cluster.example.com" {
t.Errorf("RPID = %q, want cluster.example.com", c.RPID())
}
}
// TestConnectorHealthz verifies the /orca/webauthn/healthz endpoint
// responds with the RP ID.
func TestConnectorHealthz(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/healthz", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("healthz status = %d, want 200", rec.Code)
}
if !strings.Contains(rec.Body.String(), "test.cluster") {
t.Errorf("healthz body should contain rp_id: %s", rec.Body.String())
}
}
// TestConnectorBeginRegistrationNoUsername verifies the register
// endpoint rejects requests without a username when authenticated.
func TestConnectorBeginRegistrationNoUsername(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnectorWithAuth(store, "test.cluster", "https://test.cluster",
func(r *http.Request) (bool, string, error) { return true, "admin", nil })
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/register", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("register without username (authed): %d, want 400", rec.Code)
}
}
// TestConnectorBeginRegistrationUnauthenticated (P04, T9, C-45)
// verifies the register endpoint rejects requests with no
// authenticated session — closing the hole where anyone could
// register a credential for any username.
func TestConnectorBeginRegistrationUnauthenticated(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
// No authFunc → fail-closed (401).
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/register?username=admin", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Errorf("register unauthenticated (no authFunc): %d, want 401", rec.Code)
}
// authFunc that returns false → 401.
c2, _ := NewConnectorWithAuth(store, "test.cluster", "https://test.cluster",
func(r *http.Request) (bool, string, error) { return false, "", nil })
mux2 := c2.Routes()
req2 := httptest.NewRequest("GET", "/orca/webauthn/register?username=admin", nil)
rec2 := httptest.NewRecorder()
mux2.ServeHTTP(rec2, req2)
if rec2.Code != http.StatusUnauthorized {
t.Errorf("register unauthenticated (authFunc=false): %d, want 401", rec2.Code)
}
}
// TestConnectorFinishRegistrationUnauthenticated (P04, T9) verifies
// the finish endpoint also requires authentication.
func TestConnectorFinishRegistrationUnauthenticated(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("POST", "/orca/webauthn/register/finish?username=admin", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Errorf("finish unauthenticated: %d, want 401", rec.Code)
}
}
// TestConnectorBeginLoginNotRegistered verifies login for an
// unregistered user returns 404.
func TestConnectorBeginLoginNotRegistered(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/login?username=ghost", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Errorf("login unregistered: %d, want 404", rec.Code)
}
}
// TestStoreModeEnforced verifies the DB file is 0600 after creation.
func TestStoreModeEnforced(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, err := NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore: %v", err)
}
defer store.Close()
// Trigger a write so the DB file is created on disk.
store.PutCredential(&Credential{
UserID: "u",
CredentialID: []byte("c"),
PublicKey: []byte("p"),
})
info, err := os.Stat(dbPath)
if err != nil {
t.Fatalf("stat db: %v", err)
}
if info.Mode().Perm()&0o077 != 0 {
t.Errorf("db mode = %o, want 0600", info.Mode().Perm())
}
}