diff --git a/control/controlclient/map.go b/control/controlclient/map.go index fb6d93dd9..e7db7aae4 100644 --- a/control/controlclient/map.go +++ b/control/controlclient/map.go @@ -16,6 +16,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/netmap" "tailscale.com/types/opt" + "tailscale.com/types/views" "tailscale.com/wgengine/filter" ) @@ -40,6 +41,7 @@ type mapSession struct { lastDNSConfig *tailcfg.DNSConfig lastDERPMap *tailcfg.DERPMap lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile + lastPacketFilterRules views.Slice[tailcfg.FilterRule] lastParsedPacketFilter []filter.Match lastSSHPolicy *tailcfg.SSHPolicy collectServices bool @@ -96,6 +98,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo if pf := resp.PacketFilter; pf != nil { var err error + ms.lastPacketFilterRules = views.SliceOf(pf) ms.lastParsedPacketFilter, err = filter.MatchesFromFilterRules(pf) if err != nil { ms.logf("parsePacketFilter: %v", err) @@ -147,21 +150,22 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo } nm := &netmap.NetworkMap{ - NodeKey: ms.privateNodeKey.Public(), - PrivateKey: ms.privateNodeKey, - MachineKey: ms.machinePubKey, - Peers: resp.Peers, - UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile), - Domain: ms.lastDomain, - DomainAuditLogID: ms.lastDomainAuditLogID, - DNS: *ms.lastDNSConfig, - PacketFilter: ms.lastParsedPacketFilter, - SSHPolicy: ms.lastSSHPolicy, - CollectServices: ms.collectServices, - DERPMap: ms.lastDERPMap, - Debug: debug, - ControlHealth: ms.lastHealth, - TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled, + NodeKey: ms.privateNodeKey.Public(), + PrivateKey: ms.privateNodeKey, + MachineKey: ms.machinePubKey, + Peers: resp.Peers, + UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile), + Domain: ms.lastDomain, + DomainAuditLogID: ms.lastDomainAuditLogID, + DNS: *ms.lastDNSConfig, + PacketFilter: ms.lastParsedPacketFilter, + PacketFilterRules: ms.lastPacketFilterRules, + SSHPolicy: ms.lastSSHPolicy, + CollectServices: ms.collectServices, + DERPMap: ms.lastDERPMap, + Debug: debug, + ControlHealth: ms.lastHealth, + TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled, } ms.netMapBuilding = nm diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 32fe7f29f..4f3e72439 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -61,39 +61,41 @@ var handler = map[string]localAPIHandler{ // The other /localapi/v0/NAME handlers are exact matches and contain only NAME // without a trailing slash: - "bugreport": (*Handler).serveBugReport, - "check-ip-forwarding": (*Handler).serveCheckIPForwarding, - "check-prefs": (*Handler).serveCheckPrefs, - "component-debug-logging": (*Handler).serveComponentDebugLogging, - "debug": (*Handler).serveDebug, - "debug-derp-region": (*Handler).serveDebugDERPRegion, - "derpmap": (*Handler).serveDERPMap, - "dev-set-state-store": (*Handler).serveDevSetStateStore, - "dial": (*Handler).serveDial, - "file-targets": (*Handler).serveFileTargets, - "goroutines": (*Handler).serveGoroutines, - "id-token": (*Handler).serveIDToken, - "login-interactive": (*Handler).serveLoginInteractive, - "logout": (*Handler).serveLogout, - "metrics": (*Handler).serveMetrics, - "ping": (*Handler).servePing, - "prefs": (*Handler).servePrefs, - "pprof": (*Handler).servePprof, - "serve-config": (*Handler).serveServeConfig, - "set-dns": (*Handler).serveSetDNS, - "set-expiry-sooner": (*Handler).serveSetExpirySooner, - "start": (*Handler).serveStart, - "status": (*Handler).serveStatus, - "tka/init": (*Handler).serveTKAInit, - "tka/log": (*Handler).serveTKALog, - "tka/modify": (*Handler).serveTKAModify, - "tka/sign": (*Handler).serveTKASign, - "tka/status": (*Handler).serveTKAStatus, - "tka/disable": (*Handler).serveTKADisable, - "tka/force-local-disable": (*Handler).serveTKALocalDisable, - "upload-client-metrics": (*Handler).serveUploadClientMetrics, - "watch-ipn-bus": (*Handler).serveWatchIPNBus, - "whois": (*Handler).serveWhoIs, + "bugreport": (*Handler).serveBugReport, + "check-ip-forwarding": (*Handler).serveCheckIPForwarding, + "check-prefs": (*Handler).serveCheckPrefs, + "component-debug-logging": (*Handler).serveComponentDebugLogging, + "debug": (*Handler).serveDebug, + "debug-derp-region": (*Handler).serveDebugDERPRegion, + "debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches, + "debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules, + "derpmap": (*Handler).serveDERPMap, + "dev-set-state-store": (*Handler).serveDevSetStateStore, + "dial": (*Handler).serveDial, + "file-targets": (*Handler).serveFileTargets, + "goroutines": (*Handler).serveGoroutines, + "id-token": (*Handler).serveIDToken, + "login-interactive": (*Handler).serveLoginInteractive, + "logout": (*Handler).serveLogout, + "metrics": (*Handler).serveMetrics, + "ping": (*Handler).servePing, + "prefs": (*Handler).servePrefs, + "pprof": (*Handler).servePprof, + "serve-config": (*Handler).serveServeConfig, + "set-dns": (*Handler).serveSetDNS, + "set-expiry-sooner": (*Handler).serveSetExpirySooner, + "start": (*Handler).serveStart, + "status": (*Handler).serveStatus, + "tka/init": (*Handler).serveTKAInit, + "tka/log": (*Handler).serveTKALog, + "tka/modify": (*Handler).serveTKAModify, + "tka/sign": (*Handler).serveTKASign, + "tka/status": (*Handler).serveTKAStatus, + "tka/disable": (*Handler).serveTKADisable, + "tka/force-local-disable": (*Handler).serveTKALocalDisable, + "upload-client-metrics": (*Handler).serveUploadClientMetrics, + "watch-ipn-bus": (*Handler).serveWatchIPNBus, + "whois": (*Handler).serveWhoIs, } func randHex(n int) string { @@ -506,6 +508,40 @@ func (h *Handler) serveDevSetStateStore(w http.ResponseWriter, r *http.Request) io.WriteString(w, "done\n") } +func (h *Handler) serveDebugPacketFilterRules(w http.ResponseWriter, r *http.Request) { + if !h.PermitWrite { + http.Error(w, "debug access denied", http.StatusForbidden) + return + } + nm := h.b.NetMap() + if nm == nil { + http.Error(w, "no netmap", http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/json") + + enc := json.NewEncoder(w) + enc.SetIndent("", "\t") + enc.Encode(nm.PacketFilterRules) +} + +func (h *Handler) serveDebugPacketFilterMatches(w http.ResponseWriter, r *http.Request) { + if !h.PermitWrite { + http.Error(w, "debug access denied", http.StatusForbidden) + return + } + nm := h.b.NetMap() + if nm == nil { + http.Error(w, "no netmap", http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/json") + + enc := json.NewEncoder(w) + enc.SetIndent("", "\t") + enc.Encode(nm.PacketFilter) +} + func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "debug access denied", http.StatusForbidden) diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index 2d6a32154..711c9c657 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -16,6 +16,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/tka" "tailscale.com/types/key" + "tailscale.com/types/views" "tailscale.com/wgengine/filter" ) @@ -38,9 +39,10 @@ type NetworkMap struct { Peers []*tailcfg.Node // sorted by Node.ID DNS tailcfg.DNSConfig // TODO(maisem) : replace with View. - Hostinfo tailcfg.Hostinfo - PacketFilter []filter.Match - SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed + Hostinfo tailcfg.Hostinfo + PacketFilter []filter.Match + PacketFilterRules views.Slice[tailcfg.FilterRule] + SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed // CollectServices reports whether this node's Tailnet has // requested that info about services be included in HostInfo.