A machine-only HTTP endpoint that returns 200 if the request's X-Auth-Request-Email is in the root .zddc admins: list, 403 otherwise. No body, no redirect — pure authorization decision intended to be polled by an upstream proxy's forward_auth directive. The motivating use case is gating /devshell/* (code-server) in the dev-shell pod on root-admin status before the request ever reaches code-server, which has no built-in ACL of its own. zddc-server's own routes keep the existing .zddc cascade ACL and don't go through this endpoint. Reuses zddc.IsAdmin (one cached map lookup) so the check is cheap enough to call on every request. Edits to /srv/.zddc propagate via the existing fsnotify watcher's policy-cache invalidation. Tests cover empty email, non-admin, admin, and the bootstrap state where no root .zddc exists (deny everyone — the safe default). Docs: zddc/README.md "Forward-auth target for upstream proxies" section + AGENTS.md notes bullet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
64 lines
2.1 KiB
Go
64 lines
2.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// TestServeAuthAdmin pins the contract of the forward_auth endpoint
|
|
// used by upstream proxies (Caddy in the dev-shell pod) to gate
|
|
// admin-only routes:
|
|
//
|
|
// 200 → caller is in the root .zddc admins: list
|
|
// 403 → anonymous, OR not in admins:, OR no admins configured
|
|
//
|
|
// Reuses the profileTestRoot fixture which materializes a temp .zddc
|
|
// with the supplied admins, and the requestWithEmail helper that
|
|
// injects the email into request context the same way ACLMiddleware
|
|
// would in production.
|
|
func TestServeAuthAdmin(t *testing.T) {
|
|
cfg, _ := profileTestRoot(t, []string{"alice@example.com"})
|
|
|
|
cases := []struct {
|
|
name string
|
|
email string
|
|
wantStatus int
|
|
}{
|
|
{"empty email is denied", "", http.StatusForbidden},
|
|
{"non-admin is denied", "bob@example.com", http.StatusForbidden},
|
|
{"admin is allowed", "alice@example.com", http.StatusOK},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := requestWithEmail(http.MethodGet, AuthPathPrefix+"/admin", tc.email)
|
|
rec := httptest.NewRecorder()
|
|
ServeAuthAdmin(cfg, rec, req)
|
|
if rec.Code != tc.wantStatus {
|
|
t.Errorf("status = %d, want %d (body: %q)",
|
|
rec.Code, tc.wantStatus, rec.Body.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestServeAuthAdmin_NoZddcRootDeniesEverything covers the bootstrap-
|
|
// state behaviour: when no /srv/.zddc exists, IsAdmin returns false for
|
|
// everyone, which means /.auth/admin returns 403 universally. This is
|
|
// the desired safe-default before an operator drops a root .zddc onto
|
|
// the share.
|
|
func TestServeAuthAdmin_NoZddcRootDeniesEverything(t *testing.T) {
|
|
// profileTestRoot with nil admins skips writing the file entirely.
|
|
cfg, _ := profileTestRoot(t, nil)
|
|
|
|
for _, email := range []string{"", "alice@example.com", "anyone@example.com"} {
|
|
req := requestWithEmail(http.MethodGet, AuthPathPrefix+"/admin", email)
|
|
rec := httptest.NewRecorder()
|
|
ServeAuthAdmin(cfg, rec, req)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Errorf("email %q without .zddc: status %d, want 403",
|
|
email, rec.Code)
|
|
}
|
|
}
|
|
}
|