Add identity: whois resolver, checker, header mode, middleware; config for wake, peers, identity
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package identity_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/identity"
|
||||
)
|
||||
|
||||
// The middleware sits in front of the proxy: it names the route the same way the proxy does
|
||||
// (X-Crossbar-Route header, else first path segment) and refuses callers a route does not list.
|
||||
func TestMiddleware(t *testing.T) {
|
||||
peers := map[string][]string{"locked": {"talos"}, "open": nil}
|
||||
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) })
|
||||
h := identity.Middleware(identity.NewChecker(fakeResolver{"100.64.0.5": "talos", "100.64.0.9": "titan"}),
|
||||
func(route string) ([]string, bool) { p, ok := peers[route]; return p, ok }, inner)
|
||||
for _, tc := range []struct {
|
||||
name, path, hdr, addr string
|
||||
want int
|
||||
}{
|
||||
{"open route, anyone", "/open/v1/models", "", "203.0.113.1:5", 204},
|
||||
{"locked, right peer", "/locked/v1/models", "", "100.64.0.5:5", 204},
|
||||
{"locked, wrong peer", "/locked/v1/models", "", "100.64.0.9:5", 403},
|
||||
{"locked, not a peer", "/locked/v1/models", "", "203.0.113.1:5", 403},
|
||||
{"locked via header", "/v1/models", "locked", "100.64.0.9:5", 403},
|
||||
{"header wins over path", "/open/v1/models", "locked", "203.0.113.1:5", 403},
|
||||
{"unknown route passes through to the proxy's own 404", "/nope/v1/models", "", "203.0.113.1:5", 204},
|
||||
{"admin path is never gated here", "/_crossbar/hosts", "", "203.0.113.1:5", 204},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
|
||||
req.RemoteAddr = tc.addr
|
||||
if tc.hdr != "" {
|
||||
req.Header.Set("X-Crossbar-Route", tc.hdr)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != tc.want {
|
||||
t.Errorf("%s = %d, want %d (%s)", tc.path, rec.Code, tc.want, rec.Body.String())
|
||||
}
|
||||
if rec.Code == 403 && (!strings.HasPrefix(rec.Header().Get("Content-Type"), "application/json") || !strings.Contains(rec.Body.String(), `"forbidden route"`)) {
|
||||
t.Errorf("403 must be JSON {\"error\":\"forbidden route\"}: %q", rec.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderResolver is the test/smoke identity source: it trusts X-Crossbar-Peer. It exists so the
|
||||
// smoke run can exercise the gate without a tailnet; config must call it out as insecure.
|
||||
func TestHeaderResolver(t *testing.T) {
|
||||
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) })
|
||||
h := identity.Middleware(identity.NewHeaderChecker(), func(route string) ([]string, bool) { return []string{"talos"}, true }, inner)
|
||||
req := httptest.NewRequest(http.MethodGet, "/r/v1/models", nil)
|
||||
req.Header.Set("X-Crossbar-Peer", "talos")
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != 204 {
|
||||
t.Errorf("header peer talos: %d", rec.Code)
|
||||
}
|
||||
req.Header.Set("X-Crossbar-Peer", "titan")
|
||||
rec = httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != 403 {
|
||||
t.Errorf("header peer titan: %d, want 403", rec.Code)
|
||||
}
|
||||
req.Header.Del("X-Crossbar-Peer")
|
||||
rec = httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != 403 {
|
||||
t.Errorf("no header: %d, want 403", rec.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user