mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-25 00:51:28 +02:00
inject proxy policies on nmdata path
This commit is contained in:
@@ -17,6 +17,8 @@ type sshRequirements struct {
|
||||
// exactly, operating on nmdata twins throughout — no Account reference and no
|
||||
// twin↔real conversion, since the produced components hold twins.
|
||||
func (nmd *NetworkMapData) GetPeerNetworkMapComponents(peerID string, peersCustomZone nmdata.CustomZone) *types.NetworkMapComponents {
|
||||
nmd.InjectProxyPolicies()
|
||||
|
||||
forceRoutingPeerDNS := nmd.forcesRoutingPeerDNSResolution(peerID)
|
||||
|
||||
peer := nmd.Peers[peerID]
|
||||
|
||||
@@ -48,8 +48,16 @@ type NetworkMapData struct { //nolint:revive // established name across the code
|
||||
AppliedZoneCandidates []AppliedZoneCandidate
|
||||
PrivateServiceCandidates []PrivateServiceCandidate
|
||||
|
||||
// Services are the account's reverse-proxy services, persisted ones and
|
||||
// the in-memory ones synthesised from agent-network state. They are the
|
||||
// source of the proxy ACLs injectProxyPolicies synthesises, which no
|
||||
// builder can load because they are never written to the database.
|
||||
Services []*nmdata.Service
|
||||
|
||||
peerGroupsOnce sync.Once
|
||||
peerGroupsIdx map[string]map[string]struct{}
|
||||
|
||||
proxyPoliciesOnce sync.Once
|
||||
}
|
||||
|
||||
// AppliedZoneCandidate is an account-level custom DNS zone reduced to the
|
||||
|
||||
@@ -36,6 +36,7 @@ type Peer struct {
|
||||
// ProxyMeta is the slim twin of peer.ProxyMeta.
|
||||
type ProxyMeta struct {
|
||||
Embedded bool
|
||||
Cluster string
|
||||
}
|
||||
|
||||
// PeerSystemMeta is the slim twin of peer.PeerSystemMeta.
|
||||
|
||||
25
shared/management/networkmap/nmdata/service.go
Normal file
25
shared/management/networkmap/nmdata/service.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package nmdata
|
||||
|
||||
// Service is the slim twin of the reverse-proxy service.Service. It carries
|
||||
// only the state proxy-policy injection reads: the persisted reverse-proxy
|
||||
// services and the in-memory ones synthesised from agent-network state, which
|
||||
// are never written to the database.
|
||||
type Service struct {
|
||||
ID string
|
||||
Enabled bool
|
||||
Private bool
|
||||
Mode string
|
||||
ProxyCluster string
|
||||
AccessGroups []string
|
||||
Targets []*ServiceTarget
|
||||
}
|
||||
|
||||
// ServiceTarget is the slim twin of service.Target.
|
||||
type ServiceTarget struct {
|
||||
Enabled bool
|
||||
Path string
|
||||
Port uint16
|
||||
Protocol string
|
||||
TargetID string
|
||||
TargetType string
|
||||
}
|
||||
203
shared/management/networkmap/proxypolicies.go
Normal file
203
shared/management/networkmap/proxypolicies.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package networkmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
"github.com/netbirdio/netbird/shared/management/types"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceModeUDP = "udp"
|
||||
|
||||
privateServicePortHTTP = 80
|
||||
privateServicePortHTTPS = 443
|
||||
)
|
||||
|
||||
// InjectProxyPolicies synthesises the in-memory ACLs that carry reverse-proxy
|
||||
// traffic and appends them to the twin's policies. They are never persisted,
|
||||
// so no builder can load them: a proxy-access policy lets a cluster's proxy
|
||||
// peers reach each enabled target of a service, and a private-access policy
|
||||
// lets a private service's AccessGroups reach those proxy peers on HTTP(S).
|
||||
//
|
||||
// GetPeerNetworkMapComponents calls it, so every caller of the twin gets the
|
||||
// same policy set no matter which builder produced it. It runs at most once
|
||||
// per twin, and is safe to call again to force the synthesis early.
|
||||
func (nmd *NetworkMapData) InjectProxyPolicies() {
|
||||
nmd.proxyPoliciesOnce.Do(nmd.injectProxyPolicies)
|
||||
}
|
||||
|
||||
func (nmd *NetworkMapData) injectProxyPolicies() {
|
||||
if len(nmd.Services) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
proxyPeersByCluster := nmd.proxyPeersByCluster()
|
||||
if len(proxyPeersByCluster) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, svc := range nmd.Services {
|
||||
if svc == nil || !svc.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
proxyPeers := proxyPeersByCluster[svc.ProxyCluster]
|
||||
for _, target := range svc.Targets {
|
||||
if target == nil || !target.Enabled {
|
||||
continue
|
||||
}
|
||||
port, ok := resolveTargetPort(target)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, proxyPeer := range proxyPeers {
|
||||
nmd.addInjectedPolicy(proxyAccessPolicy(svc, target, proxyPeer, port))
|
||||
}
|
||||
}
|
||||
|
||||
nmd.injectPrivateServicePolicies(svc, proxyPeers)
|
||||
}
|
||||
}
|
||||
|
||||
// injectPrivateServicePolicies synthesises AccessGroups → cluster proxy peers on TCP 80/443.
|
||||
func (nmd *NetworkMapData) injectPrivateServicePolicies(svc *nmdata.Service, proxyPeers []*nmdata.Peer) {
|
||||
if !svc.Private || len(svc.AccessGroups) == 0 || len(proxyPeers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// A service's AccessGroups can name groups that no longer exist — persisted
|
||||
// services and the agent-network synthesiser both carry the ids verbatim from
|
||||
// their own state. An unresolvable source authorises nothing, so drop it here
|
||||
// rather than let the network-map assembly resolve it to a nil group.
|
||||
sources := nmd.existingGroupIDs(svc.AccessGroups)
|
||||
if len(sources) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, proxyPeer := range proxyPeers {
|
||||
nmd.addInjectedPolicy(privateAccessPolicy(svc, proxyPeer, sources))
|
||||
}
|
||||
}
|
||||
|
||||
// addInjectedPolicy appends the policy to the twin's policy set, and to the
|
||||
// policies of the network resource it targets — mirroring the account path,
|
||||
// where the resource-policy map was built after injection.
|
||||
func (nmd *NetworkMapData) addInjectedPolicy(policy *nmdata.Policy) {
|
||||
nmd.Policies = append(nmd.Policies, policy)
|
||||
|
||||
resourceID := policy.Rules[0].DestinationResource.ID
|
||||
if resourceID == "" {
|
||||
return
|
||||
}
|
||||
for _, resource := range nmd.NetworkResources {
|
||||
if resource == nil || !resource.Enabled || resource.ID != resourceID {
|
||||
continue
|
||||
}
|
||||
if nmd.ResourcePolicies == nil {
|
||||
nmd.ResourcePolicies = make(map[string][]*nmdata.Policy)
|
||||
}
|
||||
nmd.ResourcePolicies[resourceID] = append(nmd.ResourcePolicies[resourceID], policy)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func proxyAccessPolicy(svc *nmdata.Service, target *nmdata.ServiceTarget, proxyPeer *nmdata.Peer, port uint16) *nmdata.Policy {
|
||||
policyID := fmt.Sprintf("proxy-access-%s-%s-%s", svc.ID, proxyPeer.ID, target.Path)
|
||||
|
||||
protocol := types.PolicyRuleProtocolTCP
|
||||
if svc.Mode == serviceModeUDP {
|
||||
protocol = types.PolicyRuleProtocolUDP
|
||||
}
|
||||
|
||||
return &nmdata.Policy{
|
||||
ID: policyID,
|
||||
Enabled: true,
|
||||
Rules: []*nmdata.PolicyRule{
|
||||
{
|
||||
ID: policyID,
|
||||
PolicyID: policyID,
|
||||
Enabled: true,
|
||||
SourceResource: nmdata.Resource{ID: proxyPeer.ID, Type: string(types.ResourceTypePeer)},
|
||||
DestinationResource: nmdata.Resource{ID: target.TargetID, Type: target.TargetType},
|
||||
Bidirectional: false,
|
||||
Protocol: string(protocol),
|
||||
Action: string(types.PolicyTrafficActionAccept),
|
||||
PortRanges: []nmdata.RulePortRange{{Start: port, End: port}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func privateAccessPolicy(svc *nmdata.Service, proxyPeer *nmdata.Peer, accessGroups []string) *nmdata.Policy {
|
||||
policyID := fmt.Sprintf("private-access-%s-%s", svc.ID, proxyPeer.ID)
|
||||
|
||||
return &nmdata.Policy{
|
||||
ID: policyID,
|
||||
Enabled: true,
|
||||
Rules: []*nmdata.PolicyRule{
|
||||
{
|
||||
ID: policyID,
|
||||
PolicyID: policyID,
|
||||
Enabled: true,
|
||||
Sources: slices.Clone(accessGroups),
|
||||
DestinationResource: nmdata.Resource{ID: proxyPeer.ID, Type: string(types.ResourceTypePeer)},
|
||||
Bidirectional: false,
|
||||
Protocol: string(types.PolicyRuleProtocolTCP),
|
||||
Action: string(types.PolicyTrafficActionAccept),
|
||||
PortRanges: []nmdata.RulePortRange{
|
||||
{Start: privateServicePortHTTP, End: privateServicePortHTTP},
|
||||
{Start: privateServicePortHTTPS, End: privateServicePortHTTPS},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resolveTargetPort(target *nmdata.ServiceTarget) (uint16, bool) {
|
||||
if target.Port != 0 {
|
||||
return target.Port, true
|
||||
}
|
||||
|
||||
switch target.Protocol {
|
||||
case "https", "tls":
|
||||
return privateServicePortHTTPS, true
|
||||
case "http":
|
||||
return privateServicePortHTTP, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// proxyPeersByCluster groups the account's embedded proxy peers by the cluster
|
||||
// they serve. Sorted by peer ID so the synthesised policy order is stable.
|
||||
func (nmd *NetworkMapData) proxyPeersByCluster() map[string][]*nmdata.Peer {
|
||||
var proxyPeers map[string][]*nmdata.Peer
|
||||
for _, peer := range nmd.Peers {
|
||||
if peer == nil || !peer.ProxyMeta.Embedded {
|
||||
continue
|
||||
}
|
||||
if proxyPeers == nil {
|
||||
proxyPeers = make(map[string][]*nmdata.Peer)
|
||||
}
|
||||
proxyPeers[peer.ProxyMeta.Cluster] = append(proxyPeers[peer.ProxyMeta.Cluster], peer)
|
||||
}
|
||||
for _, peers := range proxyPeers {
|
||||
slices.SortFunc(peers, func(a, b *nmdata.Peer) int { return strings.Compare(a.ID, b.ID) })
|
||||
}
|
||||
return proxyPeers
|
||||
}
|
||||
|
||||
// existingGroupIDs returns the subset of groupIDs that resolve to a group,
|
||||
// preserving the input order.
|
||||
func (nmd *NetworkMapData) existingGroupIDs(groupIDs []string) []string {
|
||||
out := make([]string, 0, len(groupIDs))
|
||||
for _, groupID := range groupIDs {
|
||||
if _, ok := nmd.Groups[groupID]; ok {
|
||||
out = append(out, groupID)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user