Files
orca/internal/txn/auth_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

76 lines
2.5 KiB
Go

package txn
import (
"context"
"errors"
"os"
"testing"
)
// TestApplyRefusesUnauthorized (P04, T4, C-44) verifies that Apply
// returns ErrUnauthorized when the Authorize hook returns an error.
// The apply path MUST refuse to run without a verified operator
// token.
func TestApplyRefusesUnauthorized(t *testing.T) {
tr := &authMockTransport{}
opts := ApplyOptions{
Namespace: "myapp",
Authorize: func(ctx context.Context) (string, error) {
return "", errors.New("ORCA_OIDC_TOKEN not set")
},
}
err := Apply(context.Background(), "T-deadbeefdeadbeef", "lead.example.com", tr, opts)
if err == nil {
t.Fatal("expected error when Authorize fails, got nil")
}
if !errors.Is(err, ErrUnauthorized) {
t.Errorf("expected ErrUnauthorized, got %v", err)
}
}
// TestApplyAuthorizesWithHook verifies that Apply proceeds when the
// Authorize hook returns nil, and that the actor is logged.
func TestApplyAuthorizesWithHook(t *testing.T) {
tr := &authMockTransport{execOut: []byte("applied\n")}
opts := ApplyOptions{
Namespace: "myapp",
Authorize: func(ctx context.Context) (string, error) {
return "oidc:operator@example.com", nil
},
}
err := Apply(context.Background(), "T-deadbeefdeadbeef", "lead.example.com", tr, opts)
// We expect a non-ErrUnauthorized error here because the mock
// transport's orca-pull.sh path doesn't exist; the point is that
// the apply got PAST the authorize hook.
if err != nil && errors.Is(err, ErrUnauthorized) {
t.Errorf("apply should not be refused after successful authorize: %v", err)
}
}
// TestApplyNoAuthorizeHookSkipsCheck verifies that when Authorize is
// nil (legacy/test path), the apply proceeds without an auth check.
// This preserves backward compat for tests that call Apply directly.
func TestApplyNoAuthorizeHookSkipsCheck(t *testing.T) {
tr := &authMockTransport{execOut: []byte("applied\n")}
opts := ApplyOptions{Namespace: "myapp"}
err := Apply(context.Background(), "T-deadbeefdeadbeef", "lead.example.com", tr, opts)
// Any error is fine as long as it's not ErrUnauthorized.
if err != nil && errors.Is(err, ErrUnauthorized) {
t.Errorf("apply should skip auth when Authorize is nil: %v", err)
}
}
// authMockTransport is a minimal Transport for the auth tests.
type authMockTransport struct {
execOut []byte
execErr error
}
func (m *authMockTransport) WriteFileIdempotent(ctx context.Context, peer, path string, content []byte, mode os.FileMode) (bool, error) {
return true, nil
}
func (m *authMockTransport) Exec(ctx context.Context, peer, cmd string) ([]byte, error) {
return m.execOut, m.execErr
}