From 04198344827dfff962908cfb6726559927c77df3 Mon Sep 17 00:00:00 2001 From: pascal Date: Fri, 6 Feb 2026 15:42:13 +0100 Subject: [PATCH] add routed exposed services support in nmap --- .../network_map/controller/controller.go | 12 +- .../http/handlers/peers/peers_handler.go | 2 +- management/server/peer.go | 10 +- management/server/types/account.go | 132 ++++++++++++++---- 4 files changed, 117 insertions(+), 39 deletions(-) diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index 064a13573..01310a554 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -179,6 +179,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin peersCustomZone := account.GetPeersCustomZone(ctx, dnsDomain) resourcePolicies := account.GetResourcePoliciesMap() routers := account.GetResourceRoutersMap() + resources := account.GetResourcesMap() groupIDToUserIDs := account.GetActiveGroupUsers() exposedServices := account.GetExposedServicesMap() proxyPeers := account.GetProxyPeers() @@ -234,7 +235,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin if c.experimentalNetworkMap(accountID) { remotePeerNetworkMap = c.getPeerNetworkMapExp(ctx, p.AccountID, p.ID, approvedPeersMap, peersCustomZone, accountZones, c.accountManagerMetrics) } else { - remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, p.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs, exposedServices, proxyPeers) + remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, p.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, resources, c.accountManagerMetrics, groupIDToUserIDs, exposedServices, proxyPeers) } c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start)) @@ -330,6 +331,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe peersCustomZone := account.GetPeersCustomZone(ctx, dnsDomain) resourcePolicies := account.GetResourcePoliciesMap() routers := account.GetResourceRoutersMap() + resources := account.GetResourcesMap() groupIDToUserIDs := account.GetActiveGroupUsers() postureChecks, err := c.getPeerPostureChecks(account, peerId) @@ -355,7 +357,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe if c.experimentalNetworkMap(accountId) { remotePeerNetworkMap = c.getPeerNetworkMapExp(ctx, peer.AccountID, peer.ID, approvedPeersMap, peersCustomZone, accountZones, c.accountManagerMetrics) } else { - remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, peerId, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs, account.GetExposedServicesMap(), account.GetProxyPeers()) + remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, peerId, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, resources, c.accountManagerMetrics, groupIDToUserIDs, account.GetExposedServicesMap(), account.GetProxyPeers()) } proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] @@ -471,7 +473,8 @@ func (c *Controller) GetValidatedPeerWithMap(ctx context.Context, isRequiresAppr } else { resourcePolicies := account.GetResourcePoliciesMap() routers := account.GetResourceRoutersMap() - networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers()) + resources := account.GetResourcesMap() + networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, resources, c.accountManagerMetrics, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers()) } proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] @@ -844,7 +847,8 @@ func (c *Controller) GetNetworkMap(ctx context.Context, peerID string) (*types.N } else { resourcePolicies := account.GetResourcePoliciesMap() routers := account.GetResourceRoutersMap() - networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, validatedPeers, resourcePolicies, routers, nil, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers()) + resources := account.GetResourcesMap() + networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, validatedPeers, resourcePolicies, routers, resources, nil, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers()) } proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] diff --git a/management/server/http/handlers/peers/peers_handler.go b/management/server/http/handlers/peers/peers_handler.go index 976ef1f64..3abb1b6f2 100644 --- a/management/server/http/handlers/peers/peers_handler.go +++ b/management/server/http/handlers/peers/peers_handler.go @@ -395,7 +395,7 @@ func (h *Handler) GetAccessiblePeers(w http.ResponseWriter, r *http.Request) { dnsDomain := h.networkMapController.GetDNSDomain(account.Settings) - netMap := account.GetPeerNetworkMap(r.Context(), peerID, dns.CustomZone{}, nil, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers()) + netMap := account.GetPeerNetworkMap(r.Context(), peerID, dns.CustomZone{}, nil, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), account.GetResourcesMap(), nil, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers()) util.WriteJSONObject(r.Context(), w, toAccessiblePeers(netMap, dnsDomain)) } diff --git a/management/server/peer.go b/management/server/peer.go index e056f871a..71d3fbb0e 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -574,9 +574,9 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe var setupKeyID string var setupKeyName string - var ephemeral bool var groupsToAdd []string var allowExtraDNSLabels bool + ephemeral := peer.Ephemeral switch { case addedByUser: user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID) @@ -732,9 +732,11 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe } } - err = transaction.AddPeerToAllGroup(ctx, accountID, newPeer.ID) - if err != nil { - return fmt.Errorf("failed adding peer to All group: %w", err) + if !peer.ProxyEmbedded { + err = transaction.AddPeerToAllGroup(ctx, accountID, newPeer.ID) + if err != nil { + return fmt.Errorf("failed adding peer to All group: %w", err) + } } switch { diff --git a/management/server/types/account.go b/management/server/types/account.go index 5ad7bdab9..53d9ad51f 100644 --- a/management/server/types/account.go +++ b/management/server/types/account.go @@ -283,9 +283,10 @@ func (a *Account) GetPeerNetworkMap( validatedPeersMap map[string]struct{}, resourcePolicies map[string][]*Policy, routers map[string]map[string]*routerTypes.NetworkRouter, + resourcesMap map[string]*resourceTypes.NetworkResource, metrics *telemetry.AccountManagerMetrics, groupIDToUserIDs map[string][]string, - exposedServices map[string][]*reverseproxy.ReverseProxy, // routerPeer -> list of exposed services + exposedServices map[string][]*reverseproxy.ReverseProxy, proxyPeers []*nbpeer.Peer, ) *NetworkMap { start := time.Now() @@ -309,7 +310,7 @@ func (a *Account) GetPeerNetworkMap( var authorizedUsers map[string]map[string]struct{} var enableSSH bool if peer.ProxyEmbedded { - aclPeers, firewallRules = a.GetProxyConnectionResources(exposedServices) + aclPeers, firewallRules = a.GetProxyConnectionResources(ctx, exposedServices) } else { aclPeers, firewallRules, authorizedUsers, enableSSH = a.GetPeerConnectionResources(ctx, peer, validatedPeersMap, groupIDToUserIDs) proxyAclPeers, proxyFirewallRules := a.GetPeerProxyResources(exposedServices[peerID], proxyPeers) @@ -328,14 +329,34 @@ func (a *Account) GetPeerNetworkMap( peersToConnect = append(peersToConnect, p) } - routesUpdate := a.GetRoutesToSync(ctx, peerID, peersToConnect, peerGroups) - routesFirewallRules := a.GetPeerRoutesFirewallRules(ctx, peerID, validatedPeersMap) - isRouter, networkResourcesRoutes, sourcePeers := a.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, routers) - var networkResourcesFirewallRules []*RouteFirewallRule - if isRouter { - networkResourcesFirewallRules = a.GetPeerNetworkResourceFirewallRules(ctx, peer, validatedPeersMap, networkResourcesRoutes, resourcePolicies) + var routes, networksRoutes []*route.Route + var isRouter bool + var sourcePeers map[string]struct{} + var routesFirewallRules []*RouteFirewallRule + if peer.ProxyEmbedded { + routes, routesFirewallRules, aclPeers = a.GetPeerProxyRoutes(ctx, peer, exposedServices, resourcesMap, routers, proxyPeers) + for _, p := range aclPeers { + expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration) + if a.Settings.PeerLoginExpirationEnabled && expired { + expiredPeers = append(expiredPeers, p) + continue + } + peersToConnect = append(peersToConnect, p) + } + } else { + oldRoutes := a.GetRoutesToSync(ctx, peerID, peersToConnect, peerGroups) + oldRoutesFirewallRules := a.GetPeerRoutesFirewallRules(ctx, peerID, validatedPeersMap) + proxyRoutes, proxyRoutesFirewallRules, _ := a.GetPeerProxyRoutes(ctx, peer, exposedServices, resourcesMap, routers, proxyPeers) + isRouter, networksRoutes, sourcePeers = a.GetNetworkResourcesRoutesToSync(ctx, peerID, resourcePolicies, routers) + var networksFirewallRules []*RouteFirewallRule + if isRouter { + networksFirewallRules = a.GetPeerNetworkResourceFirewallRules(ctx, peer, validatedPeersMap, networksRoutes, resourcePolicies) + } + routes = slices.Concat(networksRoutes, oldRoutes, proxyRoutes) + routesFirewallRules = slices.Concat(networksFirewallRules, oldRoutesFirewallRules, proxyRoutesFirewallRules) } - peersToConnectIncludingRouters := a.addNetworksRoutingPeers(networkResourcesRoutes, peer, peersToConnect, expiredPeers, isRouter, sourcePeers) + + peersToConnectIncludingRouters := a.addNetworksRoutingPeers(routes, peer, peersToConnect, expiredPeers, isRouter, sourcePeers) dnsManagementStatus := a.getPeerDNSManagementStatus(peerID) dnsUpdate := nbdns.Config{ @@ -363,31 +384,31 @@ func (a *Account) GetPeerNetworkMap( nm := &NetworkMap{ Peers: peersToConnectIncludingRouters, Network: a.Network.Copy(), - Routes: slices.Concat(networkResourcesRoutes, routesUpdate), + Routes: routes, DNSConfig: dnsUpdate, OfflinePeers: expiredPeers, FirewallRules: firewallRules, - RoutesFirewallRules: slices.Concat(networkResourcesFirewallRules, routesFirewallRules), + RoutesFirewallRules: routesFirewallRules, AuthorizedUsers: authorizedUsers, EnableSSH: enableSSH, } if metrics != nil { - objectCount := int64(len(peersToConnectIncludingRouters) + len(expiredPeers) + len(routesUpdate) + len(networkResourcesRoutes) + len(firewallRules) + +len(networkResourcesFirewallRules) + len(routesFirewallRules)) + objectCount := int64(len(peersToConnectIncludingRouters) + len(expiredPeers) + len(routes) + len(firewallRules) + +len(routesFirewallRules)) metrics.CountNetworkMapObjects(objectCount) metrics.CountGetPeerNetworkMapDuration(time.Since(start)) if objectCount > 5000 { log.WithContext(ctx).Tracef("account: %s has a total resource count of %d objects, "+ - "peers to connect: %d, expired peers: %d, routes: %d, firewall rules: %d, network resources routes: %d, network resources firewall rules: %d, routes firewall rules: %d", - a.Id, objectCount, len(peersToConnectIncludingRouters), len(expiredPeers), len(routesUpdate), len(firewallRules), len(networkResourcesRoutes), len(networkResourcesFirewallRules), len(routesFirewallRules)) + "peers to connect: %d, expired peers: %d, routes: %d, firewall rules: %d, routes firewall rules: %d", + a.Id, objectCount, len(peersToConnectIncludingRouters), len(expiredPeers), len(routes), len(firewallRules), len(routesFirewallRules)) } } return nm } -func (a *Account) GetProxyConnectionResources(exposedServices map[string][]*reverseproxy.ReverseProxy) ([]*nbpeer.Peer, []*FirewallRule) { +func (a *Account) GetProxyConnectionResources(ctx context.Context, exposedServices map[string][]*reverseproxy.ReverseProxy) ([]*nbpeer.Peer, []*FirewallRule) { var aclPeers []*nbpeer.Peer var firewallRules []*FirewallRule @@ -400,8 +421,7 @@ func (a *Account) GetProxyConnectionResources(exposedServices map[string][]*reve if !target.Enabled { continue } - switch target.TargetType { - case reverseproxy.TargetTypePeer: + if target.TargetType == reverseproxy.TargetTypePeer { tpeer := a.GetPeer(target.TargetId) if tpeer == nil { continue @@ -415,8 +435,6 @@ func (a *Account) GetProxyConnectionResources(exposedServices map[string][]*reve Protocol: string(PolicyRuleProtocolTCP), PortRange: RulePortRange{Start: uint16(target.Port), End: uint16(target.Port)}, }) - case reverseproxy.TargetTypeResource: - // TODO: handle resource type targets } } } @@ -438,17 +456,6 @@ func (a *Account) GetPeerProxyResources(services []*reverseproxy.ReverseProxy, p continue } aclPeers = proxyPeers - for _, peer := range aclPeers { - firewallRules = append(firewallRules, &FirewallRule{ - PolicyID: "proxy-" + service.ID, - PeerIP: peer.IP.String(), - Direction: FirewallRuleDirectionIN, - Action: "allow", - Protocol: string(PolicyRuleProtocolTCP), - PortRange: RulePortRange{Start: uint16(target.Port), End: uint16(target.Port)}, - }) - } - // TODO: handle routes } } @@ -1895,6 +1902,71 @@ func (a *Account) GetExposedServicesMap() map[string][]*reverseproxy.ReverseProx return services } +func (a *Account) GetPeerProxyRoutes(ctx context.Context, peer *nbpeer.Peer, proxies map[string][]*reverseproxy.ReverseProxy, resourcesMap map[string]*resourceTypes.NetworkResource, routers map[string]map[string]*routerTypes.NetworkRouter, proxyPeers []*nbpeer.Peer) ([]*route.Route, []*RouteFirewallRule, []*nbpeer.Peer) { + sourceRanges := make([]string, 0, len(proxyPeers)) + for _, proxyPeer := range proxyPeers { + sourceRanges = append(sourceRanges, fmt.Sprintf(AllowedIPsFormat, proxyPeer.IP)) + } + peers := make(map[string]*nbpeer.Peer, len(resourcesMap)) + + var routes []*route.Route + var firewallRules []*RouteFirewallRule + for _, proxyPerResource := range proxies { + for _, proxy := range proxyPerResource { + for _, target := range proxy.Targets { + if target.TargetType == reverseproxy.TargetTypeResource { + resource, ok := resourcesMap[target.TargetId] + if !ok { + log.WithContext(ctx).Warnf("proxy target %s not found in resources map", target.TargetId) + continue + } + networkRouters, ok := routers[resource.NetworkID] + if !ok { + log.WithContext(ctx).Warnf("proxy target %s not found in routers map", target.TargetId) + continue + } + for peerID, router := range networkRouters { + routePeer := a.GetPeer(peerID) + route := resource.ToRoute(routePeer, router) + routes = append(routes, route) + rule := RouteFirewallRule{ + PolicyID: fmt.Sprintf("proxy-%s-%s", proxy.ID, route.ID), + RouteID: route.ID, + SourceRanges: sourceRanges, + Action: string(PolicyTrafficActionAccept), + Destination: route.Network.String(), + Protocol: string(PolicyRuleProtocolTCP), + Domains: route.Domains, + IsDynamic: route.IsDynamic(), + PortRange: RulePortRange{ + Start: uint16(target.Port), + End: uint16(target.Port), + }, + } + firewallRules = append(firewallRules, &rule) + peers[peerID] = routePeer + } + } + } + } + } + + resultPeers := make([]*nbpeer.Peer, 0, len(peers)) + for _, peer := range peers { + resultPeers = append(resultPeers, peer) + } + + return routes, firewallRules, resultPeers +} + +func (a *Account) GetResourcesMap() map[string]*resourceTypes.NetworkResource { + resourcesMap := make(map[string]*resourceTypes.NetworkResource, len(a.NetworkResources)) + for _, resource := range a.NetworkResources { + resourcesMap[resource.ID] = resource + } + return resourcesMap +} + // expandPortsAndRanges expands Ports and PortRanges of a rule into individual firewall rules func expandPortsAndRanges(base FirewallRule, rule *PolicyRule, peer *nbpeer.Peer) []*FirewallRule { features := peerSupportedFirewallFeatures(peer.Meta.WtVersion)