fix(nat): scrub dashboard-issued credentials from all accepted token channels

The initial selective strip covered only the Authorization header, but the
dashboard also accepts its JWT via the ?token= query parameter and the
nz-jwt cookie (jwt.go TokenLookup channels). A dashboard credential arriving
through those channels would still reach a NAT backend.

Apply the same classify-then-strip rule to all three channels at NAT
ingress: values verified as dashboard-issued are removed, foreign values
are preserved byte-for-byte. Adds a ServeNAT flow test exercising the live
classifier and a wiring test pinning the gate injection in main().
This commit is contained in:
xvkong233 authored and naiba committed 2026-10-01 11:17:45 +00:00
1 parent 40240ed02b
commit 3ad985f36e
12 files changed
+806 -82

No files matched your search

+13 -5
View File
@@ -169,11 +169,7 @@ func main() {
grpcHandler := rpc.ServeRPC()
httpHandler := controller.ServeWeb(frontendDist)
controller.InitUpgrader()
// The NAT ingress strips dashboard-issued Authorization values (panel JWT /
// panel PAT) before tunneling a request to the agent and forwards foreign
// credentials untouched. ServeWeb built the JWT parser the classifier
// reuses, so the gate is wired right after it.
rpc.SetNATDashboardCredentialGate(controller.IsDashboardCredential)
wireNATDashboardCredentialGate()
muxHandler := newHTTPandGRPCMux(httpHandler, grpcHandler)
muxServerHTTP := &http.Server{
@@ -233,6 +229,18 @@ func main() {
close(errChan)
}
// wireNATDashboardCredentialGate connects the NAT ingress to the real
// dashboard-credential classifiers. The NAT ingress strips dashboard-issued
// credentials (panel JWT / panel PAT) from every channel the panel's own
// TokenLookup accepts — the Authorization header, the ?token= query parameter
// and the nz-jwt cookie — before tunneling a request to the agent, and
// forwards foreign values untouched. ServeWeb built the JWT parser the
// classifiers reuse, so the gate is wired right after it and before any
// listener starts serving.
func wireNATDashboardCredentialGate() {
rpc.SetNATDashboardCredentialGate(controller.IsDashboardCredential, controller.IsDashboardCredentialValue)
}
func newHTTPandGRPCMux(httpHandler http.Handler, grpcHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
natConfig := singleton.NATShared.GetNATConfigByDomain(r.Host)