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,71 @@
|
||||
// Package identity gates a route on the set of tailnet peers allowed to use it.
|
||||
// The middleware sits in front of the proxy: it names the route the same way the
|
||||
// proxy does and refuses with 403 any caller a route does not allow.
|
||||
package identity
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// routeHeader is how a caller names the route, the same header the proxy
|
||||
// reads.
|
||||
routeHeader = "X-Crossbar-Route"
|
||||
// peerHeader carries the node name in header mode.
|
||||
peerHeader = "X-Crossbar-Peer"
|
||||
// adminPrefix is never gated here; the proxy's own handlers own it.
|
||||
adminPrefix = "/_crossbar/"
|
||||
)
|
||||
|
||||
// Middleware wraps next with the peer gate. peersFor names the allow list for a
|
||||
// route and reports whether it knows the route; an unknown route, like a path
|
||||
// under adminPrefix, passes straight through.
|
||||
func Middleware(c *Checker, peersFor func(route string) ([]string, bool), next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, adminPrefix) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
route := r.Header.Get(routeHeader)
|
||||
if route == "" {
|
||||
route = firstSegment(r.URL.Path)
|
||||
}
|
||||
peers, known := peersFor(route)
|
||||
if !known {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
ctx := WithHeaderPeer(r.Context(), r.Header.Get(peerHeader))
|
||||
if err := c.Allow(ctx, peers, r.RemoteAddr); err != nil {
|
||||
writeForbidden(w)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// writeForbidden answers the JSON 403 the tests and callers expect.
|
||||
func writeForbidden(w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte(`{"error":"forbidden route"}`))
|
||||
}
|
||||
|
||||
// firstSegment takes the first path segment as the route, "/a/v1/x" -> "a".
|
||||
func firstSegment(path string) string {
|
||||
if path == "" || path[0] != '/' {
|
||||
return ""
|
||||
}
|
||||
after := path[1:]
|
||||
if slash := strings.IndexByte(after, '/'); slash >= 0 {
|
||||
if after[:slash] == "" {
|
||||
return ""
|
||||
}
|
||||
return after[:slash]
|
||||
}
|
||||
if after == "" {
|
||||
return ""
|
||||
}
|
||||
return after
|
||||
}
|
||||
Reference in New Issue
Block a user