Merge branch 'main' into agent-network/provider-credential-check

This commit is contained in:
Maycon Santos
2026-08-27 13:36:42 +02:00
committed by GitHub
353 changed files with 25422 additions and 3859 deletions

View File

@@ -126,7 +126,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := mgmt.NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, mgmt.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peersManger), config)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, mgmt.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peersManger), config, nil)
accountManager, err := mgmt.BuildManager(context.Background(), config, store, networkMapController, jobManager, nil, "", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false, cacheStore)
if err != nil {
t.Fatal(err)

View File

@@ -0,0 +1,23 @@
package integration_reference
import (
"fmt"
"strings"
)
// IntegrationReference holds the reference to a particular integration
type IntegrationReference struct {
ID int
IntegrationType string
}
func (ir IntegrationReference) String() string {
return fmt.Sprintf("%s:%d", ir.IntegrationType, ir.ID)
}
func (ir IntegrationReference) CacheKey(path ...string) string {
if len(path) == 0 {
return ir.String()
}
return fmt.Sprintf("%s:%s", ir.String(), strings.Join(path, ":"))
}

View File

@@ -1,18 +1,19 @@
package networkmap
import (
"context"
"encoding/base64"
"fmt"
"net"
"net/netip"
"slices"
"strconv"
"time"
log "github.com/sirupsen/logrus"
nbdns "github.com/netbirdio/netbird/dns"
nbroute "github.com/netbirdio/netbird/route"
"github.com/netbirdio/netbird/shared/management/domain"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
"github.com/netbirdio/netbird/shared/management/types"
)
@@ -24,7 +25,7 @@ import (
// ID scheme on the client side:
//
// Peers base64(wg_pub_key) // stable across snapshots
func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents, error) {
func DecodeEnvelope(ctx context.Context, env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents, error) {
full := env.GetFull()
if full == nil {
return nil, fmt.Errorf("envelope has no Full payload")
@@ -35,28 +36,28 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
Network: decodeAccountNetwork(full.Network),
AccountSettings: decodeAccountSettings(full.AccountSettings),
CustomZoneDomain: full.CustomZoneDomain,
Peers: make(map[string]*types.ComponentPeer, len(full.Peers)),
Groups: make(map[string]*types.ComponentGroup, len(full.Groups)),
Policies: make([]*types.Policy, 0, len(full.Policies)),
Routes: make([]*nbroute.Route, 0, len(full.Routes)),
NameServerGroups: make([]*nbdns.NameServerGroup, 0, len(full.NameserverGroups)),
Peers: make(map[string]*nmdata.Peer, len(full.Peers)),
Groups: make(map[string]*nmdata.Group, len(full.Groups)),
Policies: make([]*nmdata.Policy, 0, len(full.Policies)),
Routes: make([]*nmdata.Route, 0, len(full.Routes)),
NameServerGroups: make([]*nmdata.NameServerGroup, 0, len(full.NameserverGroups)),
AllDNSRecords: decodeSimpleRecords(full.AllDnsRecords),
AccountZones: decodeCustomZones(full.AccountZones),
ResourcePoliciesMap: make(map[string][]*types.Policy),
RoutersMap: make(map[string]map[string]*types.ComponentRouter),
NetworkResources: make([]*types.ComponentResource, 0, len(full.NetworkResources)),
RouterPeers: make(map[string]*types.ComponentPeer),
ResourcePoliciesMap: make(map[string][]*nmdata.Policy),
RoutersMap: make(map[string]map[string]*nmdata.NetworkRouter),
NetworkResources: make([]*nmdata.NetworkResource, 0, len(full.NetworkResources)),
RouterPeers: make(map[string]*nmdata.Peer),
AllowedUserIDs: stringSliceToSet(full.AllowedUserIds),
PostureFailedPeers: make(map[string]map[string]struct{}, len(full.PostureFailedPeers)),
GroupIDToUserIDs: make(map[string][]string, len(full.GroupIdToUserIds)),
}
if full.DnsSettings != nil {
c.DNSSettings = &types.DNSSettings{
c.DNSSettings = &nmdata.DNSSettings{
DisabledManagementGroups: full.DnsSettings.DisabledManagementGroupIds,
}
} else {
c.DNSSettings = &types.DNSSettings{}
c.DNSSettings = &nmdata.DNSSettings{}
}
// Phase 1: peers. The envelope's peers slice is index-addressed on the
@@ -98,20 +99,36 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
log.WithField("peer idx", idx).Error("unrecognized peer idx during decoding")
}
}
group := &types.ComponentGroup{
ID: groupID,
PublicID: gc.Id,
Peers: peerIDs,
fromCompactResources := func() []nmdata.Resource {
var toret []nmdata.Resource
for _, r := range gc.Resources {
res := resourceFromProto(r, peerIDByIndex)
if res == (nmdata.Resource{}) {
log.WithContext(ctx).Warnf("skipping invalid resource in group compact: %s", r.String())
continue
}
toret = append(toret, res)
}
return toret
}
group := &nmdata.Group{
PublicID: gc.Id,
Peers: peerIDs,
Resources: fromCompactResources(),
}
if gc.IsAll {
group.Name = types.GroupAllName
group.Name = nmdata.GroupAllName
}
c.Groups[groupID] = group
}
// Phase 3: policies (PolicyCompact = one rule per entry; current data
// model is 1 rule per policy).
policyByID := make(map[string]*types.Policy, len(full.Policies))
policyByID := make(map[string]*nmdata.Policy, len(full.Policies))
for i, pc := range full.Policies {
if pc == nil {
return nil, fmt.Errorf("invalid envelope: policies[%d] is nil", i)
@@ -148,7 +165,7 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
// Phase 7: routers_map (outer key = network seq id, inner key = peer-id
// reconstructed from peer_index). Synthesized network id is "net_<seq>".
for networkID, list := range full.RoutersMap {
inner := make(map[string]*types.ComponentRouter, len(list.Entries))
inner := make(map[string]*nmdata.NetworkRouter, len(list.Entries))
for _, entry := range list.Entries {
if !entry.PeerIndexSet {
continue
@@ -158,10 +175,8 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
continue
}
peerID := peerIDByIndex[entry.PeerIndex]
inner[peerID] = &types.ComponentRouter{
NetworkID: networkID,
inner[peerID] = &nmdata.NetworkRouter{
PublicID: entry.Id,
Peer: peerID,
PeerGroups: entry.PeerGroupIds,
Masquerade: entry.Masquerade,
Metric: int(entry.Metric),
@@ -180,7 +195,7 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
if len(ids.Ids) == 0 {
continue
}
policies := make([]*types.Policy, 0, len(ids.Ids))
policies := make([]*nmdata.Policy, 0, len(ids.Ids))
for _, id := range ids.Ids {
if p, ok := policyByID[id]; ok {
policies = append(policies, p)
@@ -193,6 +208,15 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
}
}
// Phase 8: rebuild resource_policies_map
for _, r := range c.NetworkResources {
policies := policiesForNetworkResource(r.ID, c.Policies, c.Groups)
if len(policies) == 0 {
continue
}
c.ResourcePoliciesMap[r.ID] = policies
}
// Phase 9: group_id_to_user_ids — wire keys are seq ids, synth to strings.
for groupId, list := range full.GroupIdToUserIds {
c.GroupIDToUserIDs[groupId] = append([]string(nil), list.UserIds...)
@@ -228,17 +252,54 @@ func DecodeEnvelope(env *proto.NetworkMapEnvelope) (*types.NetworkMapComponents,
return c, nil
}
func networkResourceGroups(resourceId string, groups map[string]*nmdata.Group) []string {
var toret []string
for _, group := range groups {
for _, resource := range group.Resources {
if resource.ID == resourceId {
toret = append(toret, group.PublicID)
}
}
}
return toret
}
func policiesForNetworkResource(resourceId string, allPolicies []*nmdata.Policy, groups map[string]*nmdata.Group) []*nmdata.Policy {
var toret []*nmdata.Policy
networkResourceGroups := networkResourceGroups(resourceId, groups)
for _, p := range allPolicies {
if p == nil || !p.Enabled || len(p.Rules) == 0 {
continue
}
// there's always only one rule in each policy
if p.Rules[0].DestinationResource.ID == resourceId {
toret = append(toret, p)
continue
}
for _, groupId := range networkResourceGroups {
if slices.Contains(p.Rules[0].Destinations, groupId) {
toret = append(toret, p)
break
}
}
}
return toret
}
// decodeAccountNetwork never returns nil — Calculate() dereferences
// c.Network unconditionally, and servers that predate the fix omit the field
// entirely from the empty-components envelope.
func decodeAccountNetwork(an *proto.AccountNetwork) *types.Network {
n := &types.Network{}
func decodeAccountNetwork(an *proto.AccountNetwork) *nmdata.Network {
n := &nmdata.Network{}
if an == nil {
return n
}
n.Identifier = an.Identifier
n.Dns = an.Dns
n.Serial = an.Serial
n.Serial = int64(an.Serial)
if an.NetCidr != "" {
if _, ipnet, err := net.ParseCIDR(an.NetCidr); err == nil && ipnet != nil {
n.Net = *ipnet
@@ -252,33 +313,51 @@ func decodeAccountNetwork(an *proto.AccountNetwork) *types.Network {
return n
}
func decodeAccountSettings(as *proto.AccountSettingsCompact) *types.AccountSettingsInfo {
func decodeAccountSettings(as *proto.AccountSettingsCompact) *nmdata.AccountSettingsInfo {
if as == nil {
return &types.AccountSettingsInfo{}
return &nmdata.AccountSettingsInfo{}
}
return &types.AccountSettingsInfo{
return &nmdata.AccountSettingsInfo{
PeerLoginExpirationEnabled: as.PeerLoginExpirationEnabled,
PeerLoginExpiration: time.Duration(as.PeerLoginExpirationNs),
}
}
func decodePeerCompact(pc *proto.PeerCompact, peerID string) *types.ComponentPeer {
peer := &types.ComponentPeer{
func decodePeerCompact(pc *proto.PeerCompact, peerID string) *nmdata.Peer {
var caps []int32
if pc.SupportsSourcePrefixes {
caps = append(caps, nmdata.PeerCapabilitySourcePrefixes)
}
if pc.SupportsIpv6 {
caps = append(caps, nmdata.PeerCapabilityIPv6Overlay)
}
peer := &nmdata.Peer{
ID: peerID,
Key: peerID,
SSHKey: string(pc.SshPubKey),
SSHEnabled: pc.SshEnabled,
DNSLabel: pc.DnsLabel,
LoginExpirationEnabled: pc.LoginExpirationEnabled,
AgentVersion: pc.AgentVersion,
SupportsSourcePrefixes: pc.SupportsSourcePrefixes,
SupportsIPv6: pc.SupportsIpv6,
ServerSSHAllowed: pc.ServerSshAllowed,
AddedWithSSOLogin: pc.AddedWithSsoLogin,
ProxyEmbedded: pc.ProxyEmbedded,
ProxyMeta: nmdata.ProxyMeta{Embedded: pc.ProxyEmbedded},
Meta: nmdata.PeerSystemMeta{
WtVersion: pc.AgentVersion,
Capabilities: caps,
Flags: nmdata.Flags{
ServerSSHAllowed: pc.ServerSshAllowed,
},
},
}
if pc.AddedWithSsoLogin {
// Set a non-empty UserID so (*Peer).AddedWithSSOLogin() returns true.
// The original UserID isn't on the wire; the value is intentionally
// visibly synthetic so any future consumer that mistakes UserID for a
// real account user xid won't silently match (or worse, write the
// sentinel into a downstream record).
peer.UserID = "<env-sso>"
}
if pc.LastLoginUnixNano != 0 {
peer.LastLogin = time.Unix(0, pc.LastLoginUnixNano)
t := time.Unix(0, pc.LastLoginUnixNano)
peer.LastLogin = &t
}
switch len(pc.Ip) {
case 4:
@@ -296,13 +375,13 @@ func decodePeerCompact(pc *proto.PeerCompact, peerID string) *types.ComponentPee
return peer
}
func decodePolicyCompact(pc *proto.PolicyCompact, policyID string, peerIDByIndex []string) *types.Policy {
rule := &types.PolicyRule{
func decodePolicyCompact(pc *proto.PolicyCompact, policyID string, peerIDByIndex []string) *nmdata.Policy {
rule := &nmdata.PolicyRule{
ID: policyID, // 1 rule per policy → reuse synthesized id
PolicyID: policyID,
Enabled: true,
Action: actionFromProto(pc.Action),
Protocol: protocolFromProto(pc.Protocol),
Action: string(actionFromProto(pc.Action)),
Protocol: string(protocolFromProto(pc.Protocol)),
Bidirectional: pc.Bidirectional,
Ports: uint32SliceToStrings(pc.Ports),
PortRanges: portRangesFromProto(pc.PortRanges),
@@ -313,11 +392,11 @@ func decodePolicyCompact(pc *proto.PolicyCompact, policyID string, peerIDByIndex
SourceResource: resourceFromProto(pc.SourceResource, peerIDByIndex),
DestinationResource: resourceFromProto(pc.DestinationResource, peerIDByIndex),
}
return &types.Policy{
return &nmdata.Policy{
ID: policyID,
PublicID: pc.Id,
Enabled: true,
Rules: []*types.PolicyRule{rule},
Rules: []*nmdata.PolicyRule{rule},
SourcePostureChecks: pc.SourcePostureCheckIds,
}
}
@@ -325,15 +404,19 @@ func decodePolicyCompact(pc *proto.PolicyCompact, policyID string, peerIDByIndex
// resourceFromProto rebuilds types.Resource. For peer-typed resources the
// peer reference is reconstructed from the envelope's peer index — wire
// format ships no xid for peers, so we use the synthesized peer id.
func resourceFromProto(r *proto.ResourceCompact, peerIDByIndex []string) types.Resource {
if r == nil {
return types.Resource{}
func resourceFromProto(r *proto.ResourceCompact, peerIDByIndex []string) nmdata.Resource {
if r == nil || !types.ResourceType(r.Type).Valid() {
return nmdata.Resource{}
}
out := types.Resource{Type: types.ResourceType(r.Type)}
if r.PeerIndexSet && int(r.PeerIndex) < len(peerIDByIndex) {
out.ID = peerIDByIndex[r.PeerIndex]
if r.Type == string(types.ResourceTypePeer) {
if !r.PeerIndexSet || int(r.PeerIndex) >= len(peerIDByIndex) {
return nmdata.Resource{}
}
return nmdata.Resource{Type: r.Type, ID: peerIDByIndex[int(r.PeerIndex)]}
}
return out
return nmdata.Resource{Type: r.Type, ID: r.Id}
}
// authorizedGroupsFromProto inverts encodeAuthorizedGroups: the wire form
@@ -354,15 +437,15 @@ func authorizedGroupsFromProto(m map[string]*proto.UserNameList) map[string][]st
return out
}
func decodeRouteRaw(rr *proto.RouteRaw, peerIDByIndex []string) *nbroute.Route {
r := &nbroute.Route{
ID: nbroute.ID(rr.Id),
func decodeRouteRaw(rr *proto.RouteRaw, peerIDByIndex []string) *nmdata.Route {
r := &nmdata.Route{
ID: rr.Id,
PublicID: rr.Id,
NetID: nbroute.NetID(rr.NetId),
NetID: rr.NetId,
Description: rr.Description,
Domains: domainsFromPunycode(rr.Domains),
KeepRoute: rr.KeepRoute,
NetworkType: nbroute.NetworkType(rr.NetworkType),
NetworkType: int(rr.NetworkType),
Masquerade: rr.Masquerade,
Metric: int(rr.Metric),
Enabled: rr.Enabled,
@@ -382,8 +465,8 @@ func decodeRouteRaw(rr *proto.RouteRaw, peerIDByIndex []string) *nbroute.Route {
return r
}
func decodeNameServerGroupRaw(nsg *proto.NameServerGroupRaw) *nbdns.NameServerGroup {
out := &nbdns.NameServerGroup{
func decodeNameServerGroupRaw(nsg *proto.NameServerGroupRaw) *nmdata.NameServerGroup {
out := &nmdata.NameServerGroup{
ID: nsg.Id,
PublicID: nsg.Id,
Groups: nsg.GroupIds,
@@ -391,13 +474,13 @@ func decodeNameServerGroupRaw(nsg *proto.NameServerGroupRaw) *nbdns.NameServerGr
Domains: nsg.Domains,
Enabled: nsg.Enabled,
SearchDomainsEnabled: nsg.SearchDomainsEnabled,
NameServers: make([]nbdns.NameServer, 0, len(nsg.Nameservers)),
NameServers: make([]nmdata.NameServer, 0, len(nsg.Nameservers)),
}
for _, ns := range nsg.Nameservers {
if addr, err := netip.ParseAddr(ns.IP); err == nil {
out.NameServers = append(out.NameServers, nbdns.NameServer{
out.NameServers = append(out.NameServers, nmdata.NameServer{
IP: addr,
NSType: nbdns.NameServerType(ns.NSType),
NSType: int(ns.NSType),
Port: int(ns.Port),
})
}
@@ -405,14 +488,14 @@ func decodeNameServerGroupRaw(nsg *proto.NameServerGroupRaw) *nbdns.NameServerGr
return out
}
func decodeNetworkResource(nr *proto.NetworkResourceRaw) *types.ComponentResource {
out := &types.ComponentResource{
func decodeNetworkResource(nr *proto.NetworkResourceRaw) *nmdata.NetworkResource {
out := &nmdata.NetworkResource{
ID: nr.Id,
PublicID: nr.Id,
NetworkID: nr.NetworkSeq,
Name: nr.Name,
Description: nr.Description,
Type: types.ComponentResourceType(nr.Type),
Type: nr.Type,
Address: nr.Address,
Domain: nr.DomainValue,
Enabled: nr.Enabled,
@@ -425,10 +508,10 @@ func decodeNetworkResource(nr *proto.NetworkResourceRaw) *types.ComponentResourc
return out
}
func decodeSimpleRecords(records []*proto.SimpleRecord) []nbdns.SimpleRecord {
out := make([]nbdns.SimpleRecord, 0, len(records))
func decodeSimpleRecords(records []*proto.SimpleRecord) []nmdata.SimpleRecord {
out := make([]nmdata.SimpleRecord, 0, len(records))
for _, r := range records {
out = append(out, nbdns.SimpleRecord{
out = append(out, nmdata.SimpleRecord{
Name: r.Name,
Type: int(r.Type),
Class: r.Class,
@@ -439,10 +522,10 @@ func decodeSimpleRecords(records []*proto.SimpleRecord) []nbdns.SimpleRecord {
return out
}
func decodeCustomZones(zones []*proto.CustomZone) []nbdns.CustomZone {
out := make([]nbdns.CustomZone, 0, len(zones))
func decodeCustomZones(zones []*proto.CustomZone) []nmdata.CustomZone {
out := make([]nmdata.CustomZone, 0, len(zones))
for _, z := range zones {
out = append(out, nbdns.CustomZone{
out = append(out, nmdata.CustomZone{
Domain: z.Domain,
Records: decodeSimpleRecords(z.Records),
SearchDomainDisabled: z.SearchDomainDisabled,
@@ -463,16 +546,16 @@ func uint32SliceToStrings(ports []uint32) []string {
return out
}
func portRangesFromProto(ranges []*proto.PortInfo_Range) []types.RulePortRange {
func portRangesFromProto(ranges []*proto.PortInfo_Range) []nmdata.RulePortRange {
if len(ranges) == 0 {
return nil
}
out := make([]types.RulePortRange, 0, len(ranges))
out := make([]nmdata.RulePortRange, 0, len(ranges))
for _, r := range ranges {
if r == nil || r.Start > 65535 || r.End > 65535 {
continue
}
out = append(out, types.RulePortRange{
out = append(out, nmdata.RulePortRange{
Start: uint16(r.Start),
End: uint16(r.End),
})

View File

@@ -0,0 +1,61 @@
package networkmap
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
protobuf "google.golang.org/protobuf/proto"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
)
func TestDecodePolicy(t *testing.T) {
assert.Equal(t,
nmdata.Resource{Type: "peer", ID: "valid-id"},
resourceFromProto(
&proto.ResourceCompact{Type: "peer", PeerIndexSet: true, PeerIndex: uint32(1)},
[]string{"invalid-id-0", "valid-id", "invalid-id-2"}))
// check invalid peer index returns an empty resource
assert.Equal(t,
nmdata.Resource{},
resourceFromProto(
&proto.ResourceCompact{Type: "peer", PeerIndexSet: true, PeerIndex: uint32(100)},
[]string{"invalid-id-0", "valid-id", "invalid-id-2"}))
assert.Equal(t,
nmdata.Resource{Type: "domain", ID: "domain"},
resourceFromProto(
&proto.ResourceCompact{Type: "domain", Id: "domain"}, []string{}))
assert.Equal(t,
nmdata.Resource{Type: "host", ID: "host"},
resourceFromProto(
&proto.ResourceCompact{Type: "host", Id: "host"}, []string{}))
assert.Equal(t,
nmdata.Resource{Type: "subnet", ID: "subnet"},
resourceFromProto(
&proto.ResourceCompact{Type: "subnet", Id: "subnet"}, []string{}))
// an unknown resource type return an empty resource
assert.Equal(t,
nmdata.Resource{},
resourceFromProto(
&proto.ResourceCompact{Type: "boom", Id: "boom"}, []string{}))
}
// ResourceCompact fields 1-3 are the v0.77 wire contract. Retyping any of them
// makes peers on either side of the change silently drop policy resources, so
// the encoding is pinned here as raw bytes: field 1 "peer" (bytes), field 2
// true (varint), field 3 7 (varint).
func TestResourceCompactLegacyWireFormat(t *testing.T) {
legacy := []byte{0x0a, 0x04, 'p', 'e', 'e', 'r', 0x10, 0x01, 0x18, 0x07}
var decoded proto.ResourceCompact
require.NoError(t, protobuf.Unmarshal(legacy, &decoded))
assert.Equal(t, "peer", decoded.Type)
assert.True(t, decoded.PeerIndexSet)
assert.Equal(t, uint32(7), decoded.PeerIndex)
encoded, err := protobuf.Marshal(&proto.ResourceCompact{Type: "peer", PeerIndexSet: true, PeerIndex: 7})
require.NoError(t, err)
assert.Equal(t, legacy, encoded)
}

View File

@@ -17,10 +17,11 @@ import (
log "github.com/sirupsen/logrus"
goproto "google.golang.org/protobuf/proto"
nbdns "github.com/netbirdio/netbird/dns"
"net/netip"
nbroute "github.com/netbirdio/netbird/route"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
"github.com/netbirdio/netbird/shared/management/types"
"github.com/netbirdio/netbird/shared/netiputil"
@@ -28,7 +29,7 @@ import (
)
// ToProtocolRoutes converts a slice of typed routes to their proto form.
func ToProtocolRoutes(routes []*nbroute.Route) []*proto.Route {
func ToProtocolRoutes(routes []*nmdata.Route) []*proto.Route {
protoRoutes := make([]*proto.Route, 0, len(routes))
for _, r := range routes {
protoRoutes = append(protoRoutes, ToProtocolRoute(r))
@@ -37,7 +38,7 @@ func ToProtocolRoutes(routes []*nbroute.Route) []*proto.Route {
}
// ToProtocolRoute converts one typed route to its proto form.
func ToProtocolRoute(route *nbroute.Route) *proto.Route {
func ToProtocolRoute(route *nmdata.Route) *proto.Route {
return &proto.Route{
ID: string(route.ID),
NetID: string(route.NetID),
@@ -274,7 +275,7 @@ func ToProtocolDNSConfig(update nbdns.Config, cache DNSConfigCache, forwardPort
// AppendRemotePeerConfig appends typed peers as proto.RemotePeerConfig
// entries to dst and returns the result. localIsProxy reports whether the peer
// receiving this config is itself an embedded proxy.
func AppendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*types.ComponentPeer, dnsName string, includeIPv6 bool, localIsProxy bool) []*proto.RemotePeerConfig {
func AppendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*nmdata.Peer, dnsName string, includeIPv6 bool, localIsProxy bool) []*proto.RemotePeerConfig {
for _, rPeer := range peers {
allowedIPs := []string{rPeer.IP.String() + "/32"}
if includeIPv6 && rPeer.IPv6.IsValid() {
@@ -285,7 +286,7 @@ func AppendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*types.Compon
AllowedIps: allowedIPs,
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
Fqdn: rPeer.FQDN(dnsName),
AgentVersion: rPeer.AgentVersion,
AgentVersion: rPeer.Meta.WtVersion,
LazyState: lazyStateFor(localIsProxy, rPeer),
})
}
@@ -297,8 +298,8 @@ func AppendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*types.Compon
// proxy infrastructure is not kept permanently connected to every peer. All
// other peers follow the account-wide flag. A future admin-facing per-peer
// setting can return LazyStateEager here to force a peer always-active.
func lazyStateFor(localIsProxy bool, rPeer *types.ComponentPeer) proto.LazyState {
if localIsProxy || rPeer.ProxyEmbedded {
func lazyStateFor(localIsProxy bool, rPeer *nmdata.Peer) proto.LazyState {
if localIsProxy || rPeer.ProxyMeta.Embedded {
return proto.LazyState_LazyStateLazy
}
return proto.LazyState_LazyStateDefault

View File

@@ -36,7 +36,7 @@ type EnvelopeResult struct {
// dnsName is the account's DNS domain ("netbird.cloud" etc.); used when
// rebuilding the per-peer FQDNs that proto.RemotePeerConfig carries.
func EnvelopeToNetworkMap(ctx context.Context, env *proto.NetworkMapEnvelope, localPeerKey, dnsName string) (*EnvelopeResult, error) {
components, err := DecodeEnvelope(env)
components, err := DecodeEnvelope(ctx, env)
if err != nil {
return nil, fmt.Errorf("decode envelope: %w", err)
}
@@ -54,8 +54,8 @@ func EnvelopeToNetworkMap(ctx context.Context, env *proto.NetworkMapEnvelope, lo
}
components.PeerID = canonicalKey
includeIPv6 := localPeer.SupportsIPv6 && localPeer.IPv6.IsValid()
useSourcePrefixes := localPeer.SupportsSourcePrefixes
includeIPv6 := localPeer.SupportsIPv6() && localPeer.IPv6.IsValid()
useSourcePrefixes := localPeer.SupportsSourcePrefixes()
typedNM := components.Calculate(ctx)
@@ -74,11 +74,11 @@ func EnvelopeToNetworkMap(ctx context.Context, env *proto.NetworkMapEnvelope, lo
protoNM.Routes = ToProtocolRoutes(typedNM.Routes)
protoNM.DNSConfig = ToProtocolDNSConfig(typedNM.DNSConfig, nil, dnsFwdPort)
remotePeers := AppendRemotePeerConfig(nil, typedNM.Peers, dnsName, includeIPv6, localPeer.ProxyEmbedded)
remotePeers := AppendRemotePeerConfig(nil, typedNM.Peers, dnsName, includeIPv6, localPeer.ProxyMeta.Embedded)
protoNM.RemotePeers = remotePeers
protoNM.RemotePeersIsEmpty = len(remotePeers) == 0
protoNM.OfflinePeers = AppendRemotePeerConfig(nil, typedNM.OfflinePeers, dnsName, includeIPv6, localPeer.ProxyEmbedded)
protoNM.OfflinePeers = AppendRemotePeerConfig(nil, typedNM.OfflinePeers, dnsName, includeIPv6, localPeer.ProxyMeta.Embedded)
firewallRules := ToProtocolFirewallRules(typedNM.FirewallRules, includeIPv6, useSourcePrefixes)
protoNM.FirewallRules = firewallRules

View File

@@ -15,6 +15,7 @@ import (
mgmtgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
"github.com/netbirdio/netbird/management/server/types"
nbnetworkmap "github.com/netbirdio/netbird/shared/management/networkmap"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
)
@@ -55,13 +56,13 @@ func TestEnvelopeToNetworkMap_RoundTrip(t *testing.T) {
func TestCalculate_FirewallRuleProtocol_NeverNetbirdSSH(t *testing.T) {
c, localPeerKey := buildSmokeComponents(t)
// Replace the smoke policy with a NetbirdSSH-protocol allow.
c.Policies = []*types.Policy{{
c.Policies = []*nmdata.Policy{{
ID: "pol-ssh", PublicID: "2", Enabled: true,
Rules: []*types.PolicyRule{{
Rules: []*nmdata.PolicyRule{{
ID: "rule-ssh",
Enabled: true,
Action: types.PolicyTrafficActionAccept,
Protocol: types.PolicyRuleProtocolNetbirdSSH,
Action: string(types.PolicyTrafficActionAccept),
Protocol: string(types.PolicyRuleProtocolNetbirdSSH),
Bidirectional: true,
Sources: []string{"group-all"},
Destinations: []string{"group-all"},
@@ -143,39 +144,39 @@ func TestDecodeEnvelope_MalformedWgKeyPeerSkipped(t *testing.T) {
func TestEnvelopeRoundTrip_AllGroupShortCircuitParity(t *testing.T) {
ctx := context.Background()
peers := map[string]*types.ComponentPeer{}
peers := map[string]*nmdata.Peer{}
for i, id := range []string{"peer-T", "peer-S", "peer-ALL", "peer-O"} {
peers[id] = &types.ComponentPeer{
ID: id,
Key: randomWgKey(t),
IP: netip.AddrFrom4([4]byte{100, 64, 0, byte(i + 1)}),
DNSLabel: id,
AgentVersion: "0.40.0",
peers[id] = &nmdata.Peer{
ID: id,
Key: randomWgKey(t),
IP: netip.AddrFrom4([4]byte{100, 64, 0, byte(i + 1)}),
DNSLabel: id,
Meta: nmdata.PeerSystemMeta{WtVersion: "0.40.0"},
}
}
c := &types.NetworkMapComponents{
PeerID: "peer-T",
Network: &types.Network{
Network: &nmdata.Network{
Identifier: "net-all-groups",
Net: net.IPNet{IP: net.IP{100, 64, 0, 0}, Mask: net.CIDRMask(10, 32)},
Serial: 1,
},
AccountSettings: &types.AccountSettingsInfo{},
DNSSettings: &types.DNSSettings{},
AccountSettings: &nmdata.AccountSettingsInfo{},
DNSSettings: &nmdata.DNSSettings{},
Peers: peers,
Groups: map[string]*types.ComponentGroup{
"g-src": {ID: "g-src", PublicID: "1", Name: "staff", Peers: []string{"peer-T", "peer-S"}},
"g-all": {ID: "g-all", PublicID: "2", Name: "All", Peers: []string{"peer-ALL"}},
"g-two": {ID: "g-two", PublicID: "3", Name: "second", Peers: []string{"peer-T", "peer-O"}},
Groups: map[string]*nmdata.Group{
"g-src": {PublicID: "1", Name: "staff", Peers: []string{"peer-T", "peer-S"}},
"g-all": {PublicID: "2", Name: "All", Peers: []string{"peer-ALL"}},
"g-two": {PublicID: "3", Name: "second", Peers: []string{"peer-T", "peer-O"}},
},
Policies: []*types.Policy{{
Policies: []*nmdata.Policy{{
ID: "pol-multi-dest", PublicID: "10", Enabled: true,
Rules: []*types.PolicyRule{{
Rules: []*nmdata.PolicyRule{{
ID: "rule-multi-dest",
Enabled: true,
Action: types.PolicyTrafficActionAccept,
Protocol: types.PolicyRuleProtocolALL,
Action: string(types.PolicyTrafficActionAccept),
Protocol: string(types.PolicyRuleProtocolALL),
Sources: []string{"g-src"},
Destinations: []string{"g-all", "g-two"},
}},
@@ -231,12 +232,12 @@ func TestEnvelopeToNetworkMap_EmptyComponents(t *testing.T) {
localPeerKey := randomWgKey(t)
c := types.EmptyNetworkMapComponents(&types.NetworkMapComponents{
PeerID: "peer-A",
Network: &types.Network{
Network: &nmdata.Network{
Identifier: "net-empty",
Net: net.IPNet{IP: net.IP{100, 64, 0, 0}, Mask: net.CIDRMask(10, 32)},
Serial: 7,
},
Peers: map[string]*types.ComponentPeer{
Peers: map[string]*nmdata.Peer{
"peer-A": {ID: "peer-A", Key: localPeerKey, IP: netip.AddrFrom4([4]byte{100, 64, 0, 1})},
},
})
@@ -291,33 +292,33 @@ func buildSmokeComponents(t *testing.T) (*types.NetworkMapComponents, string) {
peerAKey := randomWgKey(t)
peerBKey := randomWgKey(t)
peerA := &types.ComponentPeer{
ID: "peer-A",
Key: peerAKey,
IP: netip.AddrFrom4([4]byte{100, 64, 0, 1}),
DNSLabel: "peerA",
AgentVersion: "0.40.0",
peerA := &nmdata.Peer{
ID: "peer-A",
Key: peerAKey,
IP: netip.AddrFrom4([4]byte{100, 64, 0, 1}),
DNSLabel: "peerA",
Meta: nmdata.PeerSystemMeta{WtVersion: "0.40.0"},
}
peerB := &types.ComponentPeer{
ID: "peer-B",
Key: peerBKey,
IP: netip.AddrFrom4([4]byte{100, 64, 0, 2}),
DNSLabel: "peerB",
AgentVersion: "0.40.0",
peerB := &nmdata.Peer{
ID: "peer-B",
Key: peerBKey,
IP: netip.AddrFrom4([4]byte{100, 64, 0, 2}),
DNSLabel: "peerB",
Meta: nmdata.PeerSystemMeta{WtVersion: "0.40.0"},
}
group := &types.ComponentGroup{
ID: "group-all", PublicID: "1", Name: "All",
group := &nmdata.Group{
PublicID: "1", Name: "All",
Peers: []string{"peer-A", "peer-B"},
}
policy := &types.Policy{
policy := &nmdata.Policy{
ID: "pol-allow", PublicID: "1", Enabled: true,
Rules: []*types.PolicyRule{{
Rules: []*nmdata.PolicyRule{{
ID: "rule-allow",
Enabled: true,
Action: types.PolicyTrafficActionAccept,
Protocol: types.PolicyRuleProtocolALL,
Action: string(types.PolicyTrafficActionAccept),
Protocol: string(types.PolicyRuleProtocolALL),
Bidirectional: true,
Sources: []string{"group-all"},
Destinations: []string{"group-all"},
@@ -326,21 +327,21 @@ func buildSmokeComponents(t *testing.T) (*types.NetworkMapComponents, string) {
c := &types.NetworkMapComponents{
PeerID: "peer-A",
Network: &types.Network{
Network: &nmdata.Network{
Identifier: "net-smoke",
Net: net.IPNet{IP: net.IP{100, 64, 0, 0}, Mask: net.CIDRMask(10, 32)},
Serial: 1,
},
AccountSettings: &types.AccountSettingsInfo{},
DNSSettings: &types.DNSSettings{},
Peers: map[string]*types.ComponentPeer{
AccountSettings: &nmdata.AccountSettingsInfo{},
DNSSettings: &nmdata.DNSSettings{},
Peers: map[string]*nmdata.Peer{
"peer-A": peerA,
"peer-B": peerB,
},
Groups: map[string]*types.ComponentGroup{
Groups: map[string]*nmdata.Group{
"group-all": group,
},
Policies: []*types.Policy{policy},
Policies: []*nmdata.Policy{policy},
}
return c, peerAKey
}

View File

@@ -0,0 +1,812 @@
package networkmap
import (
"slices"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/types"
)
type sshRequirements struct {
neededGroupIDs map[string]struct{}
needAllowedUserIDs bool
}
// GetPeerNetworkMapComponents computes the peer's NetworkMapComponents from the
// slim twin store. It mirrors the former Account.GetPeerNetworkMapComponents
// 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]
if peer == nil {
return types.EmptyNetworkMapComponents(&types.NetworkMapComponents{
PeerID: peerID,
Network: nmd.Network,
Peers: map[string]*nmdata.Peer{peerID: peer},
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
})
}
if _, ok := nmd.ValidatedPeers[peerID]; !ok {
return types.EmptyNetworkMapComponents(&types.NetworkMapComponents{
PeerID: peerID,
Network: nmd.Network,
Peers: map[string]*nmdata.Peer{peerID: peer},
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
})
}
components := &types.NetworkMapComponents{
PeerID: peerID,
Network: nmd.Network,
AccountSettings: nmd.AccountSettings,
DNSSettings: nmd.DNSSettings,
CustomZoneDomain: peersCustomZone.Domain,
NameServerGroups: make([]*nmdata.NameServerGroup, 0),
ResourcePoliciesMap: make(map[string][]*nmdata.Policy),
RoutersMap: make(map[string]map[string]*nmdata.NetworkRouter),
NetworkResources: make([]*nmdata.NetworkResource, 0),
PostureFailedPeers: make(map[string]map[string]struct{}, len(nmd.PostureChecks)),
RouterPeers: make(map[string]*nmdata.Peer),
NetworkXIDToPublicID: nmd.NetworkXIDToPublicID,
PostureCheckXIDToPublicID: nmd.PostureCheckXIDToPublicID,
ForceRoutingPeerDNSResolution: forceRoutingPeerDNS,
}
relevantPeers, relevantGroups, relevantPolicies, relevantRoutes, sshReqs := nmd.getPeersGroupsPoliciesRoutes(peerID, peer.SSHEnabled, &components.PostureFailedPeers)
if len(sshReqs.neededGroupIDs) > 0 {
components.GroupIDToUserIDs = filterGroupIDToUserIDs(nmd.GroupIDToUserIDs, sshReqs.neededGroupIDs)
}
if sshReqs.needAllowedUserIDs {
components.AllowedUserIDs = nmd.getAllowedUserIDs()
}
components.Peers = relevantPeers
components.Groups = relevantGroups
components.Policies = relevantPolicies
components.Routes = relevantRoutes
components.AllDNSRecords = filterDNSRecordsByPeers(peersCustomZone.Records, relevantPeers, peer.SupportsIPv6() && peer.IPv6.IsValid())
peerGroups := nmd.GetPeerGroups(peerID)
components.AccountZones = nmd.appliedZones(peerGroups)
components.AccountZones = append(components.AccountZones, nmd.privateServiceZones(peerGroups)...)
for _, nsGroup := range nmd.NameServerGroups {
if nsGroup != nil && nsGroup.Enabled {
for _, gID := range nsGroup.Groups {
if _, found := relevantGroups[gID]; found {
components.NameServerGroups = append(components.NameServerGroups, nsGroup)
break
}
}
}
}
for _, resource := range nmd.NetworkResources {
if resource == nil || !resource.Enabled {
continue
}
policies, exists := nmd.ResourcePolicies[resource.ID]
if !exists {
continue
}
addSourcePeers := false
networkRoutingPeers, routerExists := nmd.Routers[resource.NetworkID]
if routerExists {
if _, ok := networkRoutingPeers[peerID]; ok {
addSourcePeers = true
}
}
for _, policy := range policies {
if policy == nil || !policy.Enabled || len(policy.Rules) == 0 || policy.Rules[0] == nil {
continue
}
if addSourcePeers {
var peers []string
if policy.Rules[0].SourceResource.Type == string(types.ResourceTypePeer) && policy.Rules[0].SourceResource.ID != "" {
peers = []string{policy.Rules[0].SourceResource.ID}
} else {
peers = nmd.getUniquePeerIDsFromGroupsIDs(policy.SourceGroups())
}
for _, pID := range nmd.getPostureValidPeersSaveFailed(peers, policy.SourcePostureChecks, &components.PostureFailedPeers) {
if _, exists := components.Peers[pID]; !exists {
components.Peers[pID] = nmd.Peers[pID]
}
}
} else {
peerInSources := false
if policy.Rules[0].SourceResource.Type == string(types.ResourceTypePeer) && policy.Rules[0].SourceResource.ID != "" {
peerInSources = policy.Rules[0].SourceResource.ID == peerID
} else {
for _, groupID := range policy.SourceGroups() {
if group := nmd.Groups[groupID]; group != nil && slices.Contains(group.Peers, peerID) {
peerInSources = true
break
}
}
}
if !peerInSources {
continue
}
isValid, pname := nmd.validatePostureChecksOnPeerGetFailed(policy.SourcePostureChecks, peerID)
if !isValid && len(pname) > 0 {
if _, ok := components.PostureFailedPeers[pname]; !ok {
components.PostureFailedPeers[pname] = make(map[string]struct{})
}
components.PostureFailedPeers[pname][peer.ID] = struct{}{}
continue
}
addSourcePeers = true
}
for _, rule := range policy.Rules {
if rule == nil || !rule.Enabled {
continue
}
for _, srcGroupID := range rule.Sources {
if g := nmd.Groups[srcGroupID]; g != nil {
if _, exists := components.Groups[srcGroupID]; !exists {
components.Groups[srcGroupID] = g
}
}
}
for _, dstGroupID := range rule.Destinations {
if g := nmd.Groups[dstGroupID]; g != nil {
if _, exists := components.Groups[dstGroupID]; !exists {
components.Groups[dstGroupID] = g
}
}
}
}
components.ResourcePoliciesMap[resource.ID] = policies
}
if addSourcePeers {
components.RoutersMap[resource.NetworkID] = networkRoutingPeers
for peerIDKey := range networkRoutingPeers {
p := nmd.Peers[peerIDKey]
if p == nil {
continue
}
// An unapproved peer must not carry traffic, so it is kept out of
// RouterPeers as well: the envelope encoder indexes that map into
// the wire peer table, from which the client restores every entry.
if _, validated := nmd.ValidatedPeers[peerIDKey]; !validated {
continue
}
if _, exists := components.RouterPeers[peerIDKey]; !exists {
components.RouterPeers[peerIDKey] = p
}
if _, exists := components.Peers[peerIDKey]; !exists {
components.Peers[peerIDKey] = p
}
}
components.NetworkResources = append(components.NetworkResources, resource)
}
}
filterGroupPeers(&components.Groups, components.Peers)
filterPostureFailedPeers(&components.PostureFailedPeers, components.Policies, components.ResourcePoliciesMap, components.Peers)
return components
}
func (nmd *NetworkMapData) getPeersGroupsPoliciesRoutes(
peerID string,
peerSSHEnabled bool,
postureFailedPeers *map[string]map[string]struct{},
) (map[string]*nmdata.Peer, map[string]*nmdata.Group, []*nmdata.Policy, []*nmdata.Route, sshRequirements) {
relevantPeerIDs := make(map[string]*nmdata.Peer, len(nmd.Peers)/4)
relevantGroupIDs := make(map[string]*nmdata.Group, len(nmd.Groups)/4)
relevantPolicies := make([]*nmdata.Policy, 0, len(nmd.Policies))
relevantRoutes := make([]*nmdata.Route, 0, len(nmd.Routes))
sshReqs := sshRequirements{neededGroupIDs: make(map[string]struct{})}
relevantPeerIDs[peerID] = nmd.Peers[peerID]
peerGroupSet := nmd.GetPeerGroups(peerID)
for groupID := range peerGroupSet {
relevantGroupIDs[groupID] = nmd.Groups[groupID]
}
routeAccessControlGroups := make(map[string]struct{})
for _, r := range nmd.Routes {
if r == nil {
continue
}
relevant := r.Peer == peerID
if !relevant {
for _, groupID := range r.PeerGroups {
if _, ok := peerGroupSet[groupID]; ok {
relevant = true
break
}
}
}
if !relevant && r.Enabled {
for _, groupID := range r.Groups {
if _, ok := peerGroupSet[groupID]; ok {
relevant = true
break
}
}
}
if !relevant {
continue
}
for _, groupID := range r.PeerGroups {
if g := nmd.Groups[groupID]; g != nil {
relevantGroupIDs[groupID] = g
}
}
for _, groupID := range r.Groups {
if g := nmd.Groups[groupID]; g != nil {
relevantGroupIDs[groupID] = g
}
}
if r.Enabled {
for _, groupID := range r.AccessControlGroups {
if g := nmd.Groups[groupID]; g != nil {
relevantGroupIDs[groupID] = g
}
routeAccessControlGroups[groupID] = struct{}{}
}
}
if r.Peer != "" {
if _, ok := nmd.ValidatedPeers[r.Peer]; ok {
if p := nmd.Peers[r.Peer]; p != nil {
relevantPeerIDs[r.Peer] = p
}
}
}
for _, groupID := range r.PeerGroups {
g := nmd.Groups[groupID]
if g == nil {
continue
}
for _, pid := range g.Peers {
if _, exists := relevantPeerIDs[pid]; exists {
continue
}
if _, ok := nmd.ValidatedPeers[pid]; !ok {
continue
}
if p := nmd.Peers[pid]; p != nil {
relevantPeerIDs[pid] = p
}
}
}
relevantRoutes = append(relevantRoutes, r)
}
for _, policy := range nmd.Policies {
if policy == nil || !policy.Enabled {
continue
}
policyRelevant := false
for _, rule := range policy.Rules {
if rule == nil || !rule.Enabled {
continue
}
if len(routeAccessControlGroups) > 0 {
for _, destGroupID := range rule.Destinations {
if _, needed := routeAccessControlGroups[destGroupID]; needed {
policyRelevant = true
for _, srcGroupID := range rule.Sources {
if g := nmd.Groups[srcGroupID]; g != nil {
relevantGroupIDs[srcGroupID] = g
}
}
for _, dstGroupID := range rule.Destinations {
if g := nmd.Groups[dstGroupID]; g != nil {
relevantGroupIDs[dstGroupID] = g
}
}
break
}
}
}
var sourcePeers, destinationPeers []string
var peerInSources, peerInDestinations bool
if rule.SourceResource.Type == string(types.ResourceTypePeer) && rule.SourceResource.ID != "" {
sourcePeers = []string{rule.SourceResource.ID}
if rule.SourceResource.ID == peerID {
peerInSources = true
}
} else {
sourcePeers, peerInSources = nmd.getPeersFromGroups(rule.Sources, peerID, policy.SourcePostureChecks, postureFailedPeers)
}
if rule.DestinationResource.Type == string(types.ResourceTypePeer) && rule.DestinationResource.ID != "" {
destinationPeers = []string{rule.DestinationResource.ID}
if rule.DestinationResource.ID == peerID {
peerInDestinations = true
}
} else {
destinationPeers, peerInDestinations = nmd.getPeersFromGroups(rule.Destinations, peerID, nil, postureFailedPeers)
}
if peerInSources {
policyRelevant = true
for _, pid := range destinationPeers {
relevantPeerIDs[pid] = nmd.Peers[pid]
}
for _, dstGroupID := range rule.Destinations {
if g := nmd.Groups[dstGroupID]; g != nil {
relevantGroupIDs[dstGroupID] = g
}
}
}
if peerInDestinations {
policyRelevant = true
for _, pid := range sourcePeers {
relevantPeerIDs[pid] = nmd.Peers[pid]
}
for _, srcGroupID := range rule.Sources {
if g := nmd.Groups[srcGroupID]; g != nil {
relevantGroupIDs[srcGroupID] = g
}
}
if rule.Protocol == string(types.PolicyRuleProtocolNetbirdSSH) {
switch {
case len(rule.AuthorizedGroups) > 0:
for groupID := range rule.AuthorizedGroups {
sshReqs.neededGroupIDs[groupID] = struct{}{}
}
case rule.AuthorizedUser != "":
default:
sshReqs.needAllowedUserIDs = true
}
} else if nmdata.PolicyRuleImpliesLegacySSH(rule) && peerSSHEnabled {
sshReqs.needAllowedUserIDs = true
}
}
}
if policyRelevant {
relevantPolicies = append(relevantPolicies, policy)
}
}
return relevantPeerIDs, relevantGroupIDs, relevantPolicies, relevantRoutes, sshReqs
}
func (nmd *NetworkMapData) getPeersFromGroups(groups []string, peerID string, sourcePostureChecksIDs []string,
postureFailedPeers *map[string]map[string]struct{}) ([]string, bool) {
peerInGroups := false
filteredPeerIDs := make([]string, 0, len(groups))
seenPeerIds := make(map[string]struct{}, len(groups))
for _, gid := range groups {
group := nmd.Groups[gid]
if group == nil {
continue
}
if group.IsGroupAll() || len(groups) == 1 {
filteredPeerIDs = make([]string, 0, len(group.Peers))
peerInGroups = false
for _, pid := range group.Peers {
peer, ok := nmd.Peers[pid]
if !ok || peer == nil {
continue
}
if _, ok := nmd.ValidatedPeers[peer.ID]; !ok {
continue
}
isValid, pname := nmd.validatePostureChecksOnPeerGetFailed(sourcePostureChecksIDs, peer.ID)
if !isValid && len(pname) > 0 {
if _, ok := (*postureFailedPeers)[pname]; !ok {
(*postureFailedPeers)[pname] = make(map[string]struct{})
}
(*postureFailedPeers)[pname][peer.ID] = struct{}{}
continue
}
if peer.ID == peerID {
peerInGroups = true
continue
}
filteredPeerIDs = append(filteredPeerIDs, peer.ID)
}
return filteredPeerIDs, peerInGroups
}
for _, pid := range group.Peers {
if _, seen := seenPeerIds[pid]; seen {
continue
}
seenPeerIds[pid] = struct{}{}
peer, ok := nmd.Peers[pid]
if !ok || peer == nil {
continue
}
if _, ok := nmd.ValidatedPeers[peer.ID]; !ok {
continue
}
isValid, pname := nmd.validatePostureChecksOnPeerGetFailed(sourcePostureChecksIDs, peer.ID)
if !isValid && len(pname) > 0 {
if _, ok := (*postureFailedPeers)[pname]; !ok {
(*postureFailedPeers)[pname] = make(map[string]struct{})
}
(*postureFailedPeers)[pname][peer.ID] = struct{}{}
continue
}
if peer.ID == peerID {
peerInGroups = true
continue
}
filteredPeerIDs = append(filteredPeerIDs, peer.ID)
}
}
return filteredPeerIDs, peerInGroups
}
func (nmd *NetworkMapData) validatePostureChecksOnPeerGetFailed(sourcePostureChecksID []string, peerID string) (bool, string) {
peer, ok := nmd.Peers[peerID]
if !ok || peer == nil {
return false, ""
}
for _, postureChecksID := range sourcePostureChecksID {
if valid, cached := nmd.cachedPostureCheckResult(postureChecksID, peerID); cached {
if !valid {
return false, postureChecksID
}
continue
}
postureChecks := nmd.PostureChecks[postureChecksID]
if postureChecks == nil {
continue
}
if !postureChecks.Passes(peer) {
return false, postureChecksID
}
}
return true, ""
}
func (nmd *NetworkMapData) PrecomputePostureValidation() {
if len(nmd.PostureChecks) == 0 {
nmd.PostureValidation = nil
return
}
checkPeerIDs := make(map[string]map[string]struct{})
for _, policy := range nmd.Policies {
if policy == nil || !policy.Enabled || len(policy.SourcePostureChecks) == 0 {
continue
}
groupPeerIDs := nmd.getUniquePeerIDsFromGroupsIDs(policy.SourceGroups())
for _, postureChecksID := range policy.SourcePostureChecks {
set := checkPeerIDs[postureChecksID]
if set == nil {
set = make(map[string]struct{}, len(groupPeerIDs))
checkPeerIDs[postureChecksID] = set
}
for _, pid := range groupPeerIDs {
set[pid] = struct{}{}
}
for _, rule := range policy.Rules {
if rule == nil {
continue
}
if rule.SourceResource.Type == string(types.ResourceTypePeer) && rule.SourceResource.ID != "" {
set[rule.SourceResource.ID] = struct{}{}
}
}
}
}
results := make(map[string]map[string]bool, len(checkPeerIDs))
for postureChecksID, peerIDs := range checkPeerIDs {
results[postureChecksID] = nmd.evaluatePostureChecksForPeers(postureChecksID, peerIDs)
}
nmd.PostureValidation = results
}
func (nmd *NetworkMapData) evaluatePostureChecksForPeers(postureChecksID string, peerIDs map[string]struct{}) map[string]bool {
postureChecks := nmd.PostureChecks[postureChecksID]
if postureChecks == nil {
return nil
}
checks := postureChecks.GetChecks()
results := make(map[string]bool, len(peerIDs))
for peerID := range peerIDs {
peer := nmd.Peers[peerID]
if peer == nil {
continue
}
results[peerID] = nmdata.PassesChecks(checks, peer)
}
return results
}
func (nmd *NetworkMapData) cachedPostureCheckResult(postureChecksID, peerID string) (bool, bool) {
results, ok := nmd.PostureValidation[postureChecksID]
if !ok {
return false, false
}
if results == nil {
return true, true
}
valid, found := results[peerID]
return valid, found
}
func (nmd *NetworkMapData) getPostureValidPeersSaveFailed(inputPeers []string, postureChecksIDs []string, postureFailedPeers *map[string]map[string]struct{}) []string {
var dest []string
for _, peerID := range inputPeers {
if _, validated := nmd.ValidatedPeers[peerID]; !validated {
continue
}
valid, pname := nmd.validatePostureChecksOnPeerGetFailed(postureChecksIDs, peerID)
if valid {
dest = append(dest, peerID)
continue
}
if pname == "" {
continue
}
if _, ok := (*postureFailedPeers)[pname]; !ok {
(*postureFailedPeers)[pname] = make(map[string]struct{})
}
(*postureFailedPeers)[pname][peerID] = struct{}{}
}
return dest
}
// forcesRoutingPeerDNSResolution reports whether the given peer must run
// routing-peer DNS resolution regardless of the account-global
// RoutingPeerDNSResolutionEnabled setting: true when the peer routes a domain
// network resource targeted by an enabled reverse-proxy service, so the peer's
// DNS forwarder starts and can resolve the target for the embedded proxy peers.
func (nmd *NetworkMapData) forcesRoutingPeerDNSResolution(peerID string) bool {
if len(nmd.ProxyTargetedDomainResourceIDs) == 0 {
return false
}
for _, resource := range nmd.NetworkResources {
if resource == nil || !resource.Enabled || resource.Type != string(types.ResourceTypeDomain) {
continue
}
if _, ok := nmd.ProxyTargetedDomainResourceIDs[resource.ID]; !ok {
continue
}
if _, isRouter := nmd.Routers[resource.NetworkID][peerID]; isRouter {
return true
}
}
return false
}
// GetPeerGroups returns the set of group IDs the peer belongs to. The
// underlying peer→groups index is built once per NetworkMapData and the
// returned set is shared — callers must not mutate it.
func (nmd *NetworkMapData) GetPeerGroups(peerID string) map[string]struct{} {
nmd.peerGroupsOnce.Do(func() {
idx := make(map[string]map[string]struct{}, len(nmd.Peers))
for groupID, group := range nmd.Groups {
if group == nil {
continue
}
for _, pid := range group.Peers {
set, ok := idx[pid]
if !ok {
set = make(map[string]struct{})
idx[pid] = set
}
set[groupID] = struct{}{}
}
}
nmd.peerGroupsIdx = idx
})
if set, ok := nmd.peerGroupsIdx[peerID]; ok {
return set
}
return map[string]struct{}{}
}
func (nmd *NetworkMapData) getUniquePeerIDsFromGroupsIDs(groups []string) []string {
peerIDs := make(map[string]struct{}, len(groups))
for _, groupID := range groups {
group := nmd.Groups[groupID]
if group == nil {
continue
}
if group.IsGroupAll() || len(groups) == 1 {
return group.Peers
}
for _, peerID := range group.Peers {
peerIDs[peerID] = struct{}{}
}
}
ids := make([]string, 0, len(peerIDs))
for peerID := range peerIDs {
ids = append(ids, peerID)
}
return ids
}
func (nmd *NetworkMapData) getAllowedUserIDs() map[string]struct{} {
return nmd.AllowedUserIDs
}
func (nmd *NetworkMapData) appliedZones(peerGroups map[string]struct{}) []nmdata.CustomZone {
if len(peerGroups) == 0 {
return nil
}
var out []nmdata.CustomZone
for _, cand := range nmd.AppliedZoneCandidates {
if peerInDistributionGroups(peerGroups, cand.DistributionGroups) {
out = append(out, cand.Zone)
}
}
return out
}
func (nmd *NetworkMapData) privateServiceZones(peerGroups map[string]struct{}) []nmdata.CustomZone {
byApex := make(map[string]*nmdata.CustomZone)
var order []string
for _, cand := range nmd.PrivateServiceCandidates {
if !peerInDistributionGroups(peerGroups, cand.AccessGroups) {
continue
}
zone, exists := byApex[cand.Zone.Domain]
if !exists {
nz := nmdata.CustomZone{
Domain: cand.Zone.Domain,
SearchDomainDisabled: cand.Zone.SearchDomainDisabled,
NonAuthoritative: cand.Zone.NonAuthoritative,
}
byApex[cand.Zone.Domain] = &nz
zone = &nz
order = append(order, cand.Zone.Domain)
}
zone.Records = append(zone.Records, cand.Zone.Records...)
}
var out []nmdata.CustomZone
for _, apex := range order {
zone := byApex[apex]
if len(zone.Records) == 0 {
continue
}
out = append(out, *zone)
}
return out
}
func peerInDistributionGroups(peerGroups map[string]struct{}, groups []string) bool {
for _, g := range groups {
if _, ok := peerGroups[g]; ok {
return true
}
}
return false
}
func filterGroupPeers(groups *map[string]*nmdata.Group, peers map[string]*nmdata.Peer) {
for groupID, groupInfo := range *groups {
filteredPeers := make([]string, 0, len(groupInfo.Peers))
for _, pid := range groupInfo.Peers {
if _, exists := peers[pid]; exists {
filteredPeers = append(filteredPeers, pid)
}
}
if len(filteredPeers) != len(groupInfo.Peers) {
ng := groupInfo.Copy()
ng.Peers = filteredPeers
(*groups)[groupID] = ng
}
}
}
func filterPostureFailedPeers(postureFailedPeers *map[string]map[string]struct{}, policies []*nmdata.Policy, resourcePoliciesMap map[string][]*nmdata.Policy, peers map[string]*nmdata.Peer) {
if len(*postureFailedPeers) == 0 {
return
}
referencedPostureChecks := make(map[string]struct{})
for _, policy := range policies {
for _, checkID := range policy.SourcePostureChecks {
referencedPostureChecks[checkID] = struct{}{}
}
}
for _, resPolicies := range resourcePoliciesMap {
for _, policy := range resPolicies {
for _, checkID := range policy.SourcePostureChecks {
referencedPostureChecks[checkID] = struct{}{}
}
}
}
for checkID, failedPeers := range *postureFailedPeers {
if _, referenced := referencedPostureChecks[checkID]; !referenced {
delete(*postureFailedPeers, checkID)
continue
}
for peerID := range failedPeers {
if _, exists := peers[peerID]; !exists {
delete(failedPeers, peerID)
}
}
if len(failedPeers) == 0 {
delete(*postureFailedPeers, checkID)
}
}
}
func filterDNSRecordsByPeers(records []nmdata.SimpleRecord, peers map[string]*nmdata.Peer, includeIPv6 bool) []nmdata.SimpleRecord {
if len(records) == 0 || len(peers) == 0 {
return nil
}
peerIPs := make(map[string]struct{}, len(peers)*2)
for _, peer := range peers {
if peer == nil {
continue
}
peerIPs[peer.IP.String()] = struct{}{}
if includeIPv6 && peer.IPv6.IsValid() {
peerIPs[peer.IPv6.String()] = struct{}{}
}
}
filteredRecords := make([]nmdata.SimpleRecord, 0, len(records))
for _, record := range records {
if _, exists := peerIPs[record.RData]; exists {
filteredRecords = append(filteredRecords, record)
}
}
return filteredRecords
}
func filterGroupIDToUserIDs(fullMap map[string][]string, neededGroupIDs map[string]struct{}) map[string][]string {
if len(neededGroupIDs) == 0 {
return nil
}
filtered := make(map[string][]string, len(neededGroupIDs))
for groupID := range neededGroupIDs {
if users, ok := fullMap[groupID]; ok {
filtered[groupID] = users
}
}
return filtered
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
package networkmap
import (
"sync"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
// NetworkMapData is a dependency-light, slim twin of the server Account. It
// carries only the state GetPeerNetworkMapComponents needs, expressed in the
// fresh nmdata twin types. A builder converts an Account into a NetworkMapData
// once per account; the per-peer components calculation then runs on this twin
// with no reference back to the Account.
type NetworkMapData struct { //nolint:revive // established name across the codebase
Peers map[string]*nmdata.Peer
Groups map[string]*nmdata.Group
Policies []*nmdata.Policy
Routes []*nmdata.Route
NameServerGroups []*nmdata.NameServerGroup
NetworkResources []*nmdata.NetworkResource
Network *nmdata.Network
DNSSettings *nmdata.DNSSettings
AccountSettings *nmdata.AccountSettingsInfo
PostureChecks map[string]*nmdata.PostureChecks
// PostureValidation holds the precomputed posture-check results, keyed by
// posture check ID then peer ID. Filled by PrecomputePostureValidation; a
// present but nil inner map marks a check ID that resolves to no posture
// check, which the calc treats as passing.
PostureValidation map[string]map[string]bool
AllowedUserIDs map[string]struct{}
NetworkXIDToPublicID map[string]string
PostureCheckXIDToPublicID map[string]string
ValidatedPeers map[string]struct{}
ResourcePolicies map[string][]*nmdata.Policy
Routers map[string]map[string]*nmdata.NetworkRouter
GroupIDToUserIDs map[string][]string
DNSDomain string
// ProxyTargetedDomainResourceIDs is the account-level half of
// forcesRoutingPeerDNSResolution: domain network resources targeted by an
// enabled reverse-proxy service.
ProxyTargetedDomainResourceIDs map[string]struct{}
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
// per-peer decision the components calc still makes: include the zone only when
// the peer belongs to one of its distribution groups. Record conversion is done
// once at build time.
type AppliedZoneCandidate struct {
DistributionGroups []string
Zone nmdata.CustomZone
}
// PrivateServiceCandidate is a single private service's synthesized records,
// carried per apex zone. The builder resolves proxy-cluster connectivity and
// domain-suffix matching once; the calc merges the candidates whose AccessGroups
// the peer belongs to, grouped by Zone.Domain.
type PrivateServiceCandidate struct {
AccessGroups []string
Zone nmdata.CustomZone
}

View File

@@ -0,0 +1,18 @@
package nmdata
import "time"
// AccountSettingsInfo is the slim twin of types.AccountSettingsInfo.
type AccountSettingsInfo struct {
PeerLoginExpirationEnabled bool
PeerLoginExpiration time.Duration
PeerInactivityExpirationEnabled bool
PeerInactivityExpiration time.Duration
DNSDomain string
IPv6EnabledGroups []string
RoutingPeerDNSResolutionEnabled bool
LazyConnectionEnabled bool
AutoUpdateVersion string
AutoUpdateAlways bool
MetricsPushEnabled bool
}

View File

@@ -0,0 +1,18 @@
package nmdata
// SimpleRecord is the slim twin of dns.SimpleRecord.
type SimpleRecord struct {
Name string
Type int
Class string
TTL int
RData string
}
// CustomZone is the slim twin of dns.CustomZone.
type CustomZone struct {
Domain string
Records []SimpleRecord
SearchDomainDisabled bool
NonAuthoritative bool
}

View File

@@ -0,0 +1,6 @@
package nmdata
// DNSSettings is the slim twin of types.DNSSettings.
type DNSSettings struct {
DisabledManagementGroups []string
}

View File

@@ -0,0 +1,30 @@
package nmdata
import "slices"
// GroupAllName is the reserved name of the default group that contains every
// peer in an account.
const GroupAllName = "All"
// Group is the slim twin of types.Group.
type Group struct {
ID string
Name string
PublicID string
Peers []string
Resources []Resource
}
func (g *Group) IsGroupAll() bool {
return g.Name == GroupAllName
}
func (g *Group) Copy() *Group {
return &Group{
ID: g.ID,
Name: g.Name,
PublicID: g.PublicID,
Peers: slices.Clone(g.Peers),
Resources: slices.Clone(g.Resources),
}
}

View File

@@ -0,0 +1,84 @@
package nmdata
import (
"reflect"
"testing"
)
// TestGroupCopy_AllFieldsCopied fills every Group field with a unique non-zero
// value derived from its field path, so a field added to Group but forgotten
// in Copy fails here by name without the test needing an update. The unique
// per-path values also catch fields swapped inside Copy.
func TestGroupCopy_AllFieldsCopied(t *testing.T) {
src := &Group{}
seed := 0
fillValue(t, reflect.ValueOf(src).Elem(), "Group", &seed)
copied := src.Copy()
srcV := reflect.ValueOf(src).Elem()
copiedV := reflect.ValueOf(copied).Elem()
for i := 0; i < srcV.NumField(); i++ {
name := srcV.Type().Field(i).Name
if !reflect.DeepEqual(srcV.Field(i).Interface(), copiedV.Field(i).Interface()) {
t.Errorf("field %s not copied: src=%#v copy=%#v",
name, srcV.Field(i).Interface(), copiedV.Field(i).Interface())
}
}
for i := 0; i < srcV.NumField(); i++ {
f := srcV.Field(i)
if f.Kind() != reflect.Slice || f.Len() == 0 {
continue
}
name := srcV.Type().Field(i).Name
fillValue(t, f.Index(0), name+"-mutated", &seed)
if reflect.DeepEqual(f.Interface(), copiedV.Field(i).Interface()) {
t.Errorf("field %s shares memory with the copy", name)
}
}
}
// fillValue sets v to a deterministic non-zero value derived from its field
// path. Kinds it does not handle fail the test loudly, so the filler is
// extended together with the struct instead of silently under-testing new
// fields.
func fillValue(t *testing.T, v reflect.Value, path string, seed *int) {
t.Helper()
switch v.Kind() {
case reflect.String:
v.SetString(path)
case reflect.Bool:
v.SetBool(true)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
*seed++
v.SetInt(int64(*seed))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
*seed++
v.SetUint(uint64(*seed))
case reflect.Float32, reflect.Float64:
*seed++
v.SetFloat(float64(*seed))
case reflect.Slice:
s := reflect.MakeSlice(v.Type(), 2, 2)
fillValue(t, s.Index(0), path+"[0]", seed)
fillValue(t, s.Index(1), path+"[1]", seed)
v.Set(s)
case reflect.Struct:
settable := 0
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if !f.CanSet() {
continue
}
settable++
fillValue(t, f, path+"."+v.Type().Field(i).Name, seed)
}
if settable == 0 {
t.Fatalf("struct %s at %s has no settable fields — extend fillValue to construct it", v.Type(), path)
}
default:
t.Fatalf("unsupported kind %s at %s — extend fillValue", v.Kind(), path)
}
}

View File

@@ -0,0 +1,24 @@
package nmdata
import "net/netip"
// NameServerGroup is the slim twin of dns.NameServerGroup.
type NameServerGroup struct {
ID string
PublicID string
Name string
Description string
NameServers []NameServer
Groups []string
Primary bool
Domains []string
Enabled bool
SearchDomainsEnabled bool
}
// NameServer is the slim twin of dns.NameServer.
type NameServer struct {
IP netip.Addr
NSType int
Port int
}

View File

@@ -0,0 +1,16 @@
package nmdata
import "net"
// Network is the slim twin of types.Network.
type Network struct {
Identifier string
Net net.IPNet
NetV6 net.IPNet
Dns string
Serial int64
}
func (n *Network) CurrentSerial() uint64 {
return uint64(n.Serial)
}

View File

@@ -0,0 +1,18 @@
package nmdata
import "net/netip"
// NetworkResource is the slim twin of resources/types.NetworkResource.
type NetworkResource struct {
ID string
NetworkID string
AccountID string
PublicID string
Name string
Description string
Type string
Address string // TODO: isn't persisted in the DB
Domain string
Prefix netip.Prefix
Enabled bool
}

View File

@@ -0,0 +1,10 @@
package nmdata
// NetworkRouter is the slim twin of routers/types.NetworkRouter.
type NetworkRouter struct {
PublicID string
PeerGroups []string
Masquerade bool
Metric int
Enabled bool
}

View File

@@ -0,0 +1,129 @@
package nmdata
import (
"net"
"net/netip"
"slices"
"time"
)
// Peer capability constants mirror the proto enum values.
const (
PeerCapabilitySourcePrefixes int32 = 1
PeerCapabilityIPv6Overlay int32 = 2
PeerCapabilityComponentNetworkMap int32 = 3
)
// Peer is the slim twin of peer.Peer.
type Peer struct {
ID string
Key string
SSHKey string
DNSLabel string
UserID string
SSHEnabled bool
LoginExpirationEnabled bool
LastLogin *time.Time
IP netip.Addr
IPv6 netip.Addr
RequiresApproval bool
ExtraDNSLabels []string
Meta PeerSystemMeta
ProxyMeta ProxyMeta
Location PeerLocation
}
// ProxyMeta is the slim twin of peer.ProxyMeta.
type ProxyMeta struct {
Embedded bool
Cluster string
}
// PeerSystemMeta is the slim twin of peer.PeerSystemMeta.
type PeerSystemMeta struct {
WtVersion string
GoOS string
OSVersion string
KernelVersion string
NetworkAddresses []NetworkAddress
Files []File
Capabilities []int32
Flags Flags
SyncMessageVersion int
}
// Flags is the slim twin of peer.Flags.
type Flags struct {
ServerSSHAllowed bool
DisableIPv6 bool
}
// NetworkAddress is the slim twin of peer.NetworkAddress.
type NetworkAddress struct {
NetIP netip.Prefix
}
// File is the slim twin of peer.File.
type File struct {
Path string
ProcessIsRunning bool
}
// PeerLocation is the slim twin of peer.Location.
type PeerLocation struct {
CountryCode string
CityName string
ConnectionIP net.IP
}
func (p *Peer) HasCapability(capability int32) bool {
return slices.Contains(p.Meta.Capabilities, capability)
}
func (p *Peer) SupportsIPv6() bool {
return !p.Meta.Flags.DisableIPv6 && p.HasCapability(PeerCapabilityIPv6Overlay)
}
func (p *Peer) SupportsSourcePrefixes() bool {
return p.HasCapability(PeerCapabilitySourcePrefixes)
}
func (p *Peer) AddedWithSSOLogin() bool {
return p.UserID != ""
}
func (p *Peer) FQDN(dnsDomain string) string {
if dnsDomain == "" {
return ""
}
return p.DNSLabel + "." + dnsDomain
}
func (p *Peer) GetLastLogin() time.Time {
if p.LastLogin != nil {
return *p.LastLogin
}
return time.Time{}
}
// SessionExpiresAt mirrors peer.Peer.SessionExpiresAt.
func (p *Peer) SessionExpiresAt(accountExpirationEnabled bool, expiresIn time.Duration) time.Time {
if !accountExpirationEnabled || !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
return time.Time{}
}
last := p.GetLastLogin()
if last.IsZero() {
return time.Time{}
}
return last.Add(expiresIn).UTC()
}
func (p *Peer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) {
if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
return false, 0
}
expiresAt := p.GetLastLogin().Add(expiresIn)
now := time.Now()
timeLeft := expiresAt.Sub(now)
return timeLeft <= 0, timeLeft
}

View File

@@ -0,0 +1,96 @@
package nmdata
const (
policyRuleProtocolALL = "all"
policyRuleProtocolTCP = "tcp"
defaultSSHPortString = "22"
nativeSSHPortString = "22022"
defaultSSHPortNumber uint16 = 22
nativeSSHPortNumber uint16 = 22022
)
// Policy is the slim twin of types.Policy.
type Policy struct {
ID string
PublicID string
Enabled bool
SourcePostureChecks []string
Rules []*PolicyRule
}
// PolicyRule is the slim twin of types.PolicyRule.
type PolicyRule struct {
ID string
PolicyID string
Enabled bool
Action string
Protocol string
Bidirectional bool
Sources []string
Destinations []string
SourceResource Resource
DestinationResource Resource
Ports []string
PortRanges []RulePortRange
AuthorizedGroups map[string][]string
AuthorizedUser string
}
// RulePortRange is the slim twin of types.RulePortRange.
type RulePortRange struct {
Start uint16
End uint16
}
// Resource is the slim twin of types.Resource.
type Resource struct {
ID string
Type string
}
func (p *Policy) SourceGroups() []string {
if len(p.Rules) == 1 && p.Rules[0] != nil {
return p.Rules[0].Sources
}
groups := make(map[string]struct{}, len(p.Rules))
for _, rule := range p.Rules {
if rule == nil {
continue
}
for _, source := range rule.Sources {
groups[source] = struct{}{}
}
}
groupIDs := make([]string, 0, len(groups))
for groupID := range groups {
groupIDs = append(groupIDs, groupID)
}
return groupIDs
}
// PolicyRuleImpliesLegacySSH is the twin-typed sibling of types.PolicyRuleImpliesLegacySSH.
func PolicyRuleImpliesLegacySSH(rule *PolicyRule) bool {
return rule.Protocol == policyRuleProtocolALL ||
(rule.Protocol == policyRuleProtocolTCP && (portsIncludesSSH(rule.Ports) || portRangeIncludesSSH(rule.PortRanges)))
}
func portRangeIncludesSSH(portRanges []RulePortRange) bool {
for _, pr := range portRanges {
if (pr.Start <= defaultSSHPortNumber && pr.End >= defaultSSHPortNumber) || (pr.Start <= nativeSSHPortNumber && pr.End >= nativeSSHPortNumber) {
return true
}
}
return false
}
func portsIncludesSSH(ports []string) bool {
for _, port := range ports {
if port == defaultSSHPortString || port == nativeSSHPortString {
return true
}
}
return false
}

View File

@@ -0,0 +1,67 @@
package nmdata
const (
checkActionAllow = "allow"
checkActionDeny = "deny"
)
// PostureChecks is the slim twin of posture.Checks.
type PostureChecks struct {
ID string
Checks ChecksDefinition
}
// ChecksDefinition is the slim twin of posture.ChecksDefinition.
type ChecksDefinition struct {
NBVersionCheck *NBVersionCheck
OSVersionCheck *OSVersionCheck
GeoLocationCheck *GeoLocationCheck
PeerNetworkRangeCheck *PeerNetworkRangeCheck
ProcessCheck *ProcessCheck
}
// Check is the slim twin of posture.Check. It is sealed: only the check types
// in this package implement it.
type Check interface {
check(peer *Peer) (bool, error)
}
// Passes reports whether the peer satisfies every check in this bundle. It
// mirrors the server posture path: a check returning (false, _) — including on
// an evaluation error — fails the bundle.
func (pc *PostureChecks) Passes(peer *Peer) bool {
return PassesChecks(pc.GetChecks(), peer)
}
// PassesChecks is Passes over an already built check set, for callers that
// evaluate many peers against the same bundle.
func PassesChecks(checks []Check, peer *Peer) bool {
for _, c := range checks {
valid, _ := c.check(peer)
if !valid {
return false
}
}
return true
}
// GetChecks returns the initialized checks in the same order as posture.Checks.GetChecks.
func (pc *PostureChecks) GetChecks() []Check {
var checks []Check
if pc.Checks.NBVersionCheck != nil {
checks = append(checks, pc.Checks.NBVersionCheck)
}
if pc.Checks.OSVersionCheck != nil {
checks = append(checks, pc.Checks.OSVersionCheck)
}
if pc.Checks.GeoLocationCheck != nil {
checks = append(checks, pc.Checks.GeoLocationCheck)
}
if pc.Checks.PeerNetworkRangeCheck != nil {
checks = append(checks, pc.Checks.PeerNetworkRangeCheck)
}
if pc.Checks.ProcessCheck != nil {
checks = append(checks, pc.Checks.ProcessCheck)
}
return checks
}

View File

@@ -0,0 +1,45 @@
package nmdata
import "fmt"
// GeoLocation is the slim twin of posture.Location.
type GeoLocation struct {
CountryCode string
CityName string
}
// GeoLocationCheck is the slim twin of posture.GeoLocationCheck.
type GeoLocationCheck struct {
Locations []GeoLocation
Action string
}
func (g *GeoLocationCheck) check(peer *Peer) (bool, error) {
if peer.Location.CountryCode == "" && peer.Location.CityName == "" {
return false, fmt.Errorf("peer's location is not set")
}
for _, loc := range g.Locations {
if loc.CountryCode == peer.Location.CountryCode {
if loc.CityName == "" || loc.CityName == peer.Location.CityName {
switch g.Action {
case checkActionDeny:
return false, nil
case checkActionAllow:
return true, nil
default:
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
}
}
}
}
if g.Action == checkActionDeny {
return true, nil
}
if g.Action == checkActionAllow {
return false, nil
}
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
}

View File

@@ -0,0 +1,38 @@
package nmdata
import (
"strings"
"github.com/hashicorp/go-version"
)
// NBVersionCheck is the slim twin of posture.NBVersionCheck.
type NBVersionCheck struct {
MinVersion string
}
func (n *NBVersionCheck) check(peer *Peer) (bool, error) {
return meetsMinVersion(n.MinVersion, peer.Meta.WtVersion)
}
func meetsMinVersion(minVer, peerVer string) (bool, error) {
peerVer = sanitizeVersion(peerVer)
minVer = sanitizeVersion(minVer)
peerNBVer, err := version.NewVersion(peerVer)
if err != nil {
return false, err
}
constraints, err := version.NewConstraint(">= " + minVer)
if err != nil {
return false, err
}
return constraints.Check(peerNBVer), nil
}
func sanitizeVersion(v string) string {
parts := strings.Split(v, "-")
return parts[0]
}

View File

@@ -0,0 +1,62 @@
package nmdata
import (
"fmt"
"net/netip"
)
// PeerNetworkRangeCheck is the slim twin of posture.PeerNetworkRangeCheck.
type PeerNetworkRangeCheck struct {
Action string
Ranges []netip.Prefix
}
func (p *PeerNetworkRangeCheck) check(peer *Peer) (bool, error) {
peerPrefixes := make([]netip.Prefix, 0, len(peer.Meta.NetworkAddresses)+1)
for _, peerNetAddr := range peer.Meta.NetworkAddresses {
peerPrefixes = append(peerPrefixes, peerNetAddr.NetIP)
}
if connIP := peer.Location.ConnectionIP; len(connIP) > 0 {
if addr, ok := netip.AddrFromSlice(connIP); ok {
addr = addr.Unmap()
peerPrefixes = append(peerPrefixes, netip.PrefixFrom(addr, addr.BitLen()))
}
}
if len(peerPrefixes) == 0 {
return false, fmt.Errorf("peer's does not contain peer network range addresses")
}
for _, peerPrefix := range peerPrefixes {
for _, rangePrefix := range p.Ranges {
if !prefixContains(rangePrefix, peerPrefix) {
continue
}
switch p.Action {
case checkActionDeny:
return false, nil
case checkActionAllow:
return true, nil
default:
return false, fmt.Errorf("invalid peer network range check action: %s", p.Action)
}
}
}
if p.Action == checkActionDeny {
return true, nil
}
if p.Action == checkActionAllow {
return false, nil
}
return false, fmt.Errorf("invalid peer network range check action: %s", p.Action)
}
func prefixContains(outer, inner netip.Prefix) bool {
outer = outer.Masked()
inner = inner.Masked()
return outer.Bits() <= inner.Bits() &&
outer.Addr().BitLen() == inner.Addr().BitLen() &&
outer.Contains(inner.Addr())
}

View File

@@ -0,0 +1,79 @@
package nmdata
import (
"strings"
"github.com/hashicorp/go-version"
)
// MinVersionCheck is the slim twin of posture.MinVersionCheck.
type MinVersionCheck struct {
MinVersion string
}
// MinKernelVersionCheck is the slim twin of posture.MinKernelVersionCheck.
type MinKernelVersionCheck struct {
MinKernelVersion string
}
// OSVersionCheck is the slim twin of posture.OSVersionCheck.
type OSVersionCheck struct {
Android *MinVersionCheck
Darwin *MinVersionCheck
Ios *MinVersionCheck
Linux *MinKernelVersionCheck
Windows *MinKernelVersionCheck
}
func (c *OSVersionCheck) check(peer *Peer) (bool, error) {
switch peer.Meta.GoOS {
case "android":
return checkMinVersion(peer.Meta.OSVersion, c.Android)
case "darwin":
return checkMinVersion(peer.Meta.OSVersion, c.Darwin)
case "ios":
return checkMinVersion(peer.Meta.OSVersion, c.Ios)
case "linux":
kernelVersion := strings.Split(peer.Meta.KernelVersion, "-")[0]
return checkMinKernelVersion(kernelVersion, c.Linux)
case "windows":
return checkMinKernelVersion(peer.Meta.KernelVersion, c.Windows)
}
return true, nil
}
func checkMinVersion(peerVersion string, check *MinVersionCheck) (bool, error) {
if check == nil {
return false, nil
}
peerNBVersion, err := version.NewVersion(peerVersion)
if err != nil {
return false, err
}
constraints, err := version.NewConstraint(">= " + check.MinVersion)
if err != nil {
return false, err
}
return constraints.Check(peerNBVersion), nil
}
func checkMinKernelVersion(peerVersion string, check *MinKernelVersionCheck) (bool, error) {
if check == nil {
return false, nil
}
peerNBVersion, err := version.NewVersion(peerVersion)
if err != nil {
return false, err
}
constraints, err := version.NewConstraint(">= " + check.MinKernelVersion)
if err != nil {
return false, err
}
return constraints.Check(peerNBVersion), nil
}

View File

@@ -0,0 +1,56 @@
package nmdata
import (
"fmt"
"slices"
)
// Process is the slim twin of posture.Process.
type Process struct {
LinuxPath string
MacPath string
WindowsPath string
}
// ProcessCheck is the slim twin of posture.ProcessCheck.
type ProcessCheck struct {
Processes []Process
}
func (p *ProcessCheck) check(peer *Peer) (bool, error) {
peerActiveProcesses := extractPeerActiveProcesses(peer.Meta.Files)
var pathSelector func(Process) string
switch peer.Meta.GoOS {
case "linux":
pathSelector = func(process Process) string { return process.LinuxPath }
case "darwin":
pathSelector = func(process Process) string { return process.MacPath }
case "windows":
pathSelector = func(process Process) string { return process.WindowsPath }
default:
return false, fmt.Errorf("unsupported peer's operating system: %s", peer.Meta.GoOS)
}
return p.areAllProcessesRunning(peerActiveProcesses, pathSelector), nil
}
func (p *ProcessCheck) areAllProcessesRunning(activeProcesses []string, pathSelector func(Process) string) bool {
for _, process := range p.Processes {
path := pathSelector(process)
if path == "" || !slices.Contains(activeProcesses, path) {
return false
}
}
return true
}
func extractPeerActiveProcesses(files []File) []string {
activeProcesses := make([]string, 0, len(files))
for _, file := range files {
if file.ProcessIsRunning {
activeProcesses = append(activeProcesses, file.Path)
}
}
return activeProcesses
}

View File

@@ -0,0 +1,108 @@
package nmdata
import (
"net/netip"
"slices"
"strings"
"github.com/netbirdio/netbird/shared/management/domain"
)
// NetworkType mirrors route.NetworkType iota values.
const (
NetworkTypeInvalid = 0
NetworkTypeIPv4 = 1
NetworkTypeIPv6 = 2
NetworkTypeDomain = 3
haSeparator = "|"
)
// Route is the slim twin of route.Route.
type Route struct {
ID string
AccountID string
PublicID string
Network netip.Prefix
Domains domain.List
KeepRoute bool
NetID string
Description string
Peer string
PeerID string
PeerGroups []string
NetworkType int
Masquerade bool
Metric int
Enabled bool
Groups []string
AccessControlGroups []string
SkipAutoApply bool
}
func (r *Route) Equal(other *Route) bool {
if r == nil && other == nil {
return true
} else if r == nil || other == nil {
return false
}
return other.ID == r.ID &&
other.Description == r.Description &&
other.NetID == r.NetID &&
other.Network == r.Network &&
slices.Equal(r.Domains, other.Domains) &&
other.KeepRoute == r.KeepRoute &&
other.NetworkType == r.NetworkType &&
other.Peer == r.Peer &&
other.PeerID == r.PeerID &&
other.Metric == r.Metric &&
other.Masquerade == r.Masquerade &&
other.Enabled == r.Enabled &&
slices.Equal(r.Groups, other.Groups) &&
slices.Equal(r.PeerGroups, other.PeerGroups) &&
slices.Equal(r.AccessControlGroups, other.AccessControlGroups) &&
other.SkipAutoApply == r.SkipAutoApply
}
func (r *Route) IsDynamic() bool {
return r.NetworkType == NetworkTypeDomain
}
func (r *Route) NetString() string {
if r.IsDynamic() && r.Domains != nil {
return r.Domains.SafeString()
}
return r.Network.String()
}
func (r *Route) GetHAUniqueID() string {
return r.NetID + haSeparator + r.NetString()
}
func (r *Route) GetResourceID() string {
return strings.Split(r.ID, ":")[0]
}
func (r *Route) Copy() *Route {
return &Route{
ID: r.ID,
AccountID: r.AccountID,
PublicID: r.PublicID,
Network: r.Network,
Domains: slices.Clone(r.Domains),
KeepRoute: r.KeepRoute,
NetID: r.NetID,
Description: r.Description,
Peer: r.Peer,
PeerID: r.PeerID,
PeerGroups: slices.Clone(r.PeerGroups),
NetworkType: r.NetworkType,
Masquerade: r.Masquerade,
Metric: r.Metric,
Enabled: r.Enabled,
Groups: slices.Clone(r.Groups),
AccessControlGroups: slices.Clone(r.AccessControlGroups),
SkipAutoApply: r.SkipAutoApply,
}
}

View 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
}

View File

@@ -0,0 +1,111 @@
package networkmap
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
const peersZoneRecordTTL = 300
// PeersCustomZone builds the peers DNS zone from twin peer rows. It is the
// single source of the zone-record logic; Account.GetPeersCustomZone delegates
// here via twins.
func PeersCustomZone(ctx context.Context, accountID string, dnsDomain string, peers map[string]*nmdata.Peer, ipv6AllowedPeers map[string]struct{}) nmdata.CustomZone {
var merr *multierror.Error
if dnsDomain == "" {
log.WithContext(ctx).Error("no dns domain is set, returning empty zone")
return nmdata.CustomZone{}
}
customZone := nmdata.CustomZone{
Domain: dns.Fqdn(dnsDomain),
Records: make([]nmdata.SimpleRecord, 0, len(peers)),
}
domainSuffix := "." + dnsDomain
var sb strings.Builder
for _, peer := range peers {
if peer == nil {
continue
}
if peer.DNSLabel == "" {
merr = multierror.Append(merr, fmt.Errorf("peer %s has an empty DNS label", peer.ID))
continue
}
sb.Grow(len(peer.DNSLabel) + len(domainSuffix))
sb.WriteString(peer.DNSLabel)
sb.WriteString(domainSuffix)
fqdn := sb.String()
customZone.Records = append(customZone.Records, nmdata.SimpleRecord{
Name: fqdn,
Type: int(dns.TypeA),
Class: nbdns.DefaultClass,
TTL: peersZoneRecordTTL,
RData: peer.IP.String(),
})
// Only advertise AAAA for peers that have a valid IPv6, whose client supports it,
// and that belong to an IPv6-enabled group. Old clients don't configure v6 on their
// WireGuard interface, so resolving their AAAA causes connections to hang.
// Capability changes (client upgrade/downgrade, --disable-ipv6 toggle) propagate
// to other peers via SyncPeer/LoginPeer regardless of version change, so AAAA
// records refresh when a peer first reports the IPv6 overlay capability.
_, peerAllowed := ipv6AllowedPeers[peer.ID]
hasIPv6 := peer.IPv6.IsValid() && peer.SupportsIPv6() && peerAllowed
if hasIPv6 {
customZone.Records = append(customZone.Records, nmdata.SimpleRecord{
Name: fqdn,
Type: int(dns.TypeAAAA),
Class: nbdns.DefaultClass,
TTL: peersZoneRecordTTL,
RData: peer.IPv6.String(),
})
}
sb.Reset()
for _, extraLabel := range peer.ExtraDNSLabels {
sb.Grow(len(extraLabel) + len(domainSuffix))
sb.WriteString(extraLabel)
sb.WriteString(domainSuffix)
extraFqdn := sb.String()
customZone.Records = append(customZone.Records, nmdata.SimpleRecord{
Name: extraFqdn,
Type: int(dns.TypeA),
Class: nbdns.DefaultClass,
TTL: peersZoneRecordTTL,
RData: peer.IP.String(),
})
if hasIPv6 {
customZone.Records = append(customZone.Records, nmdata.SimpleRecord{
Name: extraFqdn,
Type: int(dns.TypeAAAA),
Class: nbdns.DefaultClass,
TTL: peersZoneRecordTTL,
RData: peer.IPv6.String(),
})
}
sb.Reset()
}
}
go func() {
if merr != nil {
log.WithContext(ctx).Errorf("error generating custom zone for account %s: %v", accountID, merr)
}
}()
return customZone
}

View File

@@ -0,0 +1,209 @@
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,
// The envelope encoder puts public ids on the wire and degrades to an
// empty one when a policy has none. A synthesised policy has no
// persisted row to take a public id from, and its own id is already
// stable and unique, so it serves as both.
PublicID: 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,
PublicID: 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
}

View File

@@ -5819,8 +5819,6 @@ func (x *PolicyCompact) GetSourcePostureCheckIds() []string {
// ResourceCompact mirrors types.Resource. Used by PolicyCompact to carry
// rule.SourceResource / rule.DestinationResource when the rule targets a
// specific resource (typically a peer) rather than groups.
// peer_index_set tells whether peer_index is valid (proto3 uint32 cannot
// disambiguate "0" from "unset"); set only when type == "peer".
type ResourceCompact struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -5829,6 +5827,7 @@ type ResourceCompact struct {
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
PeerIndexSet bool `protobuf:"varint,2,opt,name=peer_index_set,json=peerIndexSet,proto3" json:"peer_index_set,omitempty"`
PeerIndex uint32 `protobuf:"varint,3,opt,name=peer_index,json=peerIndex,proto3" json:"peer_index,omitempty"`
Id string `protobuf:"bytes,5,opt,name=id,proto3" json:"id,omitempty"` // public id for domain/host/subnet resources
}
func (x *ResourceCompact) Reset() {
@@ -5884,6 +5883,13 @@ func (x *ResourceCompact) GetPeerIndex() uint32 {
return 0
}
func (x *ResourceCompact) GetId() string {
if x != nil {
return x.Id
}
return ""
}
// UserNameList is a list of local-user names — used as the value type in
// PolicyCompact.authorized_groups.
type UserNameList struct {
@@ -5949,7 +5955,8 @@ type GroupCompact struct {
// groups exactly like the server does; without this bit the decoded
// groups lose that property and the two sides expand policy
// destinations differently.
IsAll bool `protobuf:"varint,3,opt,name=is_all,json=isAll,proto3" json:"is_all,omitempty"`
IsAll bool `protobuf:"varint,3,opt,name=is_all,json=isAll,proto3" json:"is_all,omitempty"`
Resources []*ResourceCompact `protobuf:"bytes,4,rep,name=resources,proto3" json:"resources,omitempty"`
}
func (x *GroupCompact) Reset() {
@@ -6005,6 +6012,13 @@ func (x *GroupCompact) GetIsAll() bool {
return false
}
func (x *GroupCompact) GetResources() []*ResourceCompact {
if x != nil {
return x.Resources
}
return nil
}
// DNSSettingsCompact mirrors types.DNSSettings.
type DNSSettingsCompact struct {
state protoimpl.MessageState
@@ -7726,216 +7740,221 @@ var file_management_proto_rawDesc = []byte{
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d,
0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x22, 0x70, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6d, 0x70,
0x61, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f,
0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0c, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4a, 0x04, 0x08, 0x04,
0x10, 0x05, 0x22, 0x24, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x4c, 0x69,
0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0c, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0b,
0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x69,
0x73, 0x5f, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x41,
0x6c, 0x6c, 0x22, 0x57, 0x0a, 0x12, 0x44, 0x4e, 0x53, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
0x73, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x41, 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x22, 0x8d, 0x04, 0x0a, 0x08,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x61, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x65, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12,
0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x63, 0x69, 0x64,
0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x43, 0x69, 0x64, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18,
0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1d,
0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01,
0x28, 0x08, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x24, 0x0a,
0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x18,
0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x53, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65,
0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70,
0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6d,
0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0a, 0x6d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x65, 0x74,
0x72, 0x69, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0d,
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a,
0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09,
0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x63,
0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x67, 0x72, 0x6f,
0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x61, 0x63,
0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x49, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x75, 0x74, 0x6f,
0x5f, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b,
0x69, 0x70, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x22, 0xff, 0x01, 0x0a, 0x12,
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52,
0x61, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x12, 0x38, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52,
0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x0a, 0x09,
0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69,
0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x6d,
0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x05,
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a,
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x87, 0x02,
0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x52, 0x61, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f,
0x73, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x53, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x43, 0x69, 0x64, 0x72, 0x12, 0x18, 0x0a,
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x4d, 0x0a, 0x11, 0x4e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x07,
0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65,
0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a,
0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x24, 0x0a, 0x0e,
0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x03,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53,
0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70,
0x5f, 0x69, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x61, 0x73, 0x71,
0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61,
0x22, 0x80, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6d,
0x70, 0x61, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74, 0x12, 0x1d,
0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x4a, 0x04, 0x08,
0x04, 0x10, 0x05, 0x22, 0x24, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x4c,
0x69, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x0c, 0x47, 0x72,
0x6f, 0x75, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65,
0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d,
0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x15, 0x0a,
0x06, 0x69, 0x73, 0x5f, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69,
0x73, 0x41, 0x6c, 0x6c, 0x12, 0x39, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6d,
0x70, 0x61, 0x63, 0x74, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22,
0x57, 0x0a, 0x12, 0x44, 0x4e, 0x53, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x6f,
0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x41, 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x67, 0x72, 0x6f,
0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1a, 0x64, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x22, 0x8d, 0x04, 0x0a, 0x08, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x52, 0x61, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b,
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21,
0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x69, 0x64,
0x72, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03,
0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6b,
0x65, 0x65, 0x70, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
0x09, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65,
0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74,
0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12,
0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64,
0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x47, 0x72, 0x6f,
0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x61, 0x73, 0x71,
0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61,
0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72,
0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28,
0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x1d, 0x0a, 0x09, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x49, 0x64, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x27, 0x0a, 0x0a, 0x55, 0x73, 0x65,
0x72, 0x49, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49,
0x64, 0x73, 0x22, 0x31, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53,
0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x65, 0x73, 0x2a, 0x3a, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65,
0x64, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10,
0x02, 0x2a, 0x93, 0x01, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69,
0x6c, 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61,
0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12,
0x20, 0x0a, 0x1c, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,
0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x10,
0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x79, 0x49, 0x50, 0x76, 0x36, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x10, 0x02,
0x12, 0x25, 0x0a, 0x21, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69,
0x74, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x4d, 0x61, 0x70, 0x10, 0x03, 0x2a, 0x48, 0x0a, 0x09, 0x4c, 0x61, 0x7a, 0x79, 0x53,
0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x61, 0x7a, 0x79, 0x53, 0x74, 0x61, 0x74,
0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x4c, 0x61,
0x7a, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x7a, 0x79, 0x10, 0x01, 0x12, 0x12, 0x0a,
0x0e, 0x4c, 0x61, 0x7a, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x61, 0x67, 0x65, 0x72, 0x10,
0x02, 0x2a, 0x5d, 0x0a, 0x0c, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07,
0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02,
0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d,
0x50, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x12,
0x0f, 0x0a, 0x0b, 0x4e, 0x45, 0x54, 0x42, 0x49, 0x52, 0x44, 0x5f, 0x53, 0x53, 0x48, 0x10, 0x06,
0x2a, 0x20, 0x0a, 0x0d, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54,
0x10, 0x01, 0x2a, 0x22, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04,
0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x2a, 0x63, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x50, 0x4f,
0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x50,
0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45,
0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x45,
0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x45,
0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x32, 0xd0, 0x07, 0x0a, 0x11,
0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63,
0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c,
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01,
0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79,
0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68,
0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74,
0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x69, 0x63, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28,
0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72,
0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x63, 0x63, 0x65, 0x73,
0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f,
0x69, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x61, 0x63, 0x63, 0x65, 0x73,
0x73, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x73,
0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x61, 0x70,
0x70, 0x6c, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x41,
0x75, 0x74, 0x6f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x22, 0xff, 0x01, 0x0a, 0x12, 0x4e, 0x61, 0x6d,
0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x61, 0x77, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x38, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x6e, 0x61,
0x6d, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f,
0x75, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x67, 0x72,
0x6f, 0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72,
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28,
0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e,
0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x64,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x07,
0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x87, 0x02, 0x0a, 0x12, 0x4e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x61,
0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x71,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53,
0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x6f,
0x6d, 0x61, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65,
0x66, 0x69, 0x78, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x43, 0x69, 0x64, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e,
0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x22, 0x4d, 0x0a, 0x11, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x07, 0x65, 0x6e, 0x74,
0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72,
0x69, 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65,
0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09,
0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65,
0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74, 0x12,
0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64,
0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x47, 0x72, 0x6f,
0x75, 0x70, 0x49, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72,
0x61, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61, 0x73, 0x71, 0x75,
0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18,
0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x18, 0x0a,
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x1d, 0x0a, 0x09, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x49, 0x64, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x27, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44,
0x4c, 0x69, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22,
0x31, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x65, 0x73, 0x2a, 0x3a, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x12, 0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64,
0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x2a, 0x93,
0x01, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,
0x79, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x79, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c,
0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x10, 0x01, 0x12, 0x1d,
0x0a, 0x19, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
0x49, 0x50, 0x76, 0x36, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x10, 0x02, 0x12, 0x25, 0x0a,
0x21, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x43,
0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d,
0x61, 0x70, 0x10, 0x03, 0x2a, 0x48, 0x0a, 0x09, 0x4c, 0x61, 0x7a, 0x79, 0x53, 0x74, 0x61, 0x74,
0x65, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x61, 0x7a, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x44, 0x65,
0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x4c, 0x61, 0x7a, 0x79, 0x53,
0x74, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x7a, 0x79, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x61,
0x7a, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x61, 0x67, 0x65, 0x72, 0x10, 0x02, 0x2a, 0x5d,
0x0a, 0x0c, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b,
0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41,
0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a,
0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04,
0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b,
0x4e, 0x45, 0x54, 0x42, 0x49, 0x52, 0x44, 0x5f, 0x53, 0x53, 0x48, 0x10, 0x06, 0x2a, 0x20, 0x0a,
0x0d, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06,
0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x2a,
0x22, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a,
0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f,
0x50, 0x10, 0x01, 0x2a, 0x63, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f,
0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45,
0x5f, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f,
0x53, 0x45, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f,
0x53, 0x45, 0x5f, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f,
0x53, 0x45, 0x5f, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x32, 0xd0, 0x07, 0x0a, 0x11, 0x4d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45,
0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45,
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f,
0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a,
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12,
0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61,
0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b,
0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a,
0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11,
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76,
0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74,
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c,
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08,
0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a,
0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
0x28, 0x01, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x11, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41, 0x75,
0x74, 0x68, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c,
0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0b, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78,
0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12,
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30,
0x01, 0x12, 0x51, 0x0a, 0x11, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41, 0x75, 0x74, 0x68, 0x53,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x78,
0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65,
0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c,
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08,
0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0b, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73,
0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a,
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12,
0x4a, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -8149,45 +8168,46 @@ var file_management_proto_depIdxs = []int32{
91, // 97: management.PolicyCompact.authorized_groups:type_name -> management.PolicyCompact.AuthorizedGroupsEntry
73, // 98: management.PolicyCompact.source_resource:type_name -> management.ResourceCompact
73, // 99: management.PolicyCompact.destination_resource:type_name -> management.ResourceCompact
52, // 100: management.NameServerGroupRaw.nameservers:type_name -> management.NameServer
81, // 101: management.NetworkRouterList.entries:type_name -> management.NetworkRouterEntry
39, // 102: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes
80, // 103: management.NetworkMapComponentsFull.RoutersMapEntry.value:type_name -> management.NetworkRouterList
82, // 104: management.NetworkMapComponentsFull.ResourcePoliciesMapEntry.value:type_name -> management.PolicyIds
83, // 105: management.NetworkMapComponentsFull.GroupIdToUserIdsEntry.value:type_name -> management.UserIDList
84, // 106: management.NetworkMapComponentsFull.PostureFailedPeersEntry.value:type_name -> management.PeerIndexSet
74, // 107: management.PolicyCompact.AuthorizedGroupsEntry.value:type_name -> management.UserNameList
9, // 108: management.ManagementService.Login:input_type -> management.EncryptedMessage
9, // 109: management.ManagementService.Sync:input_type -> management.EncryptedMessage
27, // 110: management.ManagementService.GetServerKey:input_type -> management.Empty
27, // 111: management.ManagementService.isHealthy:input_type -> management.Empty
9, // 112: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
9, // 113: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage
9, // 114: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage
9, // 115: management.ManagementService.Logout:input_type -> management.EncryptedMessage
9, // 116: management.ManagementService.Job:input_type -> management.EncryptedMessage
9, // 117: management.ManagementService.ExtendAuthSession:input_type -> management.EncryptedMessage
9, // 118: management.ManagementService.CreateExpose:input_type -> management.EncryptedMessage
9, // 119: management.ManagementService.RenewExpose:input_type -> management.EncryptedMessage
9, // 120: management.ManagementService.StopExpose:input_type -> management.EncryptedMessage
9, // 121: management.ManagementService.Login:output_type -> management.EncryptedMessage
9, // 122: management.ManagementService.Sync:output_type -> management.EncryptedMessage
26, // 123: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
27, // 124: management.ManagementService.isHealthy:output_type -> management.Empty
9, // 125: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
9, // 126: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage
27, // 127: management.ManagementService.SyncMeta:output_type -> management.Empty
27, // 128: management.ManagementService.Logout:output_type -> management.Empty
9, // 129: management.ManagementService.Job:output_type -> management.EncryptedMessage
9, // 130: management.ManagementService.ExtendAuthSession:output_type -> management.EncryptedMessage
9, // 131: management.ManagementService.CreateExpose:output_type -> management.EncryptedMessage
9, // 132: management.ManagementService.RenewExpose:output_type -> management.EncryptedMessage
9, // 133: management.ManagementService.StopExpose:output_type -> management.EncryptedMessage
121, // [121:134] is the sub-list for method output_type
108, // [108:121] is the sub-list for method input_type
108, // [108:108] is the sub-list for extension type_name
108, // [108:108] is the sub-list for extension extendee
0, // [0:108] is the sub-list for field type_name
73, // 100: management.GroupCompact.resources:type_name -> management.ResourceCompact
52, // 101: management.NameServerGroupRaw.nameservers:type_name -> management.NameServer
81, // 102: management.NetworkRouterList.entries:type_name -> management.NetworkRouterEntry
39, // 103: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes
80, // 104: management.NetworkMapComponentsFull.RoutersMapEntry.value:type_name -> management.NetworkRouterList
82, // 105: management.NetworkMapComponentsFull.ResourcePoliciesMapEntry.value:type_name -> management.PolicyIds
83, // 106: management.NetworkMapComponentsFull.GroupIdToUserIdsEntry.value:type_name -> management.UserIDList
84, // 107: management.NetworkMapComponentsFull.PostureFailedPeersEntry.value:type_name -> management.PeerIndexSet
74, // 108: management.PolicyCompact.AuthorizedGroupsEntry.value:type_name -> management.UserNameList
9, // 109: management.ManagementService.Login:input_type -> management.EncryptedMessage
9, // 110: management.ManagementService.Sync:input_type -> management.EncryptedMessage
27, // 111: management.ManagementService.GetServerKey:input_type -> management.Empty
27, // 112: management.ManagementService.isHealthy:input_type -> management.Empty
9, // 113: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
9, // 114: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage
9, // 115: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage
9, // 116: management.ManagementService.Logout:input_type -> management.EncryptedMessage
9, // 117: management.ManagementService.Job:input_type -> management.EncryptedMessage
9, // 118: management.ManagementService.ExtendAuthSession:input_type -> management.EncryptedMessage
9, // 119: management.ManagementService.CreateExpose:input_type -> management.EncryptedMessage
9, // 120: management.ManagementService.RenewExpose:input_type -> management.EncryptedMessage
9, // 121: management.ManagementService.StopExpose:input_type -> management.EncryptedMessage
9, // 122: management.ManagementService.Login:output_type -> management.EncryptedMessage
9, // 123: management.ManagementService.Sync:output_type -> management.EncryptedMessage
26, // 124: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
27, // 125: management.ManagementService.isHealthy:output_type -> management.Empty
9, // 126: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
9, // 127: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage
27, // 128: management.ManagementService.SyncMeta:output_type -> management.Empty
27, // 129: management.ManagementService.Logout:output_type -> management.Empty
9, // 130: management.ManagementService.Job:output_type -> management.EncryptedMessage
9, // 131: management.ManagementService.ExtendAuthSession:output_type -> management.EncryptedMessage
9, // 132: management.ManagementService.CreateExpose:output_type -> management.EncryptedMessage
9, // 133: management.ManagementService.RenewExpose:output_type -> management.EncryptedMessage
9, // 134: management.ManagementService.StopExpose:output_type -> management.EncryptedMessage
122, // [122:135] is the sub-list for method output_type
109, // [109:122] is the sub-list for method input_type
109, // [109:109] is the sub-list for extension type_name
109, // [109:109] is the sub-list for extension extendee
0, // [0:109] is the sub-list for field type_name
}
func init() { file_management_proto_init() }

View File

@@ -1094,13 +1094,12 @@ message PolicyCompact {
// ResourceCompact mirrors types.Resource. Used by PolicyCompact to carry
// rule.SourceResource / rule.DestinationResource when the rule targets a
// specific resource (typically a peer) rather than groups.
// peer_index_set tells whether peer_index is valid (proto3 uint32 cannot
// disambiguate "0" from "unset"); set only when type == "peer".
message ResourceCompact {
string type = 1;
bool peer_index_set = 2;
uint32 peer_index = 3;
reserved 4; // future: host/subnet/domain references when needed
reserved 4;
string id = 5; // public id for domain/host/subnet resources
}
// UserNameList is a list of local-user names — used as the value type in
@@ -1124,6 +1123,8 @@ message GroupCompact {
// groups lose that property and the two sides expand policy
// destinations differently.
bool is_all = 3;
repeated ResourceCompact resources = 4;
}
// DNSSettingsCompact mirrors types.DNSSettings.

View File

@@ -1,106 +0,0 @@
package types
import (
"net/netip"
"time"
)
// ComponentPeer is the self-contained peer representation used by
// NetworkMapComponents and the calculated NetworkMap. It carries exactly the
// subset of peer data that crosses the components wire format, so the shared
// calculation layer stays independent of the management server's domain
// types.
type ComponentPeer struct {
ID string
Key string
IP netip.Addr
IPv6 netip.Addr
DNSLabel string
SSHKey string
SSHEnabled bool
ServerSSHAllowed bool
AgentVersion string
SupportsSourcePrefixes bool
SupportsIPv6 bool
LoginExpirationEnabled bool
AddedWithSSOLogin bool
LastLogin time.Time
// ProxyEmbedded marks an ephemeral embedded proxy peer. Connections
// involving such a peer on either endpoint default to lazy.
ProxyEmbedded bool
}
// FQDN returns the peer's FQDN combined of the peer's DNS label and the system's DNS domain.
func (p *ComponentPeer) FQDN(dnsDomain string) string {
if dnsDomain == "" {
return ""
}
return p.DNSLabel + "." + dnsDomain
}
// LoginExpired indicates whether the peer's login has expired, mirroring the
// server-side peer semantics: only SSO-added peers with login expiration
// enabled can expire.
func (p *ComponentPeer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) {
if !p.AddedWithSSOLogin || !p.LoginExpirationEnabled {
return false, 0
}
timeLeft := time.Until(p.LastLogin.Add(expiresIn))
return timeLeft <= 0, timeLeft
}
// GroupAllName is the reserved name of the default group that contains every peer in an account.
const GroupAllName = "All"
// ComponentGroup is the self-contained group representation used by
// NetworkMapComponents: just the membership view the network-map calculation
// needs, without the server's storage fields.
type ComponentGroup struct {
ID string
PublicID string
Name string
Peers []string
}
// IsGroupAll checks if the group is a default "All" group.
func (g *ComponentGroup) IsGroupAll() bool {
return g.Name == GroupAllName
}
// ComponentRouter is the self-contained network-router representation used by
// NetworkMapComponents.
type ComponentRouter struct {
NetworkID string
PublicID string
Peer string
PeerGroups []string
Masquerade bool
Metric int
Enabled bool
}
// ComponentResourceType mirrors the network-resource type enum on the
// components wire format.
type ComponentResourceType string
const (
ComponentResourceHost ComponentResourceType = "host"
ComponentResourceSubnet ComponentResourceType = "subnet"
ComponentResourceDomain ComponentResourceType = "domain"
)
// ComponentResource is the self-contained network-resource representation
// used by NetworkMapComponents.
type ComponentResource struct {
ID string
PublicID string
NetworkID string
AccountID string
Name string
Description string
Type ComponentResourceType
Address string
Domain string
Prefix netip.Prefix
Enabled bool
}

View File

@@ -1,16 +0,0 @@
package types
// DNSSettings defines dns settings at the account level
type DNSSettings struct {
// DisabledManagementGroups groups whose DNS management is disabled
DisabledManagementGroups []string `gorm:"serializer:json"`
}
// Copy returns a copy of the DNS settings
func (d DNSSettings) Copy() DNSSettings {
settings := DNSSettings{
DisabledManagementGroups: make([]string, len(d.DisabledManagementGroups)),
}
copy(settings.DisabledManagementGroups, d.DisabledManagementGroups)
return settings
}

View File

@@ -3,6 +3,7 @@ package types
import (
"strconv"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/version"
)
@@ -23,31 +24,9 @@ type supportedFeatures struct {
type LookupMap map[string]struct{}
func PolicyRuleImpliesLegacySSH(rule *PolicyRule) bool {
return rule.Protocol == PolicyRuleProtocolALL || (rule.Protocol == PolicyRuleProtocolTCP && (portsIncludesSSH(rule.Ports) || portRangeIncludesSSH(rule.PortRanges)))
}
func portRangeIncludesSSH(portRanges []RulePortRange) bool {
for _, pr := range portRanges {
if (pr.Start <= defaultSSHPortNumber && pr.End >= defaultSSHPortNumber) || (pr.Start <= nativeSSHPortNumber && pr.End >= nativeSSHPortNumber) {
return true
}
}
return false
}
func portsIncludesSSH(ports []string) bool {
for _, port := range ports {
if port == defaultSSHPortString || port == nativeSSHPortString {
return true
}
}
return false
}
// ExpandPortsAndRanges expands Ports and PortRanges of a rule into individual firewall rules.
func ExpandPortsAndRanges(base FirewallRule, rule *PolicyRule, peer *ComponentPeer) []*FirewallRule {
features := peerSupportedFirewallFeatures(peer.AgentVersion)
func ExpandPortsAndRanges(base FirewallRule, rule *nmdata.PolicyRule, peer *nmdata.Peer) []*FirewallRule {
features := peerSupportedFirewallFeatures(peer.Meta.WtVersion)
var expanded []*FirewallRule
@@ -64,7 +43,7 @@ func ExpandPortsAndRanges(base FirewallRule, rule *PolicyRule, peer *ComponentPe
fr := base
if features.portRanges {
fr.PortRange = portRange
fr.PortRange = RulePortRange{Start: portRange.Start, End: portRange.End}
} else {
if portRange.Start != portRange.End {
continue
@@ -74,7 +53,7 @@ func ExpandPortsAndRanges(base FirewallRule, rule *PolicyRule, peer *ComponentPe
expanded = append(expanded, &fr)
}
if shouldCheckRulesForNativeSSH(features.nativeSSH, rule, peer) || rule.Protocol == PolicyRuleProtocolNetbirdSSH {
if shouldCheckRulesForNativeSSH(features.nativeSSH, rule, peer) || rule.Protocol == string(PolicyRuleProtocolNetbirdSSH) {
expanded = addNativeSSHRule(base, expanded)
}
@@ -104,8 +83,8 @@ func isPortInRule(portString string, portInt uint16, rule *FirewallRule) bool {
return rule.Port == portString || (rule.PortRange.Start <= portInt && portInt <= rule.PortRange.End)
}
func shouldCheckRulesForNativeSSH(supportsNative bool, rule *PolicyRule, peer *ComponentPeer) bool {
return supportsNative && peer.SSHEnabled && peer.ServerSSHAllowed && rule.Protocol == PolicyRuleProtocolTCP
func shouldCheckRulesForNativeSSH(supportsNative bool, rule *nmdata.PolicyRule, peer *nmdata.Peer) bool {
return supportsNative && peer.SSHEnabled && peer.Meta.Flags.ServerSSHAllowed && rule.Protocol == string(PolicyRuleProtocolTCP)
}
func peerSupportedFirewallFeatures(peerVer string) supportedFeatures {

View File

@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus"
nbroute "github.com/netbirdio/netbird/route"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
const (
@@ -50,7 +51,7 @@ func (r *FirewallRule) Equal(other *FirewallRule) bool {
// For static routes, source ranges match the destination family (v4 or v6).
// For dynamic routes (domain-based), separate v4 and v6 rules are generated
// so the routing peer's forwarding chain allows both address families.
func GenerateRouteFirewallRules(ctx context.Context, route *nbroute.Route, rule *PolicyRule, groupPeers []*ComponentPeer, direction int, includeIPv6 bool) []*RouteFirewallRule {
func GenerateRouteFirewallRules(ctx context.Context, route *nmdata.Route, rule *nmdata.PolicyRule, groupPeers []*nmdata.Peer, direction int, includeIPv6 bool) []*RouteFirewallRule {
rulesExists := make(map[string]struct{})
rules := make([]*RouteFirewallRule, 0)
@@ -71,11 +72,11 @@ func GenerateRouteFirewallRules(ctx context.Context, route *nbroute.Route, rule
baseRule := RouteFirewallRule{
PolicyID: rule.PolicyID,
RouteID: route.ID,
RouteID: nbroute.ID(route.ID),
SourceRanges: sourceRanges,
Action: string(rule.Action),
Action: rule.Action,
Destination: route.Network.String(),
Protocol: string(rule.Protocol),
Protocol: rule.Protocol,
Domains: route.Domains,
IsDynamic: route.IsDynamic(),
}
@@ -93,7 +94,7 @@ func GenerateRouteFirewallRules(ctx context.Context, route *nbroute.Route, rule
v6Rule.SourceRanges = v6Sources
if isDefaultV4 {
v6Rule.Destination = "::/0"
v6Rule.RouteID = route.ID + "-v6-default"
v6Rule.RouteID = nbroute.ID(route.ID + "-v6-default")
}
if len(rule.Ports) == 0 {
rules = append(rules, generateRulesWithPortRanges(v6Rule, rule, rulesExists)...)
@@ -106,7 +107,7 @@ func GenerateRouteFirewallRules(ctx context.Context, route *nbroute.Route, rule
}
// splitPeerSourcesByFamily separates peer IPs into v4 (/32) and v6 (/128) source ranges.
func splitPeerSourcesByFamily(groupPeers []*ComponentPeer) (v4, v6 []string) {
func splitPeerSourcesByFamily(groupPeers []*nmdata.Peer) (v4, v6 []string) {
v4 = make([]string, 0, len(groupPeers))
v6 = make([]string, 0, len(groupPeers))
for _, peer := range groupPeers {
@@ -122,7 +123,7 @@ func splitPeerSourcesByFamily(groupPeers []*ComponentPeer) (v4, v6 []string) {
}
// generateRulesForPeer generates rules for a given peer based on ports and port ranges.
func generateRulesWithPortRanges(baseRule RouteFirewallRule, rule *PolicyRule, rulesExists map[string]struct{}) []*RouteFirewallRule {
func generateRulesWithPortRanges(baseRule RouteFirewallRule, rule *nmdata.PolicyRule, rulesExists map[string]struct{}) []*RouteFirewallRule {
rules := make([]*RouteFirewallRule, 0)
ruleIDBase := generateRuleIDBase(rule, baseRule)
@@ -138,7 +139,7 @@ func generateRulesWithPortRanges(baseRule RouteFirewallRule, rule *PolicyRule, r
if _, ok := rulesExists[ruleID]; !ok {
rulesExists[ruleID] = struct{}{}
pr := baseRule
pr.PortRange = portRange
pr.PortRange = RulePortRange{Start: portRange.Start, End: portRange.End}
rules = append(rules, &pr)
}
}
@@ -150,7 +151,7 @@ func generateRulesWithPortRanges(baseRule RouteFirewallRule, rule *PolicyRule, r
}
// generateRulesWithPorts generates rules when specific ports are provided.
func generateRulesWithPorts(ctx context.Context, baseRule RouteFirewallRule, rule *PolicyRule, rulesExists map[string]struct{}) []*RouteFirewallRule {
func generateRulesWithPorts(ctx context.Context, baseRule RouteFirewallRule, rule *nmdata.PolicyRule, rulesExists map[string]struct{}) []*RouteFirewallRule {
rules := make([]*RouteFirewallRule, 0)
ruleIDBase := generateRuleIDBase(rule, baseRule)
@@ -176,6 +177,6 @@ func generateRulesWithPorts(ctx context.Context, baseRule RouteFirewallRule, rul
}
// generateRuleIDBase generates the base rule ID for checking duplicates.
func generateRuleIDBase(rule *PolicyRule, baseRule RouteFirewallRule) string {
func generateRuleIDBase(rule *nmdata.PolicyRule, baseRule RouteFirewallRule) string {
return rule.ID + strings.Join(baseRule.SourceRanges, ",") + strconv.Itoa(FirewallRuleDirectionIN) + baseRule.Protocol + baseRule.Action
}

View File

@@ -8,12 +8,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/route"
"github.com/netbirdio/netbird/shared/management/domain"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
func TestSplitPeerSourcesByFamily(t *testing.T) {
peers := []*ComponentPeer{
peers := []*nmdata.Peer{
{
IP: netip.MustParseAddr("100.64.0.1"),
IPv6: netip.MustParseAddr("fd00::1"),
@@ -35,7 +35,7 @@ func TestSplitPeerSourcesByFamily(t *testing.T) {
}
func TestGenerateRouteFirewallRules_V4Route(t *testing.T) {
peers := []*ComponentPeer{
peers := []*nmdata.Peer{
{
IP: netip.MustParseAddr("100.64.0.1"),
IPv6: netip.MustParseAddr("fd00::1"),
@@ -45,15 +45,15 @@ func TestGenerateRouteFirewallRules_V4Route(t *testing.T) {
},
}
r := &route.Route{
r := &nmdata.Route{
ID: "route1",
Network: netip.MustParsePrefix("10.0.0.0/24"),
}
rule := &PolicyRule{
rule := &nmdata.PolicyRule{
PolicyID: "policy1",
ID: "rule1",
Action: PolicyTrafficActionAccept,
Protocol: PolicyRuleProtocolALL,
Action: string(PolicyTrafficActionAccept),
Protocol: string(PolicyRuleProtocolALL),
}
rules := GenerateRouteFirewallRules(context.Background(), r, rule, peers, FirewallRuleDirectionIN, true)
@@ -64,7 +64,7 @@ func TestGenerateRouteFirewallRules_V4Route(t *testing.T) {
}
func TestGenerateRouteFirewallRules_V6Route(t *testing.T) {
peers := []*ComponentPeer{
peers := []*nmdata.Peer{
{
IP: netip.MustParseAddr("100.64.0.1"),
IPv6: netip.MustParseAddr("fd00::1"),
@@ -74,15 +74,15 @@ func TestGenerateRouteFirewallRules_V6Route(t *testing.T) {
},
}
r := &route.Route{
r := &nmdata.Route{
ID: "route1",
Network: netip.MustParsePrefix("2001:db8::/32"),
}
rule := &PolicyRule{
rule := &nmdata.PolicyRule{
PolicyID: "policy1",
ID: "rule1",
Action: PolicyTrafficActionAccept,
Protocol: PolicyRuleProtocolALL,
Action: string(PolicyTrafficActionAccept),
Protocol: string(PolicyRuleProtocolALL),
}
rules := GenerateRouteFirewallRules(context.Background(), r, rule, peers, FirewallRuleDirectionIN, true)
@@ -92,7 +92,7 @@ func TestGenerateRouteFirewallRules_V6Route(t *testing.T) {
}
func TestGenerateRouteFirewallRules_DynamicRoute_DualStack(t *testing.T) {
peers := []*ComponentPeer{
peers := []*nmdata.Peer{
{
IP: netip.MustParseAddr("100.64.0.1"),
IPv6: netip.MustParseAddr("fd00::1"),
@@ -102,16 +102,16 @@ func TestGenerateRouteFirewallRules_DynamicRoute_DualStack(t *testing.T) {
},
}
r := &route.Route{
r := &nmdata.Route{
ID: "route1",
NetworkType: route.DomainNetwork,
NetworkType: nmdata.NetworkTypeDomain,
Domains: domain.List{"example.com"},
}
rule := &PolicyRule{
rule := &nmdata.PolicyRule{
PolicyID: "policy1",
ID: "rule1",
Action: PolicyTrafficActionAccept,
Protocol: PolicyRuleProtocolALL,
Action: string(PolicyTrafficActionAccept),
Protocol: string(PolicyRuleProtocolALL),
}
rules := GenerateRouteFirewallRules(context.Background(), r, rule, peers, FirewallRuleDirectionIN, true)
@@ -125,21 +125,21 @@ func TestGenerateRouteFirewallRules_DynamicRoute_DualStack(t *testing.T) {
}
func TestGenerateRouteFirewallRules_DynamicRoute_NoV6Peers(t *testing.T) {
peers := []*ComponentPeer{
peers := []*nmdata.Peer{
{IP: netip.MustParseAddr("100.64.0.1")},
{IP: netip.MustParseAddr("100.64.0.2")},
}
r := &route.Route{
r := &nmdata.Route{
ID: "route1",
NetworkType: route.DomainNetwork,
NetworkType: nmdata.NetworkTypeDomain,
Domains: domain.List{"example.com"},
}
rule := &PolicyRule{
rule := &nmdata.PolicyRule{
PolicyID: "policy1",
ID: "rule1",
Action: PolicyTrafficActionAccept,
Protocol: PolicyRuleProtocolALL,
Action: string(PolicyTrafficActionAccept),
Protocol: string(PolicyRuleProtocolALL),
}
rules := GenerateRouteFirewallRules(context.Background(), r, rule, peers, FirewallRuleDirectionIN, true)
@@ -149,7 +149,7 @@ func TestGenerateRouteFirewallRules_DynamicRoute_NoV6Peers(t *testing.T) {
}
func TestGenerateRouteFirewallRules_IncludeIPv6False(t *testing.T) {
peers := []*ComponentPeer{
peers := []*nmdata.Peer{
{
IP: netip.MustParseAddr("100.64.0.1"),
IPv6: netip.MustParseAddr("fd00::1"),
@@ -161,15 +161,15 @@ func TestGenerateRouteFirewallRules_IncludeIPv6False(t *testing.T) {
}
t.Run("v6 route excluded", func(t *testing.T) {
r := &route.Route{
r := &nmdata.Route{
ID: "route1",
Network: netip.MustParsePrefix("2001:db8::/32"),
}
rule := &PolicyRule{
rule := &nmdata.PolicyRule{
PolicyID: "policy1",
ID: "rule1",
Action: PolicyTrafficActionAccept,
Protocol: PolicyRuleProtocolALL,
Action: string(PolicyTrafficActionAccept),
Protocol: string(PolicyRuleProtocolALL),
}
rules := GenerateRouteFirewallRules(context.Background(), r, rule, peers, FirewallRuleDirectionIN, false)
@@ -177,16 +177,16 @@ func TestGenerateRouteFirewallRules_IncludeIPv6False(t *testing.T) {
})
t.Run("dynamic route only v4", func(t *testing.T) {
r := &route.Route{
r := &nmdata.Route{
ID: "route1",
NetworkType: route.DomainNetwork,
NetworkType: nmdata.NetworkTypeDomain,
Domains: domain.List{"example.com"},
}
rule := &PolicyRule{
rule := &nmdata.PolicyRule{
PolicyID: "policy1",
ID: "rule1",
Action: PolicyTrafficActionAccept,
Protocol: PolicyRuleProtocolALL,
Action: string(PolicyTrafficActionAccept),
Protocol: string(PolicyRuleProtocolALL),
}
rules := GenerateRouteFirewallRules(context.Background(), r, rule, peers, FirewallRuleDirectionIN, false)

View File

@@ -1,47 +1,28 @@
package types
import (
"encoding/binary"
"fmt"
"math/rand"
"net"
"net/netip"
"slices"
"sync"
"time"
"github.com/c-robinson/iplib"
"github.com/rs/xid"
"golang.org/x/exp/maps"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/route"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
"github.com/netbirdio/netbird/shared/management/status"
)
const (
// SubnetSize is a size of the subnet of the global network, e.g. 100.77.0.0/16
SubnetSize = 16
// NetSize is a global network size 100.64.0.0/10
NetSize = 10
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.64.30.1/32)
AllowedIPsFormat = "%s/32"
// AllowedIPsV6Format generates AllowedIPs format for v6 (e.g. fd12:3456:7890::1/128)
AllowedIPsV6Format = "%s/128"
// IPv6SubnetSize is the prefix length of per-account IPv6 subnets.
// Each account gets a /64 from its unique /48 ULA prefix.
IPv6SubnetSize = 64
)
type NetworkMap struct {
Peers []*ComponentPeer
Network *Network
Routes []*route.Route
Peers []*nmdata.Peer
Network *nmdata.Network
Routes []*nmdata.Route
DNSConfig nbdns.Config
OfflinePeers []*ComponentPeer
OfflinePeers []*nmdata.Peer
FirewallRules []*FirewallRule
RoutesFirewallRules []*RouteFirewallRule
ForwardingRules []*ForwardingRule
@@ -63,39 +44,8 @@ func (nm *NetworkMap) Merge(other *NetworkMap) {
nm.ForceRoutingPeerDNSResolution = nm.ForceRoutingPeerDNSResolution || other.ForceRoutingPeerDNSResolution
}
type comparableObject[T any] interface {
Equal(other T) bool
}
func mergeUnique[T comparableObject[T]](arr1, arr2 []T) []T {
var result []T
for _, item := range arr1 {
if !containsEqual(result, item) {
result = append(result, item)
}
}
for _, item := range arr2 {
if !containsEqual(result, item) {
result = append(result, item)
}
}
return result
}
func containsEqual[T comparableObject[T]](slice []T, element T) bool {
for _, item := range slice {
if item.Equal(element) {
return true
}
}
return false
}
func mergeUniquePeersByID(peers1, peers2 []*ComponentPeer) []*ComponentPeer {
result := make(map[string]*ComponentPeer)
func mergeUniquePeersByID(peers1, peers2 []*nmdata.Peer) []*nmdata.Peer {
result := make(map[string]*nmdata.Peer)
for _, peer := range peers1 {
result[peer.ID] = peer
}
@@ -151,245 +101,33 @@ func ipToBytes(ip net.IP) []byte {
return ip.To16()
}
type Network struct {
Identifier string `json:"id"`
Net net.IPNet `gorm:"serializer:json"`
// NetV6 is the IPv6 ULA subnet for this account's overlay. Empty if not yet allocated.
NetV6 net.IPNet `gorm:"serializer:json"`
Dns string
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
// Used to synchronize state to the client apps.
Serial uint64
Mu sync.Mutex `json:"-" gorm:"-"`
type comparableObject[T any] interface {
Equal(other T) bool
}
// NewNetwork creates a new Network initializing it with a Serial=0
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
// and a random /64 subnet from fd00:4e42::/32 for IPv6.
func NewNetwork() *Network {
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize)
sub, _ := n.Subnet(SubnetSize)
func mergeUnique[T comparableObject[T]](arr1, arr2 []T) []T {
var result []T
s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s)
intn := r.Intn(len(sub))
return &Network{
Identifier: xid.New().String(),
Net: sub[intn].IPNet,
NetV6: AllocateIPv6Subnet(r),
Dns: "",
Serial: 0,
}
}
// AllocateIPv6Subnet generates a random RFC 4193 ULA /64 prefix.
// The format follows RFC 4193 section 3.1: fd + 40-bit Global ID + 16-bit Subnet ID.
// The Global ID and Subnet ID are randomized (simplified from the SHA-1 algorithm
// in section 3.2.2), giving 2^56 possible /64 subnets across all accounts.
func AllocateIPv6Subnet(r *rand.Rand) net.IPNet {
ip := make(net.IP, 16)
ip[0] = 0xfd
// Bytes 1-5: 40-bit random Global ID
ip[1] = byte(r.Intn(256))
ip[2] = byte(r.Intn(256))
ip[3] = byte(r.Intn(256))
ip[4] = byte(r.Intn(256))
ip[5] = byte(r.Intn(256))
// Bytes 6-7: 16-bit random Subnet ID
ip[6] = byte(r.Intn(256))
ip[7] = byte(r.Intn(256))
return net.IPNet{
IP: ip,
Mask: net.CIDRMask(IPv6SubnetSize, 128),
}
}
// IncSerial increments Serial by 1 reflecting that the network state has been changed
func (n *Network) IncSerial() {
n.Mu.Lock()
defer n.Mu.Unlock()
n.Serial++
}
// CurrentSerial returns the Network.Serial of the network (latest state id)
func (n *Network) CurrentSerial() uint64 {
n.Mu.Lock()
defer n.Mu.Unlock()
return n.Serial
}
func (n *Network) Copy() *Network {
n.Mu.Lock()
defer n.Mu.Unlock()
return &Network{
Identifier: n.Identifier,
Net: n.Net,
NetV6: n.NetV6,
Dns: n.Dns,
Serial: n.Serial,
}
}
// AllocatePeerIP picks an available IP from a netip.Prefix.
// This method considers already taken IPs and reuses IPs if there are gaps in takenIps.
// E.g. if prefix=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3.
func AllocatePeerIP(prefix netip.Prefix, takenIps []netip.Addr) (netip.Addr, error) {
b := prefix.Masked().Addr().As4()
baseIP := binary.BigEndian.Uint32(b[:])
hostBits := 32 - prefix.Bits()
totalIPs := uint32(1 << hostBits)
taken := make(map[uint32]struct{}, len(takenIps)+1)
taken[baseIP] = struct{}{} // reserve network IP
taken[baseIP+totalIPs-1] = struct{}{} // reserve broadcast IP
for _, ip := range takenIps {
ab := ip.As4()
taken[binary.BigEndian.Uint32(ab[:])] = struct{}{}
}
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
maxAttempts := (int(totalIPs) - len(taken)) / 100
for i := 0; i < maxAttempts; i++ {
offset := uint32(rng.Intn(int(totalIPs-2))) + 1
candidate := baseIP + offset
if _, exists := taken[candidate]; !exists {
return uint32ToIP(candidate), nil
for _, item := range arr1 {
if !containsEqual(result, item) {
result = append(result, item)
}
}
for offset := uint32(1); offset < totalIPs-1; offset++ {
candidate := baseIP + offset
if _, exists := taken[candidate]; !exists {
return uint32ToIP(candidate), nil
for _, item := range arr2 {
if !containsEqual(result, item) {
result = append(result, item)
}
}
return netip.Addr{}, status.Errorf(status.PreconditionFailed, "network %s is out of IPs", prefix.String())
return result
}
// AllocateRandomPeerIP picks a random available IP from a netip.Prefix.
func AllocateRandomPeerIP(prefix netip.Prefix) (netip.Addr, error) {
b := prefix.Masked().Addr().As4()
baseIP := binary.BigEndian.Uint32(b[:])
hostBits := 32 - prefix.Bits()
totalIPs := uint32(1 << hostBits)
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
offset := uint32(rng.Intn(int(totalIPs-2))) + 1
candidate := baseIP + offset
return uint32ToIP(candidate), nil
}
// AllocateRandomPeerIPv6 picks a random host address within the given IPv6 prefix.
// Only the host bits (after the prefix length) are randomized.
func AllocateRandomPeerIPv6(prefix netip.Prefix) (netip.Addr, error) {
ones := prefix.Bits()
if ones == 0 || ones > 126 || !prefix.Addr().Is6() {
return netip.Addr{}, fmt.Errorf("invalid IPv6 subnet: %s", prefix.String())
}
ip := prefix.Addr().As16()
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
// Determine which byte the host bits start in
firstHostByte := ones / 8
// If the prefix doesn't end on a byte boundary, handle the partial byte
partialBits := ones % 8
if partialBits > 0 {
// Keep the network bits in the partial byte, randomize the rest
hostMask := byte(0xff >> partialBits)
ip[firstHostByte] = (ip[firstHostByte] & ^hostMask) | (byte(rng.Intn(256)) & hostMask)
firstHostByte++
}
// Randomize remaining full host bytes
for i := firstHostByte; i < 16; i++ {
ip[i] = byte(rng.Intn(256))
}
// Avoid all-zeros and all-ones host parts by checking only host bits.
if isHostAllZeroOrOnes(ip[:], ones) {
ip = prefix.Masked().Addr().As16()
ip[15] |= 0x01
}
return netip.AddrFrom16(ip).Unmap(), nil
}
// isHostAllZeroOrOnes checks whether all host bits (after prefixLen) are zero or all ones.
func isHostAllZeroOrOnes(ip []byte, prefixLen int) bool {
hostStart := prefixLen / 8
partialBits := prefixLen % 8
hostSlice := slices.Clone(ip[hostStart:])
if partialBits > 0 {
hostSlice[0] &= 0xff >> partialBits
}
allZero := !slices.ContainsFunc(hostSlice, func(v byte) bool { return v != 0 })
if allZero {
return true
}
// Build the all-ones mask for host bits
onesMask := make([]byte, len(hostSlice))
for i := range onesMask {
onesMask[i] = 0xff
}
if partialBits > 0 {
onesMask[0] = 0xff >> partialBits
}
return slices.Equal(hostSlice, onesMask)
}
func uint32ToIP(n uint32) netip.Addr {
var b [4]byte
binary.BigEndian.PutUint32(b[:], n)
return netip.AddrFrom4(b)
}
// generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list
func generateIPs(ipNet *net.IPNet, exclusions map[string]struct{}) ([]net.IP, int) {
var ips []net.IP
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); incIP(ip) {
if _, ok := exclusions[ip.String()]; !ok && ip[3] != 0 {
ips = append(ips, copyIP(ip))
}
}
// remove network address, broadcast and Fake DNS resolver address
lenIPs := len(ips)
switch {
case lenIPs < 2:
return ips, lenIPs
case lenIPs < 3:
return ips[1 : len(ips)-1], lenIPs - 2
default:
return ips[1 : len(ips)-2], lenIPs - 3
}
}
func copyIP(ip net.IP) net.IP {
dup := make(net.IP, len(ip))
copy(dup, ip)
return dup
}
func incIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
func containsEqual[T comparableObject[T]](slice []T, element T) bool {
for _, item := range slice {
if item.Equal(element) {
return true
}
}
return false
}

View File

@@ -1,41 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
type testObject struct {
value int
}
func (t testObject) Equal(other testObject) bool {
return t.value == other.value
}
func Test_MergeUniqueArraysWithoutDuplicates(t *testing.T) {
arr1 := []testObject{{value: 1}, {value: 2}}
arr2 := []testObject{{value: 2}, {value: 3}}
result := mergeUnique(arr1, arr2)
assert.Len(t, result, 3)
assert.Contains(t, result, testObject{value: 1})
assert.Contains(t, result, testObject{value: 2})
assert.Contains(t, result, testObject{value: 3})
}
func Test_MergeUniqueHandlesEmptyArrays(t *testing.T) {
arr1 := []testObject{}
arr2 := []testObject{}
result := mergeUnique(arr1, arr2)
assert.Empty(t, result)
}
func Test_MergeUniqueHandlesOneEmptyArray(t *testing.T) {
arr1 := []testObject{{value: 1}, {value: 2}}
arr2 := []testObject{}
result := mergeUnique(arr1, arr2)
assert.Len(t, result, 2)
assert.Contains(t, result, testObject{value: 1})
assert.Contains(t, result, testObject{value: 2})
}

View File

@@ -1,264 +1,41 @@
package types
import (
"encoding/binary"
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewNetwork(t *testing.T) {
network := NewNetwork()
// generated net should be a subnet of a larger 100.64.0.0/10 net
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}}
assert.Equal(t, ipNet.Contains(network.Net.IP), true)
type mergeTestObject struct {
value int
}
func TestAllocatePeerIP(t *testing.T) {
prefix := netip.MustParsePrefix("100.64.0.0/24")
var ips []netip.Addr
for i := 0; i < 252; i++ {
ip, err := AllocatePeerIP(prefix, ips)
if err != nil {
t.Fatal(err)
}
ips = append(ips, ip)
}
assert.Len(t, ips, 252)
uniq := make(map[string]struct{})
for _, ip := range ips {
if _, ok := uniq[ip.String()]; !ok {
uniq[ip.String()] = struct{}{}
} else {
t.Errorf("found duplicate IP %s", ip.String())
}
}
func (t mergeTestObject) Equal(other mergeTestObject) bool {
return t.value == other.value
}
func TestAllocatePeerIPSmallSubnet(t *testing.T) {
// Test /27 network (10.0.0.0/27) - should only have 30 usable IPs (10.0.0.1 to 10.0.0.30)
prefix := netip.MustParsePrefix("10.0.0.0/27")
var ips []netip.Addr
// Allocate all available IPs in the /27 network
for i := 0; i < 30; i++ {
ip, err := AllocatePeerIP(prefix, ips)
if err != nil {
t.Fatal(err)
}
// Verify IP is within the correct range
if !prefix.Contains(ip) {
t.Errorf("allocated IP %s is not within network %s", ip.String(), prefix.String())
}
ips = append(ips, ip)
}
assert.Len(t, ips, 30)
// Verify all IPs are unique
uniq := make(map[string]struct{})
for _, ip := range ips {
if _, ok := uniq[ip.String()]; !ok {
uniq[ip.String()] = struct{}{}
} else {
t.Errorf("found duplicate IP %s", ip.String())
}
}
// Try to allocate one more IP - should fail as network is full
_, err := AllocatePeerIP(prefix, ips)
if err == nil {
t.Error("expected error when network is full, but got none")
}
func Test_MergeUniqueArraysWithoutDuplicates(t *testing.T) {
arr1 := []mergeTestObject{{value: 1}, {value: 2}}
arr2 := []mergeTestObject{{value: 2}, {value: 3}}
result := mergeUnique(arr1, arr2)
assert.Len(t, result, 3)
assert.Contains(t, result, mergeTestObject{value: 1})
assert.Contains(t, result, mergeTestObject{value: 2})
assert.Contains(t, result, mergeTestObject{value: 3})
}
func TestAllocatePeerIPVariousCIDRs(t *testing.T) {
testCases := []struct {
name string
cidr string
expectedUsable int
}{
{"/30 network", "192.168.1.0/30", 2}, // 4 total - 2 reserved = 2 usable
{"/29 network", "192.168.1.0/29", 6}, // 8 total - 2 reserved = 6 usable
{"/28 network", "192.168.1.0/28", 14}, // 16 total - 2 reserved = 14 usable
{"/27 network", "192.168.1.0/27", 30}, // 32 total - 2 reserved = 30 usable
{"/26 network", "192.168.1.0/26", 62}, // 64 total - 2 reserved = 62 usable
{"/25 network", "192.168.1.0/25", 126}, // 128 total - 2 reserved = 126 usable
{"/16 network", "10.0.0.0/16", 65534}, // 65536 total - 2 reserved = 65534 usable
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
prefix, err := netip.ParsePrefix(tc.cidr)
require.NoError(t, err)
prefix = prefix.Masked()
var ips []netip.Addr
// For larger networks, test only a subset to avoid long test runs
testCount := tc.expectedUsable
if testCount > 1000 {
testCount = 1000
}
// Allocate IPs and verify they're within the correct range
for i := 0; i < testCount; i++ {
ip, err := AllocatePeerIP(prefix, ips)
require.NoError(t, err, "failed to allocate IP %d", i)
// Verify IP is within the correct range
assert.True(t, prefix.Contains(ip), "allocated IP %s is not within network %s", ip.String(), prefix.String())
// Verify IP is not network or broadcast address
networkAddr := prefix.Masked().Addr()
hostBits := 32 - prefix.Bits()
b := networkAddr.As4()
baseIP := binary.BigEndian.Uint32(b[:])
broadcastIP := uint32ToIP(baseIP + (1 << hostBits) - 1)
assert.NotEqual(t, networkAddr, ip, "allocated network address %s", ip.String())
assert.NotEqual(t, broadcastIP, ip, "allocated broadcast address %s", ip.String())
ips = append(ips, ip)
}
assert.Len(t, ips, testCount)
// Verify all IPs are unique
uniq := make(map[string]struct{})
for _, ip := range ips {
ipStr := ip.String()
assert.NotContains(t, uniq, ipStr, "found duplicate IP %s", ipStr)
uniq[ipStr] = struct{}{}
}
})
}
func Test_MergeUniqueHandlesEmptyArrays(t *testing.T) {
arr1 := []mergeTestObject{}
arr2 := []mergeTestObject{}
result := mergeUnique(arr1, arr2)
assert.Empty(t, result)
}
func TestGenerateIPs(t *testing.T) {
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 255, 0}}
ips, ipsLen := generateIPs(&ipNet, map[string]struct{}{"100.64.0.0": {}})
if ipsLen != 252 {
t.Errorf("expected 252 ips, got %d", len(ips))
return
}
if ips[len(ips)-1].String() != "100.64.0.253" {
t.Errorf("expected last ip to be: 100.64.0.253, got %s", ips[len(ips)-1].String())
}
}
func TestNewNetworkHasIPv6(t *testing.T) {
network := NewNetwork()
assert.NotNil(t, network.NetV6.IP, "v6 subnet should be allocated")
assert.True(t, network.NetV6.IP.To4() == nil, "v6 subnet should be IPv6")
assert.Equal(t, byte(0xfd), network.NetV6.IP[0], "v6 subnet should be ULA (fd prefix)")
ones, bits := network.NetV6.Mask.Size()
assert.Equal(t, 64, ones, "v6 subnet should be /64")
assert.Equal(t, 128, bits)
}
func TestAllocateIPv6SubnetUniqueness(t *testing.T) {
seen := make(map[string]struct{})
for i := 0; i < 100; i++ {
network := NewNetwork()
key := network.NetV6.IP.String()
_, duplicate := seen[key]
assert.False(t, duplicate, "duplicate v6 subnet: %s", key)
seen[key] = struct{}{}
}
}
func TestAllocateRandomPeerIPv6(t *testing.T) {
prefix := netip.MustParsePrefix("fd12:3456:7890:abcd::/64")
ip, err := AllocateRandomPeerIPv6(prefix)
require.NoError(t, err)
assert.True(t, ip.Is6(), "should be IPv6")
assert.True(t, prefix.Contains(ip), "should be within subnet")
// First 8 bytes (network prefix) should match
b := ip.As16()
prefixBytes := prefix.Addr().As16()
assert.Equal(t, prefixBytes[:8], b[:8], "prefix should match")
// Interface ID should not be all zeros
allZero := true
for _, v := range b[8:] {
if v != 0 {
allZero = false
break
}
}
assert.False(t, allZero, "interface ID should not be all zeros")
}
func TestAllocateRandomPeerIPv6_VariousPrefixes(t *testing.T) {
tests := []struct {
name string
cidr string
prefix int
}{
{"standard /64", "fd00:1234:5678:abcd::/64", 64},
{"small /112", "fd00:1234:5678:abcd::/112", 112},
{"large /48", "fd00:1234::/48", 48},
{"non-boundary /60", "fd00:1234:5670::/60", 60},
{"non-boundary /52", "fd00:1230::/52", 52},
{"minimum /120", "fd00:1234:5678:abcd::100/120", 120},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prefix, err := netip.ParsePrefix(tt.cidr)
require.NoError(t, err)
prefix = prefix.Masked()
assert.Equal(t, tt.prefix, prefix.Bits())
for i := 0; i < 50; i++ {
ip, err := AllocateRandomPeerIPv6(prefix)
require.NoError(t, err)
assert.True(t, prefix.Contains(ip), "IP %s should be within %s", ip, prefix)
}
})
}
}
func TestAllocateRandomPeerIPv6_PreservesNetworkBits(t *testing.T) {
// For a /112, bytes 0-13 should be preserved, only bytes 14-15 should vary
prefix := netip.MustParsePrefix("fd00:1234:5678:abcd:ef01:2345:6789:0/112")
prefixBytes := prefix.Addr().As16()
for i := 0; i < 20; i++ {
ip, err := AllocateRandomPeerIPv6(prefix)
require.NoError(t, err)
// First 14 bytes (112 bits = 14 bytes) must match the network
b := ip.As16()
assert.Equal(t, prefixBytes[:14], b[:14], "network bytes should be preserved for /112")
}
}
func TestAllocateRandomPeerIPv6_NonByteBoundary(t *testing.T) {
// For a /60, the first 7.5 bytes are network, so byte 7 is partial
prefix := netip.MustParsePrefix("fd00:1234:5678:abc0::/60")
prefixBytes := prefix.Addr().As16()
for i := 0; i < 50; i++ {
ip, err := AllocateRandomPeerIPv6(prefix)
require.NoError(t, err)
b := ip.As16()
assert.True(t, prefix.Contains(ip), "IP %s should be within %s", ip, prefix)
// First 7 bytes must match exactly
assert.Equal(t, prefixBytes[:7], b[:7], "full network bytes should match for /60")
// Byte 7: top 4 bits (0xc = 1100) must be preserved
assert.Equal(t, prefixBytes[7]&0xf0, b[7]&0xf0, "partial byte network bits should be preserved for /60")
}
func Test_MergeUniqueHandlesOneEmptyArray(t *testing.T) {
arr1 := []mergeTestObject{{value: 1}, {value: 2}}
arr2 := []mergeTestObject{}
result := mergeUnique(arr1, arr2)
assert.Len(t, result, 2)
assert.Contains(t, result, mergeTestObject{value: 1})
assert.Contains(t, result, mergeTestObject{value: 2})
}

View File

@@ -14,32 +14,33 @@ import (
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/route"
"github.com/netbirdio/netbird/shared/management/domain"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
type NetworkMapComponents struct {
PeerID string
Network *Network
AccountSettings *AccountSettingsInfo
DNSSettings *DNSSettings
Network *nmdata.Network
AccountSettings *nmdata.AccountSettingsInfo
DNSSettings *nmdata.DNSSettings
CustomZoneDomain string
Peers map[string]*ComponentPeer
Groups map[string]*ComponentGroup
Policies []*Policy
Routes []*route.Route
NameServerGroups []*nbdns.NameServerGroup
AllDNSRecords []nbdns.SimpleRecord
AccountZones []nbdns.CustomZone
ResourcePoliciesMap map[string][]*Policy
RoutersMap map[string]map[string]*ComponentRouter
NetworkResources []*ComponentResource
Peers map[string]*nmdata.Peer
Groups map[string]*nmdata.Group
Policies []*nmdata.Policy
Routes []*nmdata.Route
NameServerGroups []*nmdata.NameServerGroup
AllDNSRecords []nmdata.SimpleRecord
AccountZones []nmdata.CustomZone
ResourcePoliciesMap map[string][]*nmdata.Policy
RoutersMap map[string]map[string]*nmdata.NetworkRouter
NetworkResources []*nmdata.NetworkResource
GroupIDToUserIDs map[string][]string
AllowedUserIDs map[string]struct{}
PostureFailedPeers map[string]map[string]struct{}
RouterPeers map[string]*ComponentPeer
RouterPeers map[string]*nmdata.Peer
// NetworkXIDToPublicID maps Network.ID (xid) → PublicID.
// Consumed by the envelope encoder to
@@ -51,20 +52,21 @@ type NetworkMapComponents struct {
// Same role as NetworkXIDToPublicID, used for PostureFailedPeers keys and
// policy SourcePostureChecks references.
PostureCheckXIDToPublicID map[string]string
routesByPeerOnce sync.Once
routesByPeerIdx map[string][]routeIndexEntry
// true when returning an empty-like map (returned instead of nil)
empty bool
// ForceRoutingPeerDNSResolution forces the peer to run/use routing-peer DNS
// resolution regardless of the account-global setting, for reverse-proxy
// domain targets.
ForceRoutingPeerDNSResolution bool
routesByPeerOnce sync.Once
routesByPeerIdx map[string][]routeIndexEntry
// true when returning an empty-like map (returned instead of nil)
empty bool
}
type routeIndexEntry struct {
route *route.Route
route *nmdata.Route
viaGroup bool
}
@@ -80,15 +82,15 @@ func EmptyNetworkMapComponents(nm *NetworkMapComponents) *NetworkMapComponents {
return nm
}
func (c *NetworkMapComponents) GetPeerInfo(peerID string) *ComponentPeer {
func (c *NetworkMapComponents) GetPeerInfo(peerID string) *nmdata.Peer {
return c.Peers[peerID]
}
func (c *NetworkMapComponents) GetRouterPeerInfo(peerID string) *ComponentPeer {
func (c *NetworkMapComponents) GetRouterPeerInfo(peerID string) *nmdata.Peer {
return c.RouterPeers[peerID]
}
func (c *NetworkMapComponents) GetGroupInfo(groupID string) *ComponentGroup {
func (c *NetworkMapComponents) GetGroupInfo(groupID string) *nmdata.Group {
return c.Groups[groupID]
}
@@ -143,8 +145,8 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
peersToConnect, expiredPeers := c.filterPeersByLoginExpiration(aclPeers)
includeIPv6 := false
if p := c.Peers[targetPeerID]; p != nil {
includeIPv6 = p.SupportsIPv6 && p.IPv6.IsValid()
if p := c.GetPeerInfo(targetPeerID); p != nil {
includeIPv6 = p.SupportsIPv6() && p.IPv6.IsValid()
}
routesUpdate := filterAndExpandRoutes(c.getRoutesToSync(targetPeerID, peersToConnect, peerGroups), includeIPv6)
routesFirewallRules := c.getPeerRoutesFirewallRules(ctx, targetPeerID, includeIPv6)
@@ -175,11 +177,11 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
if c.CustomZoneDomain != "" && len(c.AllDNSRecords) > 0 {
customZones = append(customZones, nbdns.CustomZone{
Domain: c.CustomZoneDomain,
Records: c.AllDNSRecords,
Records: toRealRecords(c.AllDNSRecords),
})
}
customZones = append(customZones, c.AccountZones...)
customZones = append(customZones, toRealZones(c.AccountZones)...)
dnsUpdate.CustomZones = customZones
dnsUpdate.NameServerGroups = c.getPeerNSGroupsFromGroups(targetPeerID, peerGroups)
@@ -187,7 +189,7 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
return &NetworkMap{
Peers: peersToConnectIncludingRouters,
Network: c.Network.Copy(),
Network: c.Network,
Routes: append(filterAndExpandRoutes(networkResourcesRoutes, includeIPv6), routesUpdate...),
DNSConfig: dnsUpdate,
OfflinePeers: expiredPeers,
@@ -204,7 +206,7 @@ func (c *NetworkMapComponents) IsEmpty() bool {
return c.empty
}
func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) ([]*ComponentPeer, []*FirewallRule, map[string]map[string]struct{}, bool) {
func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) ([]*nmdata.Peer, []*FirewallRule, map[string]map[string]struct{}, bool) {
targetPeer := c.GetPeerInfo(targetPeerID)
if targetPeer == nil {
return nil, nil, nil, false
@@ -215,25 +217,25 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) (
sshEnabled := false
for _, policy := range c.Policies {
if !policy.Enabled {
if policy == nil || !policy.Enabled {
continue
}
for _, rule := range policy.Rules {
if !rule.Enabled {
if rule == nil || !rule.Enabled {
continue
}
var sourcePeers, destinationPeers []*ComponentPeer
var sourcePeers, destinationPeers []*nmdata.Peer
var peerInSources, peerInDestinations bool
if rule.SourceResource.Type == ResourceTypePeer && rule.SourceResource.ID != "" {
if rule.SourceResource.Type == string(ResourceTypePeer) && rule.SourceResource.ID != "" {
sourcePeers, peerInSources = c.getPeerFromResource(rule.SourceResource, targetPeerID)
} else {
sourcePeers, peerInSources = c.getAllPeersFromGroups(rule.Sources, targetPeerID, policy.SourcePostureChecks)
}
if rule.DestinationResource.Type == ResourceTypePeer && rule.DestinationResource.ID != "" {
if rule.DestinationResource.Type == string(ResourceTypePeer) && rule.DestinationResource.ID != "" {
destinationPeers, peerInDestinations = c.getPeerFromResource(rule.DestinationResource, targetPeerID)
} else {
destinationPeers, peerInDestinations = c.getAllPeersFromGroups(rule.Destinations, targetPeerID, nil)
@@ -256,7 +258,7 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) (
generateResources(rule, sourcePeers, FirewallRuleDirectionIN)
}
if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdSSH {
if peerInDestinations && rule.Protocol == string(PolicyRuleProtocolNetbirdSSH) {
sshEnabled = true
switch {
case len(rule.AuthorizedGroups) > 0:
@@ -287,7 +289,7 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) (
default:
authorizedUsers[auth.Wildcard] = c.getAllowedUserIDs()
}
} else if peerInDestinations && PolicyRuleImpliesLegacySSH(rule) && targetPeer.SSHEnabled {
} else if peerInDestinations && nmdata.PolicyRuleImpliesLegacySSH(rule) && targetPeer.SSHEnabled {
sshEnabled = true
authorizedUsers[auth.Wildcard] = c.getAllowedUserIDs()
}
@@ -307,19 +309,19 @@ func (c *NetworkMapComponents) getAllowedUserIDs() map[string]struct{} {
return make(map[string]struct{})
}
func (c *NetworkMapComponents) connResourcesGenerator(targetPeer *ComponentPeer) (func(*PolicyRule, []*ComponentPeer, int), func() ([]*ComponentPeer, []*FirewallRule)) {
func (c *NetworkMapComponents) connResourcesGenerator(targetPeer *nmdata.Peer) (func(*nmdata.PolicyRule, []*nmdata.Peer, int), func() ([]*nmdata.Peer, []*FirewallRule)) {
rulesExists := make(map[string]struct{})
peersExists := make(map[string]struct{})
rules := make([]*FirewallRule, 0)
peers := make([]*ComponentPeer, 0)
peers := make([]*nmdata.Peer, 0)
return func(rule *PolicyRule, groupPeers []*ComponentPeer, direction int) {
return func(rule *nmdata.PolicyRule, groupPeers []*nmdata.Peer, direction int) {
protocol := rule.Protocol
if protocol == PolicyRuleProtocolNetbirdSSH {
protocol = PolicyRuleProtocolTCP
if protocol == string(PolicyRuleProtocolNetbirdSSH) {
protocol = string(PolicyRuleProtocolTCP)
}
protocolStr := string(protocol)
protocolStr := protocol
actionStr := string(rule.Action)
dirStr := strconv.Itoa(direction)
portsJoined := strings.Join(rule.Ports, ",")
@@ -365,15 +367,15 @@ func (c *NetworkMapComponents) connResourcesGenerator(targetPeer *ComponentPeer)
PortsJoined: portsJoined,
})
}
}, func() ([]*ComponentPeer, []*FirewallRule) {
}, func() ([]*nmdata.Peer, []*FirewallRule) {
return peers, rules
}
}
func (c *NetworkMapComponents) getAllPeersFromGroups(groups []string, peerID string, sourcePostureChecksIDs []string) ([]*ComponentPeer, bool) {
func (c *NetworkMapComponents) getAllPeersFromGroups(groups []string, peerID string, sourcePostureChecksIDs []string) ([]*nmdata.Peer, bool) {
peerInGroups := false
uniquePeerIDs := c.getUniquePeerIDsFromGroupsIDs(groups)
filteredPeers := make([]*ComponentPeer, 0, len(uniquePeerIDs))
filteredPeers := make([]*nmdata.Peer, 0, len(uniquePeerIDs))
for _, p := range uniquePeerIDs {
peerInfo := c.GetPeerInfo(p)
@@ -425,22 +427,22 @@ func (c *NetworkMapComponents) getUniquePeerIDsFromGroupsIDs(groups []string) []
return ids
}
func (c *NetworkMapComponents) getPeerFromResource(resource Resource, peerID string) ([]*ComponentPeer, bool) {
func (c *NetworkMapComponents) getPeerFromResource(resource nmdata.Resource, peerID string) ([]*nmdata.Peer, bool) {
if resource.ID == peerID {
return []*ComponentPeer{}, true
return []*nmdata.Peer{}, true
}
peerInfo := c.GetPeerInfo(resource.ID)
if peerInfo == nil {
return []*ComponentPeer{}, false
return []*nmdata.Peer{}, false
}
return []*ComponentPeer{peerInfo}, false
return []*nmdata.Peer{peerInfo}, false
}
func (c *NetworkMapComponents) filterPeersByLoginExpiration(aclPeers []*ComponentPeer) ([]*ComponentPeer, []*ComponentPeer) {
peersToConnect := make([]*ComponentPeer, 0, len(aclPeers))
var expiredPeers []*ComponentPeer
func (c *NetworkMapComponents) filterPeersByLoginExpiration(aclPeers []*nmdata.Peer) ([]*nmdata.Peer, []*nmdata.Peer) {
peersToConnect := make([]*nmdata.Peer, 0, len(aclPeers))
var expiredPeers []*nmdata.Peer
for _, p := range aclPeers {
expired, _ := p.LoginExpired(c.AccountSettings.PeerLoginExpiration)
@@ -480,7 +482,7 @@ func (c *NetworkMapComponents) getPeerNSGroupsFromGroups(peerID string, groupLis
for _, gID := range nsGroup.Groups {
if _, found := groupList[gID]; found {
if !c.peerIsNameserver(peerIPStr, nsGroup) {
peerNSGroups = append(peerNSGroups, nsGroup.Copy())
peerNSGroups = append(peerNSGroups, toRealNSGroup(nsGroup))
}
break
}
@@ -490,7 +492,7 @@ func (c *NetworkMapComponents) getPeerNSGroupsFromGroups(peerID string, groupLis
return peerNSGroups
}
func (c *NetworkMapComponents) peerIsNameserver(peerIPStr string, nsGroup *nbdns.NameServerGroup) bool {
func (c *NetworkMapComponents) peerIsNameserver(peerIPStr string, nsGroup *nmdata.NameServerGroup) bool {
for _, ns := range nsGroup.NameServers {
if peerIPStr == ns.IP.String() {
return true
@@ -502,8 +504,8 @@ func (c *NetworkMapComponents) peerIsNameserver(peerIPStr string, nsGroup *nbdns
// filterAndExpandRoutes drops v6 routes for non-capable peers and duplicates
// the default v4 route (0.0.0.0/0) as ::/0 for v6-capable peers.
// TODO: the "-v6" suffix on IDs could collide with user-supplied route IDs.
func filterAndExpandRoutes(routes []*route.Route, includeIPv6 bool) []*route.Route {
filtered := make([]*route.Route, 0, len(routes))
func filterAndExpandRoutes(routes []*nmdata.Route, includeIPv6 bool) []*nmdata.Route {
filtered := make([]*nmdata.Route, 0, len(routes))
for _, r := range routes {
if !includeIPv6 && r.Network.Addr().Is6() {
continue
@@ -515,14 +517,14 @@ func filterAndExpandRoutes(routes []*route.Route, includeIPv6 bool) []*route.Rou
v6.ID = r.ID + "-v6-default"
v6.NetID = r.NetID + "-v6"
v6.Network = netip.MustParsePrefix("::/0")
v6.NetworkType = route.IPv6Network
v6.NetworkType = nmdata.NetworkTypeIPv6
filtered = append(filtered, v6)
}
}
return filtered
}
func (c *NetworkMapComponents) getRoutesToSync(peerID string, aclPeers []*ComponentPeer, peerGroups LookupMap) []*route.Route {
func (c *NetworkMapComponents) getRoutesToSync(peerID string, aclPeers []*nmdata.Peer, peerGroups LookupMap) []*nmdata.Route {
routes, peerDisabledRoutes := c.getRoutingPeerRoutes(peerID)
peerRoutesMembership := make(LookupMap)
for _, r := range append(routes, peerDisabledRoutes...) {
@@ -539,7 +541,7 @@ func (c *NetworkMapComponents) getRoutesToSync(peerID string, aclPeers []*Compon
return routes
}
func (c *NetworkMapComponents) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Route, disabledRoutes []*route.Route) {
func (c *NetworkMapComponents) getRoutingPeerRoutes(peerID string) (enabledRoutes []*nmdata.Route, disabledRoutes []*nmdata.Route) {
peerInfo := c.GetPeerInfo(peerID)
if peerInfo == nil {
peerInfo = c.GetRouterPeerInfo(peerID)
@@ -548,9 +550,9 @@ func (c *NetworkMapComponents) getRoutingPeerRoutes(peerID string) (enabledRoute
return enabledRoutes, disabledRoutes
}
seenRoute := make(map[route.ID]struct{})
seenRoute := make(map[string]struct{})
takeRoute := func(r *route.Route) {
takeRoute := func(r *nmdata.Route) {
if _, ok := seenRoute[r.ID]; ok {
return
}
@@ -569,7 +571,7 @@ func (c *NetworkMapComponents) getRoutingPeerRoutes(peerID string) (enabledRoute
if entry.viaGroup {
newPeerRoute := entry.route.Copy()
newPeerRoute.PeerGroups = nil
newPeerRoute.ID = route.ID(string(entry.route.ID) + ":" + peerID)
newPeerRoute.ID = entry.route.ID + ":" + peerID
takeRoute(newPeerRoute)
continue
}
@@ -602,8 +604,8 @@ func (c *NetworkMapComponents) routesByPeer() map[string][]routeIndexEntry {
return c.routesByPeerIdx
}
func (c *NetworkMapComponents) filterRoutesByGroups(routes []*route.Route, groupListMap LookupMap) []*route.Route {
var filteredRoutes []*route.Route
func (c *NetworkMapComponents) filterRoutesByGroups(routes []*nmdata.Route, groupListMap LookupMap) []*nmdata.Route {
var filteredRoutes []*nmdata.Route
for _, r := range routes {
for _, groupID := range r.Groups {
_, found := groupListMap[groupID]
@@ -616,8 +618,8 @@ func (c *NetworkMapComponents) filterRoutesByGroups(routes []*route.Route, group
return filteredRoutes
}
func (c *NetworkMapComponents) filterRoutesFromPeersOfSameHAGroup(routes []*route.Route, peerMemberships LookupMap) []*route.Route {
var filteredRoutes []*route.Route
func (c *NetworkMapComponents) filterRoutesFromPeersOfSameHAGroup(routes []*nmdata.Route, peerMemberships LookupMap) []*nmdata.Route {
var filteredRoutes []*nmdata.Route
for _, r := range routes {
_, found := peerMemberships[string(r.GetHAUniqueID())]
if !found {
@@ -650,7 +652,7 @@ func (c *NetworkMapComponents) getPeerRoutesFirewallRules(ctx context.Context, p
return routesFirewallRules
}
func (c *NetworkMapComponents) getDefaultPermit(r *route.Route, includeIPv6 bool) []*RouteFirewallRule {
func (c *NetworkMapComponents) getDefaultPermit(r *nmdata.Route, includeIPv6 bool) []*RouteFirewallRule {
if r.Network.Addr().Is6() && !includeIPv6 {
return nil
}
@@ -667,7 +669,7 @@ func (c *NetworkMapComponents) getDefaultPermit(r *route.Route, includeIPv6 bool
Protocol: string(PolicyRuleProtocolALL),
Domains: r.Domains,
IsDynamic: r.IsDynamic(),
RouteID: r.ID,
RouteID: route.ID(r.ID),
}
rules := []*RouteFirewallRule{&rule}
@@ -678,7 +680,7 @@ func (c *NetworkMapComponents) getDefaultPermit(r *route.Route, includeIPv6 bool
ruleV6.SourceRanges = []string{"::/0"}
if isDefaultV4 {
ruleV6.Destination = "::/0"
ruleV6.RouteID = r.ID + "-v6-default"
ruleV6.RouteID = route.ID(r.ID + "-v6-default")
}
rules = append(rules, &ruleV6)
}
@@ -686,7 +688,7 @@ func (c *NetworkMapComponents) getDefaultPermit(r *route.Route, includeIPv6 bool
return rules
}
func (c *NetworkMapComponents) getDistributionGroupsPeers(r *route.Route) map[string]struct{} {
func (c *NetworkMapComponents) getDistributionGroupsPeers(r *nmdata.Route) map[string]struct{} {
distPeers := make(map[string]struct{})
for _, id := range r.Groups {
group := c.GetGroupInfo(id)
@@ -701,11 +703,17 @@ func (c *NetworkMapComponents) getDistributionGroupsPeers(r *route.Route) map[st
return distPeers
}
func (c *NetworkMapComponents) getAllRoutePoliciesFromGroups(accessControlGroups []string) []*Policy {
routePolicies := make([]*Policy, 0)
func (c *NetworkMapComponents) getAllRoutePoliciesFromGroups(accessControlGroups []string) []*nmdata.Policy {
routePolicies := make([]*nmdata.Policy, 0)
for _, groupID := range accessControlGroups {
for _, policy := range c.Policies {
if policy == nil {
continue
}
for _, rule := range policy.Rules {
if rule == nil {
continue
}
if slices.Contains(rule.Destinations, groupID) {
routePolicies = append(routePolicies, policy)
}
@@ -716,15 +724,15 @@ func (c *NetworkMapComponents) getAllRoutePoliciesFromGroups(accessControlGroups
return routePolicies
}
func (c *NetworkMapComponents) getRouteFirewallRules(ctx context.Context, peerID string, policies []*Policy, route *route.Route, distributionPeers map[string]struct{}, includeIPv6 bool) []*RouteFirewallRule {
func (c *NetworkMapComponents) getRouteFirewallRules(ctx context.Context, peerID string, policies []*nmdata.Policy, route *nmdata.Route, distributionPeers map[string]struct{}, includeIPv6 bool) []*RouteFirewallRule {
var fwRules []*RouteFirewallRule
for _, policy := range policies {
if !policy.Enabled {
if policy == nil || !policy.Enabled {
continue
}
for _, rule := range policy.Rules {
if !rule.Enabled {
if rule == nil || !rule.Enabled {
continue
}
@@ -736,7 +744,7 @@ func (c *NetworkMapComponents) getRouteFirewallRules(ctx context.Context, peerID
return fwRules
}
func (c *NetworkMapComponents) getRulePeers(rule *PolicyRule, postureChecks []string, peerID string, distributionPeers map[string]struct{}) []*ComponentPeer {
func (c *NetworkMapComponents) getRulePeers(rule *nmdata.PolicyRule, postureChecks []string, peerID string, distributionPeers map[string]struct{}) []*nmdata.Peer {
distPeersWithPolicy := make(map[string]struct{})
for _, id := range rule.Sources {
group := c.GetGroupInfo(id)
@@ -755,7 +763,7 @@ func (c *NetworkMapComponents) getRulePeers(rule *PolicyRule, postureChecks []st
}
}
}
if rule.SourceResource.Type == ResourceTypePeer && rule.SourceResource.ID != "" {
if rule.SourceResource.Type == string(ResourceTypePeer) && rule.SourceResource.ID != "" {
_, distPeer := distributionPeers[rule.SourceResource.ID]
_, valid := c.Peers[rule.SourceResource.ID]
if distPeer && valid && c.ValidatePostureChecksOnPeer(rule.SourceResource.ID, postureChecks) {
@@ -763,7 +771,7 @@ func (c *NetworkMapComponents) getRulePeers(rule *PolicyRule, postureChecks []st
}
}
distributionGroupPeers := make([]*ComponentPeer, 0, len(distPeersWithPolicy))
distributionGroupPeers := make([]*nmdata.Peer, 0, len(distPeersWithPolicy))
for pID := range distPeersWithPolicy {
peerInfo := c.GetPeerInfo(pID)
if peerInfo == nil {
@@ -774,9 +782,9 @@ func (c *NetworkMapComponents) getRulePeers(rule *PolicyRule, postureChecks []st
return distributionGroupPeers
}
func (c *NetworkMapComponents) getNetworkResourcesRoutesToSync(peerID string) (bool, []*route.Route, map[string]struct{}) {
func (c *NetworkMapComponents) getNetworkResourcesRoutesToSync(peerID string) (bool, []*nmdata.Route, map[string]struct{}) {
var isRoutingPeer bool
var routes []*route.Route
var routes []*nmdata.Route
allSourcePeers := make(map[string]struct{})
for _, resource := range c.NetworkResources {
@@ -803,14 +811,17 @@ func (c *NetworkMapComponents) getNetworkResourcesRoutesToSync(peerID string) (b
func (c *NetworkMapComponents) processResourcePolicies(
peerID string,
resource *ComponentResource,
networkRoutingPeers map[string]*ComponentRouter,
resource *nmdata.NetworkResource,
networkRoutingPeers map[string]*nmdata.NetworkRouter,
addSourcePeers bool,
allSourcePeers map[string]struct{},
) []*route.Route {
var routes []*route.Route
) []*nmdata.Route {
var routes []*nmdata.Route
for _, policy := range c.ResourcePoliciesMap[resource.ID] {
if policy == nil || !policy.Enabled || len(policy.Rules) == 0 || policy.Rules[0] == nil {
continue
}
peers := c.getResourcePolicyPeers(policy)
if addSourcePeers {
for _, pID := range c.getPostureValidPeers(peers, policy.SourcePostureChecks) {
@@ -830,17 +841,17 @@ func (c *NetworkMapComponents) processResourcePolicies(
return routes
}
func (c *NetworkMapComponents) getResourcePolicyPeers(policy *Policy) []string {
if policy.Rules[0].SourceResource.Type == ResourceTypePeer && policy.Rules[0].SourceResource.ID != "" {
func (c *NetworkMapComponents) getResourcePolicyPeers(policy *nmdata.Policy) []string {
if policy.Rules[0].SourceResource.Type == string(ResourceTypePeer) && policy.Rules[0].SourceResource.ID != "" {
return []string{policy.Rules[0].SourceResource.ID}
}
return c.getUniquePeerIDsFromGroupsIDs(policy.SourceGroups())
}
func (c *NetworkMapComponents) getNetworkResourcesRoutes(resource *ComponentResource, peerID string, router *ComponentRouter) []*route.Route {
func (c *NetworkMapComponents) getNetworkResourcesRoutes(resource *nmdata.NetworkResource, peerID string, router *nmdata.NetworkRouter) []*nmdata.Route {
resourceAppliedPolicies := c.ResourcePoliciesMap[resource.ID]
var routes []*route.Route
var routes []*nmdata.Route
if len(resourceAppliedPolicies) > 0 {
peerInfo := c.GetPeerInfo(peerID)
if peerInfo != nil {
@@ -851,9 +862,9 @@ func (c *NetworkMapComponents) getNetworkResourcesRoutes(resource *ComponentReso
return routes
}
func (c *NetworkMapComponents) networkResourceToRoute(resource *ComponentResource, peer *ComponentPeer, router *ComponentRouter) *route.Route {
r := &route.Route{
ID: route.ID(resource.ID + ":" + peer.ID),
func (c *NetworkMapComponents) networkResourceToRoute(resource *nmdata.NetworkResource, peer *nmdata.Peer, router *nmdata.NetworkRouter) *nmdata.Route {
r := &nmdata.Route{
ID: resource.ID + ":" + peer.ID,
AccountID: resource.AccountID,
Peer: peer.Key,
PeerID: peer.ID,
@@ -861,24 +872,24 @@ func (c *NetworkMapComponents) networkResourceToRoute(resource *ComponentResourc
Masquerade: router.Masquerade,
Enabled: resource.Enabled,
KeepRoute: true,
NetID: route.NetID(resource.Name),
NetID: resource.Name,
Description: resource.Description,
}
if resource.Type == ComponentResourceHost || resource.Type == ComponentResourceSubnet {
if resource.Type == string(ResourceTypeHost) || resource.Type == string(ResourceTypeSubnet) {
r.Network = resource.Prefix
r.NetworkType = route.IPv4Network
r.NetworkType = nmdata.NetworkTypeIPv4
if resource.Prefix.Addr().Is6() {
r.NetworkType = route.IPv6Network
r.NetworkType = nmdata.NetworkTypeIPv6
}
}
if resource.Type == ComponentResourceDomain {
if resource.Type == string(ResourceTypeDomain) {
domainList, err := domain.FromStringList([]string{resource.Domain})
if err == nil {
r.Domains = domainList
r.NetworkType = route.DomainNetwork
r.NetworkType = nmdata.NetworkTypeDomain
r.Network = netip.PrefixFrom(netip.AddrFrom4([4]byte{192, 0, 2, 0}), 32)
}
}
@@ -896,7 +907,7 @@ func (c *NetworkMapComponents) getPostureValidPeers(inputPeers []string, posture
return dest
}
func (c *NetworkMapComponents) getPeerNetworkResourceFirewallRules(ctx context.Context, peerID string, routes []*route.Route, includeIPv6 bool) []*RouteFirewallRule {
func (c *NetworkMapComponents) getPeerNetworkResourceFirewallRules(ctx context.Context, peerID string, routes []*nmdata.Route, includeIPv6 bool) []*RouteFirewallRule {
routesFirewallRules := make([]*RouteFirewallRule, 0)
peerInfo := c.GetPeerInfo(peerID)
@@ -924,11 +935,17 @@ func (c *NetworkMapComponents) getPeerNetworkResourceFirewallRules(ctx context.C
return routesFirewallRules
}
func (c *NetworkMapComponents) getPoliciesSourcePeers(policies []*Policy) map[string]struct{} {
func (c *NetworkMapComponents) getPoliciesSourcePeers(policies []*nmdata.Policy) map[string]struct{} {
sourcePeers := make(map[string]struct{})
for _, policy := range policies {
if policy == nil {
continue
}
for _, rule := range policy.Rules {
if rule == nil {
continue
}
for _, sourceGroup := range rule.Sources {
group := c.GetGroupInfo(sourceGroup)
if group == nil {
@@ -940,7 +957,7 @@ func (c *NetworkMapComponents) getPoliciesSourcePeers(policies []*Policy) map[st
}
}
if rule.SourceResource.Type == ResourceTypePeer && rule.SourceResource.ID != "" {
if rule.SourceResource.Type == string(ResourceTypePeer) && rule.SourceResource.ID != "" {
sourcePeers[rule.SourceResource.ID] = struct{}{}
}
}
@@ -950,13 +967,13 @@ func (c *NetworkMapComponents) getPoliciesSourcePeers(policies []*Policy) map[st
}
func (c *NetworkMapComponents) addNetworksRoutingPeers(
networkResourcesRoutes []*route.Route,
networkResourcesRoutes []*nmdata.Route,
peerID string,
peersToConnect []*ComponentPeer,
expiredPeers []*ComponentPeer,
peersToConnect []*nmdata.Peer,
expiredPeers []*nmdata.Peer,
isRouter bool,
sourcePeers map[string]struct{},
) []*ComponentPeer {
) []*nmdata.Peer {
networkRoutesPeers := make(map[string]struct{}, len(networkResourcesRoutes))
for _, r := range networkResourcesRoutes {
@@ -1006,8 +1023,8 @@ type FirewallRuleContext struct {
PortsJoined string
}
func AppendIPv6FirewallRule(rules []*FirewallRule, rulesExists map[string]struct{}, peer, targetPeer *ComponentPeer, rule *PolicyRule, rc FirewallRuleContext) []*FirewallRule {
if !peer.IPv6.IsValid() || !targetPeer.SupportsIPv6 || !targetPeer.IPv6.IsValid() {
func AppendIPv6FirewallRule(rules []*FirewallRule, rulesExists map[string]struct{}, peer, targetPeer *nmdata.Peer, rule *nmdata.PolicyRule, rc FirewallRuleContext) []*FirewallRule {
if !peer.IPv6.IsValid() || !targetPeer.SupportsIPv6() || !targetPeer.IPv6.IsValid() {
return rules
}

View File

@@ -1,8 +1,7 @@
package types
import (
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/route"
nmdata "github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
type GroupCompact struct {
@@ -13,26 +12,26 @@ type GroupCompact struct {
type NetworkMapComponentsCompact struct {
PeerID string
Network *Network
AccountSettings *AccountSettingsInfo
DNSSettings *DNSSettings
Network *nmdata.Network
AccountSettings *nmdata.AccountSettingsInfo
DNSSettings *nmdata.DNSSettings
CustomZoneDomain string
AllPeers []*ComponentPeer
AllPeers []*nmdata.Peer
PeerIndexes []int
RouterPeerIndexes []int
Groups map[string]*GroupCompact
AllPolicies []*Policy
AllPolicies []*nmdata.Policy
PolicyIndexes []int
ResourcePoliciesMap map[string][]int
Routes []*route.Route
NameServerGroups []*nbdns.NameServerGroup
AllDNSRecords []nbdns.SimpleRecord
AccountZones []nbdns.CustomZone
Routes []*nmdata.Route
NameServerGroups []*nmdata.NameServerGroup
AllDNSRecords []nmdata.SimpleRecord
AccountZones []nmdata.CustomZone
RoutersMap map[string]map[string]*ComponentRouter
NetworkResources []*ComponentResource
RoutersMap map[string]map[string]*nmdata.NetworkRouter
NetworkResources []*nmdata.NetworkResource
GroupIDToUserIDs map[string][]string
AllowedUserIDs map[string]struct{}
@@ -41,7 +40,7 @@ type NetworkMapComponentsCompact struct {
func (c *NetworkMapComponents) ToCompact() *NetworkMapComponentsCompact {
peerToIndex := make(map[string]int)
var allPeers []*ComponentPeer
var allPeers []*nmdata.Peer
for id, peer := range c.Peers {
if _, exists := peerToIndex[id]; !exists {
@@ -81,8 +80,8 @@ func (c *NetworkMapComponents) ToCompact() *NetworkMapComponentsCompact {
}
}
policyToIndex := make(map[*Policy]int)
var allPolicies []*Policy
policyToIndex := make(map[*nmdata.Policy]int)
var allPolicies []*nmdata.Policy
for _, policy := range c.Policies {
if _, exists := policyToIndex[policy]; !exists {
@@ -147,7 +146,7 @@ func (c *NetworkMapComponents) ToCompact() *NetworkMapComponentsCompact {
}
func (c *NetworkMapComponentsCompact) ToFull() *NetworkMapComponents {
peers := make(map[string]*ComponentPeer, len(c.PeerIndexes))
peers := make(map[string]*nmdata.Peer, len(c.PeerIndexes))
for _, idx := range c.PeerIndexes {
if idx >= 0 && idx < len(c.AllPeers) {
peer := c.AllPeers[idx]
@@ -155,7 +154,7 @@ func (c *NetworkMapComponentsCompact) ToFull() *NetworkMapComponents {
}
}
routerPeers := make(map[string]*ComponentPeer, len(c.RouterPeerIndexes))
routerPeers := make(map[string]*nmdata.Peer, len(c.RouterPeerIndexes))
for _, idx := range c.RouterPeerIndexes {
if idx >= 0 && idx < len(c.AllPeers) {
peer := c.AllPeers[idx]
@@ -163,7 +162,7 @@ func (c *NetworkMapComponentsCompact) ToFull() *NetworkMapComponents {
}
}
groups := make(map[string]*ComponentGroup, len(c.Groups))
groups := make(map[string]*nmdata.Group, len(c.Groups))
for id, gc := range c.Groups {
peerIDs := make([]string, 0, len(gc.PeerIndexes))
for _, idx := range gc.PeerIndexes {
@@ -171,25 +170,24 @@ func (c *NetworkMapComponentsCompact) ToFull() *NetworkMapComponents {
peerIDs = append(peerIDs, c.AllPeers[idx].ID)
}
}
groups[id] = &ComponentGroup{
ID: id,
groups[id] = &nmdata.Group{
Name: gc.Name,
Peers: peerIDs,
}
}
policies := make([]*Policy, len(c.PolicyIndexes))
policies := make([]*nmdata.Policy, len(c.PolicyIndexes))
for i, idx := range c.PolicyIndexes {
if idx >= 0 && idx < len(c.AllPolicies) {
policies[i] = c.AllPolicies[idx]
}
}
var resourcePoliciesMap map[string][]*Policy
var resourcePoliciesMap map[string][]*nmdata.Policy
if len(c.ResourcePoliciesMap) > 0 {
resourcePoliciesMap = make(map[string][]*Policy, len(c.ResourcePoliciesMap))
resourcePoliciesMap = make(map[string][]*nmdata.Policy, len(c.ResourcePoliciesMap))
for resID, indexes := range c.ResourcePoliciesMap {
pols := make([]*Policy, 0, len(indexes))
pols := make([]*nmdata.Policy, 0, len(indexes))
for _, idx := range indexes {
if idx >= 0 && idx < len(c.AllPolicies) {
pols = append(pols, c.AllPolicies[idx])

View File

@@ -0,0 +1,70 @@
package types
import (
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
)
// This file holds the twin→real converters that survive the twin-NetworkMap
// refactor: only the DNS materialization. NetworkMap.DNSConfig stays a real
// nbdns.Config (the client DNS type), so Calculate converts the twin DNS
// components to nbdns at the output boundary. Peers/Routes/Network flow as
// twins all the way through and need no conversion.
func toRealNSGroup(n *nmdata.NameServerGroup) *nbdns.NameServerGroup {
if n == nil {
return nil
}
nameServers := make([]nbdns.NameServer, 0, len(n.NameServers))
for _, ns := range n.NameServers {
nameServers = append(nameServers, nbdns.NameServer{
IP: ns.IP,
NSType: nbdns.NameServerType(ns.NSType),
Port: ns.Port,
})
}
return &nbdns.NameServerGroup{
ID: n.ID,
Name: n.Name,
Description: n.Description,
NameServers: nameServers,
Groups: n.Groups,
Primary: n.Primary,
Domains: n.Domains,
Enabled: n.Enabled,
SearchDomainsEnabled: n.SearchDomainsEnabled,
}
}
func toRealRecords(recs []nmdata.SimpleRecord) []nbdns.SimpleRecord {
if recs == nil {
return nil
}
out := make([]nbdns.SimpleRecord, len(recs))
for i, r := range recs {
out[i] = nbdns.SimpleRecord{
Name: r.Name,
Type: r.Type,
Class: r.Class,
TTL: r.TTL,
RData: r.RData,
}
}
return out
}
func toRealZones(zones []nmdata.CustomZone) []nbdns.CustomZone {
if zones == nil {
return nil
}
out := make([]nbdns.CustomZone, len(zones))
for i, z := range zones {
out[i] = nbdns.CustomZone{
Domain: z.Domain,
Records: toRealRecords(z.Records),
SearchDomainDisabled: z.SearchDomainDisabled,
NonAuthoritative: z.NonAuthoritative,
}
}
return out
}

View File

@@ -1,268 +0,0 @@
package types
import (
"errors"
"fmt"
"strconv"
"strings"
)
const (
// PolicyTrafficActionAccept indicates that the traffic is accepted
PolicyTrafficActionAccept = PolicyTrafficActionType("accept")
// PolicyTrafficActionDrop indicates that the traffic is dropped
PolicyTrafficActionDrop = PolicyTrafficActionType("drop")
)
const (
// PolicyRuleProtocolALL type of traffic
PolicyRuleProtocolALL = PolicyRuleProtocolType("all")
// PolicyRuleProtocolTCP type of traffic
PolicyRuleProtocolTCP = PolicyRuleProtocolType("tcp")
// PolicyRuleProtocolUDP type of traffic
PolicyRuleProtocolUDP = PolicyRuleProtocolType("udp")
// PolicyRuleProtocolICMP type of traffic
PolicyRuleProtocolICMP = PolicyRuleProtocolType("icmp")
// PolicyRuleProtocolNetbirdSSH type of traffic
PolicyRuleProtocolNetbirdSSH = PolicyRuleProtocolType("netbird-ssh")
)
const (
// PolicyRuleFlowDirect allows traffic from source to destination
PolicyRuleFlowDirect = PolicyRuleDirection("direct")
// PolicyRuleFlowBidirect allows traffic to both directions
PolicyRuleFlowBidirect = PolicyRuleDirection("bidirect")
)
const (
// DefaultRuleName is a name for the Default rule that is created for every account
DefaultRuleName = "Default"
// DefaultRuleDescription is a description for the Default rule that is created for every account
DefaultRuleDescription = "This is a default rule that allows connections between all the resources"
// DefaultPolicyName is a name for the Default policy that is created for every account
DefaultPolicyName = "Default"
// DefaultPolicyDescription is a description for the Default policy that is created for every account
DefaultPolicyDescription = "This is a default policy that allows connections between all the resources"
)
// PolicyUpdateOperation operation object with type and values to be applied
type PolicyUpdateOperation struct {
Type PolicyUpdateOperationType
Values []string
}
// Policy of the Rego query
type Policy struct {
// ID of the policy'
ID string `gorm:"primaryKey"`
PublicID string `json:"-"`
// AccountID is a reference to Account that this object belongs
AccountID string `json:"-" gorm:"index"`
// Name of the Policy
Name string
// Description of the policy visible in the UI
Description string
// Enabled status of the policy
Enabled bool
// Rules of the policy
Rules []*PolicyRule `gorm:"foreignKey:PolicyID;references:id;constraint:OnDelete:CASCADE;"`
// SourcePostureChecks are ID references to Posture checks for policy source groups
SourcePostureChecks []string `gorm:"serializer:json"`
}
// Copy returns a copy of the policy.
func (p *Policy) Copy() *Policy {
c := &Policy{
ID: p.ID,
AccountID: p.AccountID,
PublicID: p.PublicID,
Name: p.Name,
Description: p.Description,
Enabled: p.Enabled,
Rules: make([]*PolicyRule, len(p.Rules)),
SourcePostureChecks: make([]string, len(p.SourcePostureChecks)),
}
for i, r := range p.Rules {
c.Rules[i] = r.Copy()
}
copy(c.SourcePostureChecks, p.SourcePostureChecks)
return c
}
func (p *Policy) Equal(other *Policy) bool {
if p == nil || other == nil {
return p == other
}
if p.ID != other.ID ||
p.AccountID != other.AccountID ||
p.Name != other.Name ||
p.Description != other.Description ||
p.Enabled != other.Enabled {
return false
}
if !stringSlicesEqualUnordered(p.SourcePostureChecks, other.SourcePostureChecks) {
return false
}
if len(p.Rules) != len(other.Rules) {
return false
}
otherRules := make(map[string]*PolicyRule, len(other.Rules))
for _, r := range other.Rules {
otherRules[r.ID] = r
}
for _, r := range p.Rules {
otherRule, ok := otherRules[r.ID]
if !ok {
return false
}
if !r.Equal(otherRule) {
return false
}
}
return true
}
// EventMeta returns activity event meta related to this policy
func (p *Policy) EventMeta() map[string]any {
return map[string]any{"name": p.Name}
}
// UpgradeAndFix different version of policies to latest version
func (p *Policy) UpgradeAndFix() {
for _, r := range p.Rules {
// start migrate from version v0.20.3
if r.Protocol == "" {
r.Protocol = PolicyRuleProtocolALL
}
if r.Protocol == PolicyRuleProtocolALL && !r.Bidirectional {
r.Bidirectional = true
}
// -- v0.20.4
}
}
// RuleGroups returns a list of all groups referenced in the policy's rules,
// including sources and destinations.
func (p *Policy) RuleGroups() []string {
groups := make([]string, 0)
for _, rule := range p.Rules {
groups = append(groups, rule.Sources...)
groups = append(groups, rule.Destinations...)
}
return groups
}
// SourceGroups returns a slice of all unique source groups referenced in the policy's rules.
func (p *Policy) SourceGroups() []string {
if len(p.Rules) == 1 {
return p.Rules[0].Sources
}
groups := make(map[string]struct{}, len(p.Rules))
for _, rule := range p.Rules {
for _, source := range rule.Sources {
groups[source] = struct{}{}
}
}
groupIDs := make([]string, 0, len(groups))
for groupID := range groups {
groupIDs = append(groupIDs, groupID)
}
return groupIDs
}
func ParseRuleString(rule string) (PolicyRuleProtocolType, RulePortRange, error) {
rule = strings.TrimSpace(strings.ToLower(rule))
if rule == "all" {
return PolicyRuleProtocolALL, RulePortRange{}, nil
}
if rule == "icmp" {
return PolicyRuleProtocolICMP, RulePortRange{}, nil
}
split := strings.Split(rule, "/")
if len(split) != 2 {
return "", RulePortRange{}, errors.New("invalid rule format: expected protocol/port or protocol/port-range")
}
protoStr := strings.TrimSpace(split[0])
portStr := strings.TrimSpace(split[1])
var protocol PolicyRuleProtocolType
switch protoStr {
case "tcp":
protocol = PolicyRuleProtocolTCP
case "udp":
protocol = PolicyRuleProtocolUDP
case "icmp":
return "", RulePortRange{}, errors.New("icmp does not accept ports; use 'icmp' without '/…'")
case "netbird-ssh":
return PolicyRuleProtocolNetbirdSSH, RulePortRange{Start: nativeSSHPortNumber, End: nativeSSHPortNumber}, nil
default:
return "", RulePortRange{}, fmt.Errorf("invalid protocol: %q", protoStr)
}
portRange, err := parsePortRange(portStr)
if err != nil {
return "", RulePortRange{}, err
}
return protocol, portRange, nil
}
func parsePortRange(portStr string) (RulePortRange, error) {
if strings.Contains(portStr, "-") {
rangeParts := strings.Split(portStr, "-")
if len(rangeParts) != 2 {
return RulePortRange{}, fmt.Errorf("invalid port range %q", portStr)
}
start, err := parsePort(strings.TrimSpace(rangeParts[0]))
if err != nil {
return RulePortRange{}, err
}
end, err := parsePort(strings.TrimSpace(rangeParts[1]))
if err != nil {
return RulePortRange{}, err
}
if start > end {
return RulePortRange{}, fmt.Errorf("invalid port range: start %d > end %d", start, end)
}
return RulePortRange{Start: uint16(start), End: uint16(end)}, nil
}
p, err := parsePort(portStr)
if err != nil {
return RulePortRange{}, err
}
return RulePortRange{Start: uint16(p), End: uint16(p)}, nil
}
func parsePort(portStr string) (int, error) {
if portStr == "" {
return 0, errors.New("empty port")
}
p, err := strconv.Atoi(portStr)
if err != nil {
return 0, fmt.Errorf("invalid port %q: %w", portStr, err)
}
if p < 1 || p > 65535 {
return 0, fmt.Errorf("port out of range (165535): %d", p)
}
return p, nil
}

View File

@@ -1,22 +1,39 @@
package types
import (
"slices"
"errors"
"fmt"
"strconv"
"strings"
"github.com/netbirdio/netbird/shared/management/proto"
)
// PolicyUpdateOperationType operation type
type PolicyUpdateOperationType int
// PolicyTrafficActionType action type for the firewall
type PolicyTrafficActionType string
// PolicyRuleProtocolType type of traffic
type PolicyRuleProtocolType string
// PolicyRuleDirection direction of traffic
type PolicyRuleDirection string
const (
// PolicyTrafficActionAccept indicates that the traffic is accepted
PolicyTrafficActionAccept = PolicyTrafficActionType("accept")
// PolicyTrafficActionDrop indicates that the traffic is dropped
PolicyTrafficActionDrop = PolicyTrafficActionType("drop")
)
const (
// PolicyRuleProtocolALL type of traffic
PolicyRuleProtocolALL = PolicyRuleProtocolType("all")
// PolicyRuleProtocolTCP type of traffic
PolicyRuleProtocolTCP = PolicyRuleProtocolType("tcp")
// PolicyRuleProtocolUDP type of traffic
PolicyRuleProtocolUDP = PolicyRuleProtocolType("udp")
// PolicyRuleProtocolICMP type of traffic
PolicyRuleProtocolICMP = PolicyRuleProtocolType("icmp")
// PolicyRuleProtocolNetbirdSSH type of traffic
PolicyRuleProtocolNetbirdSSH = PolicyRuleProtocolType("netbird-ssh")
)
// RulePortRange represents a range of ports for a firewall rule.
type RulePortRange struct {
@@ -39,187 +56,84 @@ func (r *RulePortRange) Equal(other *RulePortRange) bool {
return r.Start == other.Start && r.End == other.End
}
// PolicyRule is the metadata of the policy
type PolicyRule struct {
// ID of the policy rule
ID string `gorm:"primaryKey"`
func ParseRuleString(rule string) (PolicyRuleProtocolType, RulePortRange, error) {
rule = strings.TrimSpace(strings.ToLower(rule))
if rule == "all" {
return PolicyRuleProtocolALL, RulePortRange{}, nil
}
if rule == "icmp" {
return PolicyRuleProtocolICMP, RulePortRange{}, nil
}
// PolicyID is a reference to Policy that this object belongs
PolicyID string `json:"-" gorm:"index"`
split := strings.Split(rule, "/")
if len(split) != 2 {
return "", RulePortRange{}, errors.New("invalid rule format: expected protocol/port or protocol/port-range")
}
// Name of the rule visible in the UI
Name string
protoStr := strings.TrimSpace(split[0])
portStr := strings.TrimSpace(split[1])
// Description of the rule visible in the UI
Description string
var protocol PolicyRuleProtocolType
switch protoStr {
case "tcp":
protocol = PolicyRuleProtocolTCP
case "udp":
protocol = PolicyRuleProtocolUDP
case "icmp":
return "", RulePortRange{}, errors.New("icmp does not accept ports; use 'icmp' without '/…'")
case "netbird-ssh":
return PolicyRuleProtocolNetbirdSSH, RulePortRange{Start: nativeSSHPortNumber, End: nativeSSHPortNumber}, nil
default:
return "", RulePortRange{}, fmt.Errorf("invalid protocol: %q", protoStr)
}
// Enabled status of rule in the system
Enabled bool
portRange, err := parsePortRange(portStr)
if err != nil {
return "", RulePortRange{}, err
}
// Action policy accept or drops packets
Action PolicyTrafficActionType
// Destinations policy destination groups
Destinations []string `gorm:"serializer:json"`
// DestinationResource policy destination resource that the rule is applied to
DestinationResource Resource `gorm:"serializer:json"`
// Sources policy source groups
Sources []string `gorm:"serializer:json"`
// SourceResource policy source resource that the rule is applied to
SourceResource Resource `gorm:"serializer:json"`
// Bidirectional define if the rule is applicable in both directions, sources, and destinations
Bidirectional bool
// Protocol type of the traffic
Protocol PolicyRuleProtocolType
// Ports or it ranges list
Ports []string `gorm:"serializer:json"`
// PortRanges a list of port ranges.
PortRanges []RulePortRange `gorm:"serializer:json"`
// AuthorizedGroups is a map of groupIDs and their respective access to local users via ssh
AuthorizedGroups map[string][]string `gorm:"serializer:json"`
// AuthorizedUser is a list of userIDs that are authorized to access local resources via ssh
AuthorizedUser string
return protocol, portRange, nil
}
// Copy returns a copy of a policy rule
func (pm *PolicyRule) Copy() *PolicyRule {
rule := &PolicyRule{
ID: pm.ID,
PolicyID: pm.PolicyID,
Name: pm.Name,
Description: pm.Description,
Enabled: pm.Enabled,
Action: pm.Action,
Destinations: make([]string, len(pm.Destinations)),
DestinationResource: pm.DestinationResource,
Sources: make([]string, len(pm.Sources)),
SourceResource: pm.SourceResource,
Bidirectional: pm.Bidirectional,
Protocol: pm.Protocol,
Ports: make([]string, len(pm.Ports)),
PortRanges: make([]RulePortRange, len(pm.PortRanges)),
AuthorizedGroups: make(map[string][]string, len(pm.AuthorizedGroups)),
AuthorizedUser: pm.AuthorizedUser,
}
copy(rule.Destinations, pm.Destinations)
copy(rule.Sources, pm.Sources)
copy(rule.Ports, pm.Ports)
copy(rule.PortRanges, pm.PortRanges)
for k, v := range pm.AuthorizedGroups {
rule.AuthorizedGroups[k] = make([]string, len(v))
copy(rule.AuthorizedGroups[k], v)
}
return rule
}
func (pm *PolicyRule) Equal(other *PolicyRule) bool {
if pm == nil || other == nil {
return pm == other
}
if pm.ID != other.ID ||
pm.PolicyID != other.PolicyID ||
pm.Name != other.Name ||
pm.Description != other.Description ||
pm.Enabled != other.Enabled ||
pm.Action != other.Action ||
pm.Bidirectional != other.Bidirectional ||
pm.Protocol != other.Protocol ||
pm.SourceResource != other.SourceResource ||
pm.DestinationResource != other.DestinationResource ||
pm.AuthorizedUser != other.AuthorizedUser {
return false
}
if !stringSlicesEqualUnordered(pm.Sources, other.Sources) {
return false
}
if !stringSlicesEqualUnordered(pm.Destinations, other.Destinations) {
return false
}
if !stringSlicesEqualUnordered(pm.Ports, other.Ports) {
return false
}
if !portRangeSlicesEqualUnordered(pm.PortRanges, other.PortRanges) {
return false
}
if !authorizedGroupsEqual(pm.AuthorizedGroups, other.AuthorizedGroups) {
return false
}
return true
}
func stringSlicesEqualUnordered(a, b []string) bool {
if len(a) != len(b) {
return false
}
if len(a) == 0 {
return true
}
sorted1 := make([]string, len(a))
sorted2 := make([]string, len(b))
copy(sorted1, a)
copy(sorted2, b)
slices.Sort(sorted1)
slices.Sort(sorted2)
return slices.Equal(sorted1, sorted2)
}
func portRangeSlicesEqualUnordered(a, b []RulePortRange) bool {
if len(a) != len(b) {
return false
}
if len(a) == 0 {
return true
}
cmp := func(x, y RulePortRange) int {
if x.Start != y.Start {
if x.Start < y.Start {
return -1
}
return 1
func parsePortRange(portStr string) (RulePortRange, error) {
if strings.Contains(portStr, "-") {
rangeParts := strings.Split(portStr, "-")
if len(rangeParts) != 2 {
return RulePortRange{}, fmt.Errorf("invalid port range %q", portStr)
}
if x.End != y.End {
if x.End < y.End {
return -1
}
return 1
start, err := parsePort(strings.TrimSpace(rangeParts[0]))
if err != nil {
return RulePortRange{}, err
}
return 0
end, err := parsePort(strings.TrimSpace(rangeParts[1]))
if err != nil {
return RulePortRange{}, err
}
if start > end {
return RulePortRange{}, fmt.Errorf("invalid port range: start %d > end %d", start, end)
}
return RulePortRange{Start: uint16(start), End: uint16(end)}, nil
}
sorted1 := make([]RulePortRange, len(a))
sorted2 := make([]RulePortRange, len(b))
copy(sorted1, a)
copy(sorted2, b)
slices.SortFunc(sorted1, cmp)
slices.SortFunc(sorted2, cmp)
return slices.EqualFunc(sorted1, sorted2, func(x, y RulePortRange) bool {
return x.Start == y.Start && x.End == y.End
})
p, err := parsePort(portStr)
if err != nil {
return RulePortRange{}, err
}
return RulePortRange{Start: uint16(p), End: uint16(p)}, nil
}
func authorizedGroupsEqual(a, b map[string][]string) bool {
if len(a) != len(b) {
return false
func parsePort(portStr string) (int, error) {
if portStr == "" {
return 0, errors.New("empty port")
}
for k, va := range a {
vb, ok := b[k]
if !ok {
return false
}
if !stringSlicesEqualUnordered(va, vb) {
return false
}
p, err := strconv.Atoi(portStr)
if err != nil {
return 0, fmt.Errorf("invalid port %q: %w", portStr, err)
}
return true
if p < 1 || p > 65535 {
return 0, fmt.Errorf("port out of range (165535): %d", p)
}
return p, nil
}

View File

@@ -1,9 +1,5 @@
package types
import (
"github.com/netbirdio/netbird/shared/management/http/api"
)
type ResourceType string
const (
@@ -13,27 +9,11 @@ const (
ResourceTypeSubnet ResourceType = "subnet"
)
type Resource struct {
ID string
Type ResourceType
}
func (r *Resource) ToAPIResponse() *api.Resource {
if r.ID == "" && r.Type == "" {
return nil
}
return &api.Resource{
Id: r.ID,
Type: api.ResourceType(r.Type),
func (t ResourceType) Valid() bool {
switch t {
case ResourceTypePeer, ResourceTypeDomain, ResourceTypeHost, ResourceTypeSubnet:
return true
default:
return false
}
}
func (r *Resource) FromAPIRequest(req *api.Resource) {
if req == nil {
return
}
r.ID = req.Id
r.Type = ResourceType(req.Type)
}