mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-29 02:51:29 +02:00
204 lines
6.3 KiB
Go
204 lines
6.3 KiB
Go
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
|
|
}
|