From 6839784c7aa3b48871b63af808aa5a5113d88333 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Tue, 24 Mar 2026 11:50:29 +1100 Subject: [PATCH] fix: restrict default OPA policy to path-based admin protection Allow all methods on strategy paths (git, gomod, etc.) from any source, and restrict remote access to admin paths (/api/*, /admin/*). Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- internal/opa/opa.go | 4 ++-- internal/opa/opa_test.go | 18 +++++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9768bdd..9bcd71e 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ s3 { ## Authorization (OPA) -Cachew uses [Open Policy Agent](https://www.openpolicyagent.org/) for request authorization. The default policy allows GET/HEAD from any source and all methods from `127.0.0.1`. +Cachew uses [Open Policy Agent](https://www.openpolicyagent.org/) for request authorization. The default policy allows all requests from localhost and restricts remote access to non-admin paths (`/api/*`, `/admin/*`). Policies must be in `package cachew.authz` and define an `allow` rule. If `allow` is true the request proceeds; otherwise it is rejected with 403. diff --git a/internal/opa/opa.go b/internal/opa/opa.go index c3affd7..230355d 100644 --- a/internal/opa/opa.go +++ b/internal/opa/opa.go @@ -14,12 +14,12 @@ import ( "github.com/block/cachew/internal/logging" ) -// DefaultPolicy allows GET and HEAD from any source, and all methods from localhost. +// DefaultPolicy allows all requests from localhost and restricts remote access to non-admin paths. const DefaultPolicy = `package cachew.authz default allow := false -allow if input.method in {"GET", "HEAD"} allow if startswith(input.remote_addr, "127.0.0.1:") +allow if not input.path[0] in {"api", "admin"} ` // Config for OPA policy evaluation. If neither Policy nor PolicyFile is set, diff --git a/internal/opa/opa_test.go b/internal/opa/opa_test.go index ddd2dec..65ab8b5 100644 --- a/internal/opa/opa_test.go +++ b/internal/opa/opa_test.go @@ -23,15 +23,19 @@ func TestMiddlewareDefaultPolicy(t *testing.T) { tests := []struct { Name string Method string + Path string RemoteAddr string ExpectedStatus int }{ - {"GETFromAnywhere", http.MethodGet, "10.0.0.1:9999", http.StatusOK}, - {"HEADFromAnywhere", http.MethodHead, "10.0.0.1:9999", http.StatusOK}, - {"POSTFromLocalhost", http.MethodPost, "127.0.0.1:12345", http.StatusOK}, - {"PUTFromLocalhost", http.MethodPut, "127.0.0.1:12345", http.StatusOK}, - {"POSTFromRemote", http.MethodPost, "10.0.0.1:9999", http.StatusForbidden}, - {"DELETEFromRemote", http.MethodDelete, "10.0.0.1:9999", http.StatusForbidden}, + {"LocalhostGetAdmin", http.MethodGet, "/admin/log/level", "127.0.0.1:12345", http.StatusOK}, + {"LocalhostPostAPI", http.MethodPost, "/api/v1/object/ns/key", "127.0.0.1:12345", http.StatusOK}, + {"RemoteGetStrategy", http.MethodGet, "/git/github.com/org/repo", "10.0.0.1:9999", http.StatusOK}, + {"RemotePostStrategy", http.MethodPost, "/git/github.com/org/repo", "10.0.0.1:9999", http.StatusOK}, + {"RemoteLiveness", http.MethodGet, "/_liveness", "10.0.0.1:9999", http.StatusOK}, + {"RemoteReadiness", http.MethodGet, "/_readiness", "10.0.0.1:9999", http.StatusOK}, + {"RemoteGetAdmin", http.MethodGet, "/admin/pprof/", "10.0.0.1:9999", http.StatusForbidden}, + {"RemotePostAPI", http.MethodPost, "/api/v1/object/ns/key", "10.0.0.1:9999", http.StatusForbidden}, + {"RemoteDeleteAPI", http.MethodDelete, "/api/v1/object/ns/key", "10.0.0.1:9999", http.StatusForbidden}, } next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}) @@ -40,7 +44,7 @@ func TestMiddlewareDefaultPolicy(t *testing.T) { for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - r := newRequest(test.Method, "/some/path") + r := newRequest(test.Method, test.Path) r.RemoteAddr = test.RemoteAddr w := httptest.NewRecorder() handler.ServeHTTP(w, r)