75 lines
3.0 KiB
Go
75 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|