diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index 9e2afc6ac..cd900d2eb 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -377,9 +377,9 @@ func (c *Controller) sendUpdatesFromData(ctx context.Context, accountID string, dnsCache := &cache.DNSConfigCache{} dnsDomain := c.getDNSDomainFromData(nmData.AccountSettings) - peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, ipv6AllowedPeersFromData(nmData)) + peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, IPv6AllowedPeersFromData(nmData)) - dnsFwdPort := computeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) + dnsFwdPort := ComputeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) var wg sync.WaitGroup semaphore := make(chan struct{}, 10) @@ -433,7 +433,7 @@ func (c *Controller) sendUpdatesFromData(ctx context.Context, accountID string, return } - nmap := networkMapFromData(ctx, nmData, p.ID, peersCustomZone) + nmap := NetworkMapFromData(ctx, nmData, p.ID, peersCustomZone) c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start)) @@ -477,7 +477,7 @@ func (c *Controller) getDNSDomainFromData(settings *nmdata.AccountSettingsInfo) return settings.DNSDomain } -func ipv6AllowedPeersFromData(nmData *networkmap.NetworkMapData) map[string]struct{} { +func IPv6AllowedPeersFromData(nmData *networkmap.NetworkMapData) map[string]struct{} { result := make(map[string]struct{}) if nmData.AccountSettings != nil { for _, groupID := range nmData.AccountSettings.IPv6EnabledGroups { @@ -498,7 +498,7 @@ func ipv6AllowedPeersFromData(nmData *networkmap.NetworkMapData) map[string]stru return result } -func networkMapFromData(ctx context.Context, nmData *networkmap.NetworkMapData, peerID string, peersCustomZone nmdata.CustomZone) *types.NetworkMap { +func NetworkMapFromData(ctx context.Context, nmData *networkmap.NetworkMapData, peerID string, peersCustomZone nmdata.CustomZone) *types.NetworkMap { components := nmData.GetPeerNetworkMapComponents(peerID, peersCustomZone) if components.IsEmpty() { return &types.NetworkMap{Network: components.Network} @@ -971,10 +971,10 @@ func (c *Controller) getValidatedPeerWithComponentsFromData(ctx context.Context, postureChecks := peerPostureChecksFromData(nmData, peer.ID) dnsDomain := c.getDNSDomainFromData(nmData.AccountSettings) - peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, ipv6AllowedPeersFromData(nmData)) + peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, IPv6AllowedPeersFromData(nmData)) components := nmData.GetPeerNetworkMapComponents(peer.ID, peersCustomZone) - dnsFwdPort := computeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) + dnsFwdPort := ComputeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) return peer, components, nil, postureChecks, dnsFwdPort, nil } @@ -1148,10 +1148,10 @@ func (c *Controller) getValidatedPeerWithMapFromData(ctx context.Context, accoun postureChecks := peerPostureChecksFromData(nmData, peerID) dnsDomain := c.getDNSDomainFromData(nmData.AccountSettings) - peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, ipv6AllowedPeersFromData(nmData)) + peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, IPv6AllowedPeersFromData(nmData)) - networkMap := networkMapFromData(ctx, nmData, peerID, peersCustomZone) - dnsFwdPort := computeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) + networkMap := NetworkMapFromData(ctx, nmData, peerID, peersCustomZone) + dnsFwdPort := ComputeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) return networkMap, postureChecks, dnsFwdPort, nil } @@ -1225,7 +1225,7 @@ func computeForwarderPort(peers []*nbpeer.Peer, requiredVersion string) int64 { return computeForwarderPortFromVersions(versions, requiredVersion) } -func computeForwarderPortFromData(peers map[string]*nmdata.Peer, requiredVersion string) int64 { +func ComputeForwarderPortFromData(peers map[string]*nmdata.Peer, requiredVersion string) int64 { versions := make([]string, 0, len(peers)) for _, peer := range peers { versions = append(versions, peer.Meta.WtVersion) diff --git a/management/internals/controllers/network_map/nmaptest/canonicalize.go b/management/internals/controllers/network_map/nmaptest/canonicalize.go new file mode 100644 index 000000000..ec6614d81 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/canonicalize.go @@ -0,0 +1,380 @@ +package nmaptest + +import ( + "bytes" + "cmp" + "fmt" + "slices" + "sort" + "strconv" + "strings" + + "github.com/netbirdio/netbird/shared/management/proto" +) + +// normalizeIDSpace replaces policy and route identifiers with positional +// placeholders so a comparison can reach everything else. +// +// This exists only because the envelope round-trip currently substitutes each +// internal xid with the object's public id, which is a tracked defect and not a +// licence to differ: those identifiers reach the server again inside flow +// events, which resolve them by internal id, so the substitution silently +// breaks flow attribution for component-format peers. TestIDSpaceMatches +// asserts the equality that must eventually hold; this erasure keeps the other +// 40-odd cases reporting on semantics meanwhile. When the id space is unified, +// delete this and the calls to it — every case should still pass. +// +// Cardinality and cross-references survive the erasure: two rules under one +// policy still share a token and a route firewall rule still points at its +// route, so a path that drops a policy, merges two policies, or misattributes a +// rule to the wrong route still fails. +func normalizeIDSpace(nm *proto.NetworkMap) { + if nm == nil { + return + } + policies := newTokenizer("policy") + routes := newTokenizer("route") + + for _, i := range orderBy(nm.Routes, routeKeyWithoutID) { + nm.Routes[i].ID = routes.get(nm.Routes[i].ID) + } + for _, i := range orderBy(nm.FirewallRules, firewallKeyWithoutPolicy) { + r := nm.FirewallRules[i] + if len(r.PolicyID) > 0 { + r.PolicyID = []byte(policies.get(string(r.PolicyID))) + } + } + for _, i := range orderBy(nm.RoutesFirewallRules, routeFirewallKeyWithoutIDs) { + r := nm.RoutesFirewallRules[i] + if len(r.PolicyID) > 0 { + r.PolicyID = []byte(policies.get(string(r.PolicyID))) + } + r.RouteID = routes.get(r.RouteID) + } +} + +// tokenizer maps identifiers to positional placeholders in order of first use. +type tokenizer struct { + prefix string + seen map[string]string +} + +func newTokenizer(prefix string) *tokenizer { + return &tokenizer{prefix: prefix, seen: make(map[string]string)} +} + +func (t *tokenizer) get(id string) string { + if id == "" { + return "" + } + if tok, ok := t.seen[id]; ok { + return tok + } + tok := fmt.Sprintf("%s#%d", t.prefix, len(t.seen)) + t.seen[id] = tok + return tok +} + +// orderBy returns indices sorted by key, so placeholder numbering does not +// depend on the identifiers being erased. +func orderBy[T any](items []T, key func(T) string) []int { + idx := make([]int, len(items)) + for i := range idx { + idx[i] = i + } + sort.SliceStable(idx, func(a, b int) bool { return key(items[idx[a]]) < key(items[idx[b]]) }) + return idx +} + +func routeKeyWithoutID(r *proto.Route) string { + if r == nil { + return "" + } + return fmt.Sprintf("%s|%s|%s|%d|%d|%t|%t|%v", + r.Network, r.NetID, r.Peer, r.Metric, r.NetworkType, r.Masquerade, r.KeepRoute, r.Domains) +} + +func firewallKeyWithoutPolicy(r *proto.FirewallRule) string { + if r == nil { + return "" + } + return fmt.Sprintf("%s|%d|%d|%d|%s|%s|%v", + r.PeerIP, r.Direction, r.Action, r.Protocol, r.Port, portInfoKey(r.PortInfo), r.SourcePrefixes) //nolint:staticcheck +} + +func routeFirewallKeyWithoutIDs(r *proto.RouteFirewallRule) string { + if r == nil { + return "" + } + return fmt.Sprintf("%s|%d|%d|%s|%v|%v|%t|%d", + r.Destination, r.Protocol, r.Action, portInfoKey(r.PortInfo), r.Domains, r.SourceRanges, r.IsDynamic, r.CustomProtocol) +} + +// canonicalize sorts every repeated field of the NetworkMap by a stable key. +// The producing paths iterate Go maps while building these slices, so order +// can differ between runs even when the content is identical; comparing +// without this reports noise. +func canonicalize(nm *proto.NetworkMap) { + if nm == nil { + return + } + slices.SortFunc(nm.RemotePeers, cmpRemotePeer) + slices.SortFunc(nm.OfflinePeers, cmpRemotePeer) + slices.SortFunc(nm.Routes, cmpRoute) + slices.SortFunc(nm.FirewallRules, cmpFirewallRule) + slices.SortFunc(nm.RoutesFirewallRules, cmpRouteFirewallRule) + slices.SortFunc(nm.ForwardingRules, cmpForwardingRule) + + for _, r := range nm.FirewallRules { + slices.SortFunc(r.SourcePrefixes, bytes.Compare) + } + for _, r := range nm.RoutesFirewallRules { + slices.Sort(r.SourceRanges) + } + canonicalizeDNSConfig(nm.DNSConfig) + canonicalizeSSHAuth(nm.SshAuth) +} + +func canonicalizeDNSConfig(d *proto.DNSConfig) { + if d == nil { + return + } + for _, g := range d.NameServerGroups { + if g == nil { + continue + } + slices.Sort(g.Domains) + slices.SortFunc(g.NameServers, func(a, b *proto.NameServer) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + if c := cmp.Compare(a.IP, b.IP); c != 0 { + return c + } + if c := cmp.Compare(a.Port, b.Port); c != 0 { + return c + } + return cmp.Compare(a.NSType, b.NSType) + }) + } + slices.SortFunc(d.NameServerGroups, func(a, b *proto.NameServerGroup) int { + return cmp.Compare(nsgKey(a), nsgKey(b)) + }) + for _, z := range d.CustomZones { + if z == nil { + continue + } + slices.SortFunc(z.Records, cmpSimpleRecord) + } + slices.SortFunc(d.CustomZones, func(a, b *proto.CustomZone) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + return cmp.Compare(a.Domain, b.Domain) + }) +} + +// canonicalizeSSHAuth sorts AuthorizedUsers and re-keys MachineUsers.Indexes +// against the new ordering, preserving which machine user maps to which hashes. +func canonicalizeSSHAuth(s *proto.SSHAuth) { + if s == nil || len(s.AuthorizedUsers) == 0 { + return + } + type hashed struct { + bytes []byte + old uint32 + } + entries := make([]hashed, len(s.AuthorizedUsers)) + for i, b := range s.AuthorizedUsers { + entries[i] = hashed{bytes: b, old: uint32(i)} + } + slices.SortFunc(entries, func(a, b hashed) int { return bytes.Compare(a.bytes, b.bytes) }) + + remap := make(map[uint32]uint32, len(entries)) + sorted := make([][]byte, len(entries)) + for newIdx, e := range entries { + remap[e.old] = uint32(newIdx) + sorted[newIdx] = e.bytes + } + s.AuthorizedUsers = sorted + + for _, mu := range s.MachineUsers { + if mu == nil { + continue + } + for i, oldIdx := range mu.Indexes { + if newIdx, ok := remap[oldIdx]; ok { + mu.Indexes[i] = newIdx + } + } + slices.Sort(mu.Indexes) + } +} + +func boolCmp(a, b bool) int { + if a == b { + return 0 + } + if a { + return 1 + } + return -1 +} + +func nsgKey(g *proto.NameServerGroup) string { + if g == nil { + return "" + } + var parts []string + for _, ns := range g.NameServers { + if ns == nil { + continue + } + parts = append(parts, ns.IP+":"+strconv.FormatInt(ns.Port, 10)+":"+strconv.FormatInt(ns.NSType, 10)) + } + slices.Sort(parts) + key := strings.Join(parts, ",") + domains := append([]string(nil), g.Domains...) + slices.Sort(domains) + key += "|" + strings.Join(domains, "|") + if g.Primary { + key += "|P" + } + if g.SearchDomainsEnabled { + key += "|S" + } + return key +} + +func cmpSimpleRecord(a, b *proto.SimpleRecord) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + if c := cmp.Compare(a.Name, b.Name); c != 0 { + return c + } + if c := cmp.Compare(a.Type, b.Type); c != 0 { + return c + } + if c := cmp.Compare(a.Class, b.Class); c != 0 { + return c + } + if c := cmp.Compare(a.RData, b.RData); c != 0 { + return c + } + return cmp.Compare(a.TTL, b.TTL) +} + +func cmpRemotePeer(a, b *proto.RemotePeerConfig) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + return cmp.Compare(a.WgPubKey, b.WgPubKey) +} + +func cmpRoute(a, b *proto.Route) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + if c := cmp.Compare(a.ID, b.ID); c != 0 { + return c + } + if c := cmp.Compare(a.NetID, b.NetID); c != 0 { + return c + } + if c := cmp.Compare(a.Network, b.Network); c != 0 { + return c + } + if c := cmp.Compare(a.Peer, b.Peer); c != 0 { + return c + } + if c := cmp.Compare(a.Metric, b.Metric); c != 0 { + return c + } + return slices.Compare(a.Domains, b.Domains) +} + +func cmpFirewallRule(a, b *proto.FirewallRule) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + if c := bytes.Compare(a.PolicyID, b.PolicyID); c != 0 { + return c + } + if c := cmp.Compare(a.PeerIP, b.PeerIP); c != 0 { //nolint:staticcheck + return c + } + if c := cmp.Compare(int32(a.Direction), int32(b.Direction)); c != 0 { + return c + } + if c := cmp.Compare(int32(a.Action), int32(b.Action)); c != 0 { + return c + } + if c := cmp.Compare(int32(a.Protocol), int32(b.Protocol)); c != 0 { + return c + } + if c := cmp.Compare(a.Port, b.Port); c != 0 { + return c + } + return cmp.Compare(portInfoKey(a.PortInfo), portInfoKey(b.PortInfo)) +} + +func cmpRouteFirewallRule(a, b *proto.RouteFirewallRule) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + if c := bytes.Compare(a.PolicyID, b.PolicyID); c != 0 { + return c + } + if c := cmp.Compare(a.RouteID, b.RouteID); c != 0 { + return c + } + if c := cmp.Compare(a.Destination, b.Destination); c != 0 { + return c + } + if c := cmp.Compare(int32(a.Protocol), int32(b.Protocol)); c != 0 { + return c + } + if c := cmp.Compare(portInfoKey(a.PortInfo), portInfoKey(b.PortInfo)); c != 0 { + return c + } + if c := cmp.Compare(int32(a.Action), int32(b.Action)); c != 0 { + return c + } + if c := slices.Compare(a.Domains, b.Domains); c != 0 { + return c + } + if c := slices.Compare(a.SourceRanges, b.SourceRanges); c != 0 { + return c + } + if c := cmp.Compare(a.CustomProtocol, b.CustomProtocol); c != 0 { + return c + } + return boolCmp(a.IsDynamic, b.IsDynamic) +} + +func cmpForwardingRule(a, b *proto.ForwardingRule) int { + if a == nil || b == nil { + return boolCmp(a == nil, b == nil) + } + if c := cmp.Compare(int32(a.Protocol), int32(b.Protocol)); c != 0 { + return c + } + return bytes.Compare(a.TranslatedAddress, b.TranslatedAddress) +} + +func portInfoKey(pi *proto.PortInfo) string { + if pi == nil { + return "" + } + switch sel := pi.PortSelection.(type) { + case *proto.PortInfo_Port: + return "P" + strconv.FormatUint(uint64(sel.Port), 10) + case *proto.PortInfo_Range_: + if sel.Range == nil { + return "R" + } + return "R" + strconv.FormatUint(uint64(sel.Range.Start), 10) + "-" + strconv.FormatUint(uint64(sel.Range.End), 10) + } + return "" +} diff --git a/management/internals/controllers/network_map/nmaptest/fixture.go b/management/internals/controllers/network_map/nmaptest/fixture.go new file mode 100644 index 000000000..d56285f95 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/fixture.go @@ -0,0 +1,218 @@ +package nmaptest + +import ( + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "os" + + "github.com/netbirdio/netbird/shared/management/networkmap" + "github.com/netbirdio/netbird/shared/management/networkmap/nmdata" +) + +// LoadNetworkMapData reads a fixture holding the NetworkMapData the store +// would return for one account. Unknown fields are rejected so fixture typos +// fail loudly instead of silently testing a default. +func LoadNetworkMapData(path string) (*networkmap.NetworkMapData, error) { + f, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("open fixture: %w", err) + } + defer f.Close() + + dec := json.NewDecoder(f) + dec.DisallowUnknownFields() + var nmData networkmap.NetworkMapData + if err := dec.Decode(&nmData); err != nil { + return nil, fmt.Errorf("decode fixture %s: %w", path, err) + } + return &nmData, nil +} + +var defaultNetworkNet = func() net.IPNet { + _, ipnet, err := net.ParseCIDR("100.64.0.0/10") + if err != nil { + panic(err) + } + return *ipnet +}() + +// applyFixtureDefaults fills the boilerplate a fixture may omit. Map-keyed +// objects inherit their key as ID, peers get a deterministic WG-shaped key +// and their ID as DNS label, PublicIDs default to the internal ID (the +// envelope encoder puts public IDs on the wire and silently degrades on +// empty ones), and a nil ValidatedPeers validates every peer — production +// fills it through the integrated validator, not the store. +func applyFixtureDefaults(nmData *networkmap.NetworkMapData) { + if nmData.Network == nil { + nmData.Network = &nmdata.Network{} + } + if nmData.Network.Identifier == "" { + nmData.Network.Identifier = "network" + } + if nmData.Network.Net.IP == nil { + nmData.Network.Net = defaultNetworkNet + } + if nmData.AccountSettings == nil { + nmData.AccountSettings = &nmdata.AccountSettingsInfo{} + } + if nmData.DNSSettings == nil { + nmData.DNSSettings = &nmdata.DNSSettings{} + } + + for id, p := range nmData.Peers { + if p == nil { + continue + } + if p.ID == "" { + p.ID = id + } + if p.Key == "" { + p.Key = derivedWgKey(p.ID) + } + if p.DNSLabel == "" { + p.DNSLabel = p.ID + } + } + + for id, g := range nmData.Groups { + if g == nil { + continue + } + if g.ID == "" { + g.ID = id + } + if g.Name == "" { + g.Name = g.ID + } + if g.PublicID == "" { + g.PublicID = g.ID + } + } + + for _, policy := range nmData.Policies { + defaultPolicyIDs(policy) + } + resolveResourcePolicyRefs(nmData) + + for _, r := range nmData.Routes { + if r != nil && r.PublicID == "" { + r.PublicID = r.ID + } + } + for _, nsg := range nmData.NameServerGroups { + if nsg != nil && nsg.PublicID == "" { + nsg.PublicID = nsg.ID + } + } + for _, res := range nmData.NetworkResources { + if res == nil { + continue + } + if res.PublicID == "" { + res.PublicID = res.ID + } + defaultXIDMapping(&nmData.NetworkXIDToPublicID, res.NetworkID) + } + for networkID, routers := range nmData.Routers { + defaultXIDMapping(&nmData.NetworkXIDToPublicID, networkID) + for _, router := range routers { + if router != nil && router.PublicID == "" { + router.PublicID = networkID + } + } + } + + for id, pc := range nmData.PostureChecks { + if pc == nil { + continue + } + if pc.ID == "" { + pc.ID = id + } + defaultXIDMapping(&nmData.PostureCheckXIDToPublicID, pc.ID) + } + + if nmData.ValidatedPeers == nil { + nmData.ValidatedPeers = make(map[string]struct{}, len(nmData.Peers)) + for id := range nmData.Peers { + nmData.ValidatedPeers[id] = struct{}{} + } + } +} + +// resolveResourcePolicyRefs lets a fixture name an account policy by ID in +// ResourcePolicies — {"ID": "pol-x"} with no rules — instead of repeating it. +// The real store puts the same policy pointer in both places, which is what +// resolving the reference reproduces. +func resolveResourcePolicyRefs(nmData *networkmap.NetworkMapData) { + byID := make(map[string]*nmdata.Policy, len(nmData.Policies)) + for _, policy := range nmData.Policies { + if policy != nil && policy.ID != "" { + byID[policy.ID] = policy + } + } + + for _, policies := range nmData.ResourcePolicies { + for i, policy := range policies { + if policy == nil { + continue + } + if len(policy.Rules) == 0 { + if full, ok := byID[policy.ID]; ok { + policies[i] = full + continue + } + } + defaultPolicyIDs(policy) + } + } +} + +func defaultPolicyIDs(policy *nmdata.Policy) { + if policy == nil { + return + } + if policy.PublicID == "" { + policy.PublicID = policy.ID + } + for i, rule := range policy.Rules { + if rule == nil { + continue + } + if rule.PolicyID == "" { + rule.PolicyID = policy.ID + } + if rule.ID == "" { + // Production gives a rule its policy's id (management/server/policy.go:205, + // "when policy can contain multiple rules, need refactor"), so a + // single-rule policy — the only shape the product can create today — + // must be modelled that way or the wire ids come out unrealistic. + rule.ID = policy.ID + if len(policy.Rules) > 1 { + rule.ID = fmt.Sprintf("%s-rule-%d", policy.ID, i) + } + } + } +} + +func defaultXIDMapping(m *map[string]string, id string) { + if id == "" { + return + } + if *m == nil { + *m = make(map[string]string) + } + if _, ok := (*m)[id]; !ok { + (*m)[id] = id + } +} + +// derivedWgKey returns a deterministic base64 key of 32 bytes, valid for the +// envelope decoder's WG-key identity. +func derivedWgKey(peerID string) string { + sum := sha256.Sum256([]byte(peerID)) + return base64.StdEncoding.EncodeToString(sum[:]) +} diff --git a/management/internals/controllers/network_map/nmaptest/golden_test.go b/management/internals/controllers/network_map/nmaptest/golden_test.go new file mode 100644 index 000000000..75c0d57d2 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/golden_test.go @@ -0,0 +1,12 @@ +package nmaptest_test + +import ( + "path/filepath" + "testing" + + "github.com/netbirdio/netbird/management/internals/controllers/network_map/nmaptest" +) + +func TestNetworkMapGolden(t *testing.T) { + nmaptest.RunGoldenDir(t, filepath.Join("testdata", "cases")) +} diff --git a/management/internals/controllers/network_map/nmaptest/runner.go b/management/internals/controllers/network_map/nmaptest/runner.go new file mode 100644 index 000000000..ea54b613c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/runner.go @@ -0,0 +1,338 @@ +// Package nmaptest measures network map generation on the dedicated store +// path against committed expectations. A case stands in for the store load +// with a NetworkMapData fixture — the value NetworkMapDBStoreImpl returns for +// one account — then runs the production per-peer pipeline the controller +// uses, PeersCustomZone → GetPeerNetworkMapComponents → proto conversion, in +// both wire shapes: the legacy full map (grpc.ToSyncResponse) and the +// component envelope expanded client-side (grpc.ToComponentSyncResponse → +// networkmap.EnvelopeToNetworkMap). +// +// The expectation files are the point of the framework. They state what the +// output should be, so a failing case means the code disagrees with the +// expectation and the answer is normally to fix the code; an expectation +// changes only through a deliberate reviewed edit. Nothing in this package +// writes to testdata — there is no flag that records current behaviour into an +// expectation, because that is how a defect becomes the baseline. Cases whose +// expectation encodes correct behaviour the code does not yet deliver stay red +// on purpose. +// +// A case lives in testdata/cases// as case.json (manifest: description, +// peers, optional accountID, dnsDomain, modes), nmdata.json (the fixture the +// mocked store returns, using Go field names; zero values may be omitted and +// applyFixtureDefaults fills the boilerplate) and golden/.json. +// +// There is ONE expectation per peer, shared by every mode. The modes are not +// different computations: CalculateNetworkMapFromComponents is +// components.Calculate, and both sides assemble the proto with the same +// encode helpers, so the only variable is what the envelope round-trip did to +// the components in transit. Any difference between modes is therefore a +// round-trip fidelity defect, and a shared expectation is what exposes it. +// Results are canonicalized before comparison, since repeated proto fields +// come from map iteration. +package nmaptest + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/netbirdio/netbird/management/internals/controllers/network_map" + "github.com/netbirdio/netbird/management/internals/controllers/network_map/controller" + "github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache" + mgmtgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc" + "github.com/netbirdio/netbird/shared/management/networkmap" + "github.com/netbirdio/netbird/shared/management/networkmap/nmdata" + "github.com/netbirdio/netbird/shared/management/proto" +) + +// Mode selects the wire shape a case is verified through. Both end in a +// *proto.NetworkMap, the one comparison surface shared by every path. +type Mode string + +const ( + // ModeFull is the legacy wire shape: the server runs Calculate and sends + // the expanded map (grpc.ToSyncResponse). + ModeFull Mode = "full" + // ModeEnvelope is the component wire shape: the server encodes components + // into a NetworkMapEnvelope (grpc.ToComponentSyncResponse) and the map is + // expanded the way the client engine does (networkmap.EnvelopeToNetworkMap). + ModeEnvelope Mode = "envelope" + + defaultAccountID = "account" + defaultDNSDomain = "netbird.test" +) + +var defaultModes = []Mode{ModeFull, ModeEnvelope} + +// Case is one nmap-generation scenario: store data for a single account, the +// peers whose network maps are computed, and the directory holding one expected +// *proto.NetworkMap per peer — shared by every mode. +type Case struct { + Name string + AccountID string + DNSDomain string + Peers []string + Modes []Mode + Data *networkmap.NetworkMapData + GoldenDir string +} + +type manifest struct { + Description string + AccountID string + DNSDomain string + Peers []string + Modes []Mode +} + +// RunGoldenDir discovers and runs every fixture case under dir. A case is a +// directory containing case.json (manifest), nmdata.json (store fixture) and +// golden/.json (expected proto.NetworkMap, protojson). +func RunGoldenDir(t *testing.T, dir string) { + t.Helper() + + entries, err := os.ReadDir(dir) + require.NoError(t, err, "read cases dir") + + ran := 0 + for _, entry := range entries { + if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { + continue + } + caseDir := filepath.Join(dir, entry.Name()) + c, err := loadCase(caseDir) + require.NoError(t, err, "load case %s", entry.Name()) + ran++ + t.Run(entry.Name(), func(t *testing.T) { + RunCase(t, c) + }) + } + require.NotZero(t, ran, "no cases found under %s", dir) +} + +func loadCase(caseDir string) (Case, error) { + raw, err := os.ReadFile(filepath.Join(caseDir, "case.json")) + if err != nil { + return Case{}, fmt.Errorf("read manifest: %w", err) + } + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + var m manifest + if err := dec.Decode(&m); err != nil { + return Case{}, fmt.Errorf("decode manifest: %w", err) + } + + data, err := LoadNetworkMapData(filepath.Join(caseDir, "nmdata.json")) + if err != nil { + return Case{}, err + } + + return Case{ + Name: filepath.Base(caseDir), + AccountID: m.AccountID, + DNSDomain: m.DNSDomain, + Peers: m.Peers, + Modes: m.Modes, + Data: data, + GoldenDir: filepath.Join(caseDir, "golden"), + }, nil +} + +// RunCase computes each target peer's network map through every enabled mode +// and compares the canonicalized result against the peer's expectation file. +// It mirrors the controller's store path: fill fixture defaults, precompute +// posture validation once, then run the per-peer pipeline. +func RunCase(t *testing.T, c Case) { + t.Helper() + + require.NotNil(t, c.Data, "case %s: Data is required", c.Name) + require.NotEmpty(t, c.Peers, "case %s: Peers is required", c.Name) + require.NotEmpty(t, c.GoldenDir, "case %s: GoldenDir is required", c.Name) + if c.AccountID == "" { + c.AccountID = defaultAccountID + } + if c.DNSDomain == "" { + c.DNSDomain = defaultDNSDomain + } + if len(c.Modes) == 0 { + c.Modes = defaultModes + } + + ctx := context.Background() + nmData := c.Data + applyFixtureDefaults(nmData) + nmData.PrecomputePostureValidation() + + dnsDomain := c.DNSDomain + if nmData.AccountSettings.DNSDomain != "" { + dnsDomain = nmData.AccountSettings.DNSDomain + } + + zone := networkmap.PeersCustomZone(ctx, c.AccountID, dnsDomain, nmData.Peers, controller.IPv6AllowedPeersFromData(nmData)) + dnsFwdPort := controller.ComputeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) + + for _, mode := range c.Modes { + if mode == ModeEnvelope { + requireEnvelopeSafeKeys(t, nmData, c.Name) + break + } + } + + for _, peerID := range c.Peers { + peer := nmData.Peers[peerID] + require.NotNil(t, peer, "case %s: target peer %q not in fixture", c.Name, peerID) + + for _, mode := range c.Modes { + t.Run(peerID+"/"+string(mode), func(t *testing.T) { + got := computeMode(t, ctx, mode, nmData, peerID, zone, dnsDomain, dnsFwdPort) + canonicalize(got) + compareGolden(t, filepath.Join(c.GoldenDir, peerID+".json"), got, mode) + }) + } + } +} + +// computeMode produces the peer's proto.NetworkMap the way the controller does +// for that wire shape. +func computeMode(t *testing.T, ctx context.Context, mode Mode, nmData *networkmap.NetworkMapData, + peerID string, zone nmdata.CustomZone, dnsDomain string, dnsFwdPort int64) *proto.NetworkMap { + t.Helper() + + peer := nmData.Peers[peerID] + require.NotNil(t, peer, "target peer %q not in fixture", peerID) + + switch mode { + case ModeFull: + nmap := controller.NetworkMapFromData(ctx, nmData, peerID, zone) + return mgmtgrpc.ToSyncResponse(ctx, nil, nil, nil, peer, nil, nil, nmap, dnsDomain, nil, + &cache.DNSConfigCache{}, nmData.AccountSettings, nil, nil, dnsFwdPort).NetworkMap + case ModeEnvelope: + components := nmData.GetPeerNetworkMapComponents(peerID, zone) + peerGroups := maps.Keys(nmData.GetPeerGroups(peerID)) + resp := mgmtgrpc.ToComponentSyncResponse(ctx, nil, nil, nil, peer, nil, nil, components, nil, + dnsDomain, nil, nmData.AccountSettings, nil, peerGroups, dnsFwdPort) + res, err := networkmap.EnvelopeToNetworkMap(ctx, resp.NetworkMapEnvelope, peer.Key, dnsDomain) + require.NoError(t, err, "expand envelope") + return res.NetworkMap + default: + t.Fatalf("unknown mode %q", mode) + return nil + } +} + +// caseInputs prepares a loaded case for computation, returning the values every +// mode needs. Exposed for tests in this package that drive modes directly. +func caseInputs(t *testing.T, c Case) (*networkmap.NetworkMapData, nmdata.CustomZone, string, int64) { + t.Helper() + + nmData := c.Data + applyFixtureDefaults(nmData) + nmData.PrecomputePostureValidation() + + dnsDomain := defaultDNSDomain + if c.DNSDomain != "" { + dnsDomain = c.DNSDomain + } + if nmData.AccountSettings.DNSDomain != "" { + dnsDomain = nmData.AccountSettings.DNSDomain + } + + accountID := c.AccountID + if accountID == "" { + accountID = defaultAccountID + } + + ctx := context.Background() + zone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, controller.IPv6AllowedPeersFromData(nmData)) + return nmData, zone, dnsDomain, controller.ComputeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion) +} + +// requireEnvelopeSafeKeys fails fast on peer keys the envelope decoder would +// silently drop: it re-keys peers by base64 of the raw 32-byte WG public key. +func requireEnvelopeSafeKeys(t *testing.T, nmData *networkmap.NetworkMapData, caseName string) { + t.Helper() + for id, p := range nmData.Peers { + if p == nil { + continue + } + raw, err := base64.StdEncoding.DecodeString(p.Key) + if err != nil || len(raw) != 32 { + t.Fatalf("case %s: peer %q Key must be base64 of 32 bytes for mode %q (the envelope decoder drops it otherwise); use a real WireGuard public key or restrict the case to mode %q", + caseName, id, ModeEnvelope, ModeFull) + } + } +} + +// compareGolden measures got against the committed expectation file. One +// expectation serves every mode, because the modes run the same computation and +// must therefore agree. The expectation is the authority: a mismatch means the +// code does not produce what this case says it should, so it is reported as a +// failure and not quietly absorbed. +// +// The full mode is compared verbatim, identifiers included, so the expectation +// pins real ids and stays readable. Other modes have identifiers erased on both +// sides first, because the envelope currently rewrites them — a tracked defect +// that TestIDSpaceMatches asserts against on its own, so it does not have to +// drown out every other case here. +// Nothing here writes to testdata. Expectation files are authored by hand and +// only ever change through a reviewed edit, so there is no mode in which a run +// can create or replace one. When a file is missing the computed map is printed +// for the author to read and, if it is genuinely correct, save deliberately. +func compareGolden(t *testing.T, path string, got *proto.NetworkMap, mode Mode) { + t.Helper() + + if mode != ModeFull { + normalizeIDSpace(got) + canonicalize(got) + } + + raw, err := os.ReadFile(path) + if err != nil { + rendered, mErr := renderNetworkMap(got) + require.NoError(t, mErr) + t.Fatalf("no expectation file %s: %v\nThis case has nothing to measure against — write the "+ + "proto.NetworkMap this peer should receive. Mode %s currently produces:\n%s\nRead it before "+ + "saving any of it: if the code is wrong, so is this.", path, err, mode, rendered) + } + want := &proto.NetworkMap{} + require.NoError(t, protojson.Unmarshal(raw, want), "parse expectation %s", path) + canonicalize(want) + if mode != ModeFull { + normalizeIDSpace(want) + canonicalize(want) + } + + if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { + t.Errorf("mode %s does not produce what %s expects (-want +got):\n%s\n"+ + "Both modes run the same computation on the same components, so they must produce the same map. "+ + "The expectation file is the committed statement of correct output — fix the code, or change the "+ + "expectation deliberately if the intended behaviour really moved.", mode, path, diff) + } +} + +// renderNetworkMap renders stable protojson: protojson output whitespace is +// deliberately unstable, so it is reformatted through json.Indent. +func renderNetworkMap(nm *proto.NetworkMap) ([]byte, error) { + raw, err := protojson.Marshal(nm) + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err := json.Indent(&buf, raw, "", " "); err != nil { + return nil, err + } + buf.WriteByte('\n') + return buf.Bytes(), nil +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/case.json new file mode 100644 index 000000000..4747e9640 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/case.json @@ -0,0 +1,7 @@ +{ + "description": "Two groups joined by one allow-all policy; peer-c has SSH enabled so the legacy-SSH path fills SshAuth from AllowedUserIDs.", + "peers": [ + "peer-a", + "peer-c" + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/golden/peer-a.json new file mode 100644 index 000000000..e2b69276c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/golden/peer-a.json @@ -0,0 +1,65 @@ +{ + "Serial": "5", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "4deEImv8zGvsyBmmfC2G0eQkbyMzyGuz/YK7pcYETwM=", + "allowedIps": [ + "100.64.0.3/32" + ], + "sshConfig": { + "sshPubKey": "c3NoLXBlZXItYw==" + }, + "fqdn": "peer-c.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-c.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.3", + "Protocol": "ALL", + "PolicyID": "cG9sLWFsbA==" + }, + { + "PeerIP": "100.64.0.3", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLWFsbA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/golden/peer-c.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/golden/peer-c.json new file mode 100644 index 000000000..4c358b163 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/golden/peer-c.json @@ -0,0 +1,102 @@ +{ + "Serial": "5", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": { + "sshEnabled": true + }, + "fqdn": "peer-c.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + }, + { + "Name": "peer-c.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "ALL", + "PolicyID": "cG9sLWFsbA==" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLWFsbA==" + }, + { + "PeerIP": "100.64.0.2", + "Protocol": "ALL", + "PolicyID": "cG9sLWFsbA==" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLWFsbA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub", + "AuthorizedUsers": [ + "u9dHvAXZJKiXITuwP9jD/A==" + ], + "machineUsers": { + "*": { + "indexes": [ + 0 + ] + } + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/nmdata.json new file mode 100644 index 000000000..7d78e5c61 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/basic-policy/nmdata.json @@ -0,0 +1,31 @@ +{ + "Network": {"Serial": 5}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-c": {"IP": "100.64.0.3", "SSHEnabled": true, "SSHKey": "ssh-peer-c", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]}, + "grp-ops": {"Peers": ["peer-c"]} + }, + "Policies": [ + { + "ID": "pol-all", + "PublicID": "pol-all-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "all", + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-ops"] + } + ] + } + ], + "AllowedUserIDs": {"user-ops": {}} +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/case.json new file mode 100644 index 000000000..4844b02f2 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/case.json @@ -0,0 +1,7 @@ +{ + "description": "Nameserver group and applied custom zone distributed to grp-dev; peer-a (with an extra DNS label) receives them, peer-c outside the group receives neither.", + "peers": [ + "peer-a", + "peer-c" + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/golden/peer-a.json new file mode 100644 index 000000000..f14ff1973 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/golden/peer-a.json @@ -0,0 +1,93 @@ +{ + "Serial": "8", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "NameServerGroups": [ + { + "NameServers": [ + { + "IP": "8.8.8.8", + "Port": "53" + } + ], + "Primary": true + } + ], + "CustomZones": [ + { + "Domain": "corp.internal.", + "Records": [ + { + "Name": "db.corp.internal.", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "10.10.0.5" + } + ] + }, + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + }, + { + "Name": "www.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.2", + "Protocol": "ALL", + "PolicyID": "cG9sLW1lc2g=" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLW1lc2g=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/golden/peer-c.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/golden/peer-c.json new file mode 100644 index 000000000..cdb77d464 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/golden/peer-c.json @@ -0,0 +1,34 @@ +{ + "Serial": "8", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": {}, + "fqdn": "peer-c.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-c.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/nmdata.json new file mode 100644 index 000000000..1581127a5 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/dns-config/nmdata.json @@ -0,0 +1,51 @@ +{ + "Network": {"Serial": 8}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "ExtraDNSLabels": ["www"], "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-c": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]}, + "grp-ops": {"Peers": ["peer-c"]} + }, + "Policies": [ + { + "ID": "pol-mesh", + "PublicID": "pol-mesh-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "all", + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-dev"] + } + ] + } + ], + "NameServerGroups": [ + { + "ID": "nsg-1", + "Name": "dns-primary", + "NameServers": [{"IP": "8.8.8.8", "Port": 53}], + "Groups": ["grp-dev"], + "Primary": true, + "Enabled": true + } + ], + "AppliedZoneCandidates": [ + { + "DistributionGroups": ["grp-dev"], + "Zone": { + "Domain": "corp.internal.", + "Records": [ + {"Name": "db.corp.internal.", "Type": 1, "Class": "IN", "TTL": 300, "RData": "10.10.0.5"} + ] + } + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/case.json new file mode 100644 index 000000000..a5776d0e7 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/case.json @@ -0,0 +1,4 @@ +{ + "description": "Domain network resource: the route carries the domain list and the 192.0.2.0/32 placeholder network with NetworkType 3 (dynamic), and peer-r's route firewall rules must be marked dynamic and repeat the domain. Two ports on the policy must produce one rule per port. A domain resource contributes no DNS custom zone of its own — resolution happens through the routing peer's forwarder.", + "peers": ["peer-a", "peer-r"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/golden/peer-a.json new file mode 100644 index 000000000..f83e7a2f2 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/golden/peer-a.json @@ -0,0 +1,59 @@ +{ + "Serial": "22", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-domain:peer-r", + "Network": "192.0.2.0/32", + "NetworkType": "3", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "app-domain", + "Domains": [ + "app.internal" + ], + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/golden/peer-r.json new file mode 100644 index 000000000..41ae3dd33 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/golden/peer-r.json @@ -0,0 +1,92 @@ +{ + "Serial": "22", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-domain:peer-r", + "Network": "192.0.2.0/32", + "NetworkType": "3", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "app-domain", + "Domains": [ + "app.internal" + ], + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32" + ], + "destination": "192.0.2.0/32", + "protocol": "TCP", + "portInfo": { + "port": 443 + }, + "isDynamic": true, + "domains": [ + "app.internal" + ], + "PolicyID": "cG9sLWFwcA==", + "RouteID": "res-domain:peer-r" + }, + { + "sourceRanges": [ + "100.64.0.1/32" + ], + "destination": "192.0.2.0/32", + "protocol": "TCP", + "portInfo": { + "port": 80 + }, + "isDynamic": true, + "domains": [ + "app.internal" + ], + "PolicyID": "cG9sLWFwcA==", + "RouteID": "res-domain:peer-r" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/nmdata.json new file mode 100644 index 000000000..db6dc8eda --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-domain-resource/nmdata.json @@ -0,0 +1,43 @@ +{ + "Network": {"Serial": 22}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]} + }, + "Policies": [ + { + "ID": "pol-app", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["80", "443"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-domain", "Type": "domain"} + } + ] + } + ], + "ResourcePolicies": {"res-domain": [{"ID": "pol-app"}]}, + "NetworkResources": [ + { + "ID": "res-domain", + "NetworkID": "net-1", + "Name": "app-domain", + "Type": "domain", + "Domain": "app.internal", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/case.json new file mode 100644 index 000000000..fa1e5c24b --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/case.json @@ -0,0 +1,4 @@ +{ + "description": "Host network resource (single /32) behind one directly-assigned router. peer-a is in the resource policy's source group and must receive one route to 10.10.0.7/32 via peer-r with KeepRoute set and NetID taken from the resource name; peer-r as the router must receive the same route plus a route firewall rule whose SourceRanges are the policy's source peers. A client never gets route firewall rules.", + "peers": ["peer-a", "peer-r"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/golden/peer-a.json new file mode 100644 index 000000000..8bf83f20e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/golden/peer-a.json @@ -0,0 +1,56 @@ +{ + "Serial": "20", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-host:peer-r", + "Network": "10.10.0.7/32", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "web-host", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/golden/peer-r.json new file mode 100644 index 000000000..ef3b5a6c8 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/golden/peer-r.json @@ -0,0 +1,69 @@ +{ + "Serial": "20", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-host:peer-r", + "Network": "10.10.0.7/32", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "web-host", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32" + ], + "destination": "10.10.0.7/32", + "protocol": "TCP", + "portInfo": { + "port": 443 + }, + "PolicyID": "cG9sLXdlYg==", + "RouteID": "res-host:peer-r" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/nmdata.json new file mode 100644 index 000000000..fdd35a439 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-host-resource/nmdata.json @@ -0,0 +1,43 @@ +{ + "Network": {"Serial": 20}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]} + }, + "Policies": [ + { + "ID": "pol-web", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-host", "Type": "host"} + } + ] + } + ], + "ResourcePolicies": {"res-host": [{"ID": "pol-web"}]}, + "NetworkResources": [ + { + "ID": "res-host", + "NetworkID": "net-1", + "Name": "web-host", + "Type": "host", + "Prefix": "10.10.0.7/32", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/case.json new file mode 100644 index 000000000..ca54a4b81 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/case.json @@ -0,0 +1,4 @@ +{ + "description": "A disabled resource with a valid policy and router must leave no trace: no routes and no route firewall rules for either the client or the router. Disabling a resource is the switch that revokes access without deleting the policy.", + "peers": ["peer-a", "peer-r"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/golden/peer-a.json new file mode 100644 index 000000000..a4f5a92bb --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/golden/peer-a.json @@ -0,0 +1,34 @@ +{ + "Serial": "25", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/golden/peer-r.json new file mode 100644 index 000000000..b83cfdff6 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/golden/peer-r.json @@ -0,0 +1,34 @@ +{ + "Serial": "25", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/nmdata.json new file mode 100644 index 000000000..43e00a2db --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-disabled/nmdata.json @@ -0,0 +1,42 @@ +{ + "Network": {"Serial": 25}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]} + }, + "Policies": [ + { + "ID": "pol-off-resource", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-disabled", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": {"res-disabled": [{"ID": "pol-off-resource"}]}, + "NetworkResources": [ + { + "ID": "res-disabled", + "NetworkID": "net-1", + "Name": "disabled-subnet", + "Type": "subnet", + "Prefix": "10.50.0.0/24" + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/case.json new file mode 100644 index 000000000..494ac0fce --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/case.json @@ -0,0 +1,4 @@ +{ + "description": "An enabled resource with a healthy router but no policy granting access to it must produce nothing anywhere: no route for the client and none for the router either, since access to a resource is only ever created by a policy. The router also gets no route firewall rules despite being a routing peer for the network.", + "peers": ["peer-a", "peer-r"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/golden/peer-a.json new file mode 100644 index 000000000..32f9cf35e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/golden/peer-a.json @@ -0,0 +1,34 @@ +{ + "Serial": "24", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/golden/peer-r.json new file mode 100644 index 000000000..a97eac9a3 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/golden/peer-r.json @@ -0,0 +1,34 @@ +{ + "Serial": "24", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/nmdata.json new file mode 100644 index 000000000..a3bbf299a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-no-policy/nmdata.json @@ -0,0 +1,26 @@ +{ + "Network": {"Serial": 24}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]} + }, + "NetworkResources": [ + { + "ID": "res-orphan", + "NetworkID": "net-1", + "Name": "orphan-subnet", + "Type": "subnet", + "Prefix": "10.40.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/case.json new file mode 100644 index 000000000..2922a5deb --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/case.json @@ -0,0 +1,4 @@ +{ + "description": "A DISABLED policy granting access to a network resource must grant nothing: no route to 10.90.0.0/24 for peer-a and none for the router either, exactly as if the policy were absent. THE FULL EXPECTATION CURRENTLY FAILS, and should: resource-policy selection never checks policy.Enabled (networkmapcompute.go and networkmap_components.go both test only nil/len(Rules)/Rules[0]), so the legacy path still hands out the route — access survives disabling the policy. The envelope path happens to be correct because the encoder drops disabled policies from the wire. Fix the compute path, do not weaken this expectation.", + "peers": ["peer-a", "peer-r"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/golden/peer-a.json new file mode 100644 index 000000000..92ba75b1e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/golden/peer-a.json @@ -0,0 +1,34 @@ +{ + "Serial": "39", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/golden/peer-r.json new file mode 100644 index 000000000..8c27320ee --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/golden/peer-r.json @@ -0,0 +1,34 @@ +{ + "Serial": "39", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/nmdata.json new file mode 100644 index 000000000..be7251712 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-resource-policy-disabled/nmdata.json @@ -0,0 +1,42 @@ +{ + "Network": {"Serial": 39}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]} + }, + "Policies": [ + { + "ID": "pol-revoked", + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["5432"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-db", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": {"res-db": [{"ID": "pol-revoked"}]}, + "NetworkResources": [ + { + "ID": "res-db", + "NetworkID": "net-1", + "Name": "db-subnet", + "Type": "subnet", + "Prefix": "10.90.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/case.json new file mode 100644 index 000000000..de1027d48 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/case.json @@ -0,0 +1,4 @@ +{ + "description": "The routing peer for the resource is not in ValidatedPeers — an unapproved peer, which the integrated validator withholds. peer-a must therefore receive no route through it and must not see it as a peer at all: traffic may not be routed through a peer the account has not approved. THE ENVELOPE EXPECTATION CURRENTLY FAILS, and should: component selection puts every routing peer into RouterPeers without checking validation, the encoder indexes them into the envelope's peer table, and the client decoder puts every peer it finds back into its peer map, so the unapproved router reappears client-side with a working route. The full path drops it correctly. Fix the component/encoder path, do not weaken this expectation.", + "peers": ["peer-a"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/golden/peer-a.json new file mode 100644 index 000000000..2554fc08c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/golden/peer-a.json @@ -0,0 +1,34 @@ +{ + "Serial": "40", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/nmdata.json new file mode 100644 index 000000000..98ba94471 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-router-unvalidated/nmdata.json @@ -0,0 +1,44 @@ +{ + "Network": {"Serial": 40}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "ValidatedPeers": {"peer-a": {}}, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]} + }, + "Policies": [ + { + "ID": "pol-db", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["5432"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-db", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": {"res-db": [{"ID": "pol-db"}]}, + "NetworkResources": [ + { + "ID": "res-db", + "NetworkID": "net-1", + "Name": "db-subnet", + "Type": "subnet", + "Prefix": "10.100.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/case.json new file mode 100644 index 000000000..8ec63c816 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/case.json @@ -0,0 +1,4 @@ +{ + "description": "Routing peer group: one router record assigned to a peer group, which the store expands into one entry per member peer sharing the router's settings. peer-a must receive one route per routing peer — same NetID and destination, different route ID and peer — which is what gives the client an HA pair to choose between. Each router must receive only its own route, never its sibling's, plus its own route firewall rule.", + "peers": ["peer-a", "peer-r1", "peer-r2"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-a.json new file mode 100644 index 000000000..020c34835 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-a.json @@ -0,0 +1,75 @@ +{ + "Serial": "23", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "Iu6Lj1HqDXfgDh5RppHCRI6RO3lMlZBCndRNrdS7QUI=", + "allowedIps": [ + "100.64.0.11/32" + ], + "sshConfig": {}, + "fqdn": "peer-r1.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "YC6sbWtvpB2d6W/XqsNIV9crsrXBoVDdwmhCoszjCps=", + "allowedIps": [ + "100.64.0.12/32" + ], + "sshConfig": {}, + "fqdn": "peer-r2.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-ha:peer-r1", + "Network": "10.30.0.0/24", + "NetworkType": "1", + "Peer": "Iu6Lj1HqDXfgDh5RppHCRI6RO3lMlZBCndRNrdS7QUI=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-subnet", + "keepRoute": true + }, + { + "ID": "res-ha:peer-r2", + "Network": "10.30.0.0/24", + "NetworkType": "1", + "Peer": "YC6sbWtvpB2d6W/XqsNIV9crsrXBoVDdwmhCoszjCps=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-r1.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-r1.json new file mode 100644 index 000000000..e42214ca8 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-r1.json @@ -0,0 +1,69 @@ +{ + "Serial": "23", + "peerConfig": { + "address": "100.64.0.11/10", + "sshConfig": {}, + "fqdn": "peer-r1.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-ha:peer-r1", + "Network": "10.30.0.0/24", + "NetworkType": "1", + "Peer": "Iu6Lj1HqDXfgDh5RppHCRI6RO3lMlZBCndRNrdS7QUI=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r1.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.11" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32" + ], + "destination": "10.30.0.0/24", + "protocol": "TCP", + "portInfo": { + "port": 5432 + }, + "PolicyID": "cG9sLWhh", + "RouteID": "res-ha:peer-r1" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-r2.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-r2.json new file mode 100644 index 000000000..2560742fb --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/golden/peer-r2.json @@ -0,0 +1,69 @@ +{ + "Serial": "23", + "peerConfig": { + "address": "100.64.0.12/10", + "sshConfig": {}, + "fqdn": "peer-r2.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-ha:peer-r2", + "Network": "10.30.0.0/24", + "NetworkType": "1", + "Peer": "YC6sbWtvpB2d6W/XqsNIV9crsrXBoVDdwmhCoszjCps=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r2.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.12" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32" + ], + "destination": "10.30.0.0/24", + "protocol": "TCP", + "portInfo": { + "port": 5432 + }, + "PolicyID": "cG9sLWhh", + "RouteID": "res-ha:peer-r2" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/nmdata.json new file mode 100644 index 000000000..03937cb14 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-routing-peer-group-ha/nmdata.json @@ -0,0 +1,46 @@ +{ + "Network": {"Serial": 23}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r1": {"IP": "100.64.0.11", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r2": {"IP": "100.64.0.12", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]}, + "grp-routers": {"Peers": ["peer-r1", "peer-r2"]} + }, + "Policies": [ + { + "ID": "pol-ha", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["5432"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-ha", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": {"res-ha": [{"ID": "pol-ha"}]}, + "NetworkResources": [ + { + "ID": "res-ha", + "NetworkID": "net-ha", + "Name": "ha-subnet", + "Type": "subnet", + "Prefix": "10.30.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-ha": { + "peer-r1": {"PublicID": "router-ha", "PeerGroups": ["grp-routers"], "Masquerade": true, "Metric": 9999, "Enabled": true}, + "peer-r2": {"PublicID": "router-ha", "PeerGroups": ["grp-routers"], "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/case.json new file mode 100644 index 000000000..16cfedf34 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/case.json @@ -0,0 +1,4 @@ +{ + "description": "Subnet network resource behind one directly-assigned router, with masquerade off and a non-default metric so both reach the wire verbatim, and an all-protocol policy from a two-peer source group. peer-r's route firewall rule must list both source peers; peer-b confirms a second client in the same group gets its own identical route.", + "peers": ["peer-a", "peer-b", "peer-r"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-a.json new file mode 100644 index 000000000..a5bc8f880 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-a.json @@ -0,0 +1,55 @@ +{ + "Serial": "21", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-subnet:peer-r", + "Network": "10.20.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "500", + "NetID": "office-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-b.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-b.json new file mode 100644 index 000000000..01c31edf6 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-b.json @@ -0,0 +1,55 @@ +{ + "Serial": "21", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-subnet:peer-r", + "Network": "10.20.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "500", + "NetID": "office-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-r.json new file mode 100644 index 000000000..39a29125d --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/golden/peer-r.json @@ -0,0 +1,76 @@ +{ + "Serial": "21", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-subnet:peer-r", + "Network": "10.20.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "500", + "NetID": "office-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32", + "100.64.0.2/32" + ], + "destination": "10.20.0.0/24", + "protocol": "ALL", + "portInfo": {}, + "PolicyID": "cG9sLXN1Ym5ldA==", + "RouteID": "res-subnet:peer-r" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/nmdata.json new file mode 100644 index 000000000..ba27f494c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/net-subnet-resource/nmdata.json @@ -0,0 +1,43 @@ +{ + "Network": {"Serial": 21}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]} + }, + "Policies": [ + { + "ID": "pol-subnet", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "all", + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-subnet", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": {"res-subnet": [{"ID": "pol-subnet"}]}, + "NetworkResources": [ + { + "ID": "res-subnet", + "NetworkID": "net-1", + "Name": "office-subnet", + "Type": "subnet", + "Prefix": "10.20.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Metric": 500, "Enabled": true} + } + } +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/case.json new file mode 100644 index 000000000..39c477b9f --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/case.json @@ -0,0 +1,4 @@ +{ + "description": "Direct peer-to-peer policy via Source/DestinationResource of type peer, no groups involved; peer-a and peer-b see each other, bystander peer-c sees nobody.", + "peers": ["peer-a", "peer-b", "peer-c"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-a.json new file mode 100644 index 000000000..4d59c33bb --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-a.json @@ -0,0 +1,64 @@ +{ + "Serial": "15", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.2", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRpcmVjdA==" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRpcmVjdA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-b.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-b.json new file mode 100644 index 000000000..59b4bd24c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-b.json @@ -0,0 +1,64 @@ +{ + "Serial": "15", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRpcmVjdA==" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRpcmVjdA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-c.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-c.json new file mode 100644 index 000000000..9ff24ce1a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/golden/peer-c.json @@ -0,0 +1,33 @@ +{ + "Serial": "15", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": {}, + "fqdn": "peer-c.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-c.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/nmdata.json new file mode 100644 index 000000000..f3ee4d163 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-peer-to-peer/nmdata.json @@ -0,0 +1,26 @@ +{ + "Network": {"Serial": 15}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-c": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0"}} + }, + "Policies": [ + { + "ID": "pol-direct", + "PublicID": "pol-direct-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Bidirectional": true, + "SourceResource": {"ID": "peer-a", "Type": "peer"}, + "DestinationResource": {"ID": "peer-b", "Type": "peer"} + } + ] + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/case.json new file mode 100644 index 000000000..a0eccba20 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/case.json @@ -0,0 +1,4 @@ +{ + "description": "One-way udp/514 plus bidirectional tcp port-range 1000-2000 between the same groups; a disabled policy and a policy whose only rule is disabled must leave no trace.", + "peers": ["peer-a", "peer-srv"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/golden/peer-a.json new file mode 100644 index 000000000..5a2276d96 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/golden/peer-a.json @@ -0,0 +1,81 @@ +{ + "Serial": "14", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 1000, + "end": 2000 + } + }, + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 1000, + "end": 2000 + } + }, + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "UDP", + "Port": "514", + "PolicyID": "cG9sLXN5c2xvZw==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/golden/peer-srv.json new file mode 100644 index 000000000..6a89148f5 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/golden/peer-srv.json @@ -0,0 +1,80 @@ +{ + "Serial": "14", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 1000, + "end": 2000 + } + }, + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 1000, + "end": 2000 + } + }, + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.1", + "Protocol": "UDP", + "Port": "514", + "PolicyID": "cG9sLXN5c2xvZw==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/nmdata.json new file mode 100644 index 000000000..f1262a0d7 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/policy-ports-and-ranges/nmdata.json @@ -0,0 +1,74 @@ +{ + "Network": {"Serial": 14}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]}, + "grp-svc": {"Peers": ["peer-srv"]} + }, + "Policies": [ + { + "ID": "pol-syslog", + "PublicID": "pol-syslog-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "udp", + "Ports": ["514"], + "Sources": ["grp-dev"], + "Destinations": ["grp-svc"] + } + ] + }, + { + "ID": "pol-range", + "PublicID": "pol-range-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "PortRanges": [{"Start": 1000, "End": 2000}], + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-svc"] + } + ] + }, + { + "ID": "pol-off", + "PublicID": "pol-off-pub", + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["9999"], + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-svc"] + } + ] + }, + { + "ID": "pol-rule-off", + "PublicID": "pol-rule-off-pub", + "Enabled": true, + "Rules": [ + { + "Action": "accept", + "Protocol": "udp", + "Ports": ["1111"], + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-svc"] + } + ] + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/case.json new file mode 100644 index 000000000..3307e0afe --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/case.json @@ -0,0 +1,4 @@ +{ + "description": "Posture checks gate a policy's sources only, never its destinations. peer-srv-old would fail the version check, but it sits in the destination group, so peer-client must still receive it alongside peer-srv-new, and peer-srv-old must still receive peer-client. This asymmetry is deliberate in the compute path — destination peers are resolved with no posture checks passed in — and it is worth pinning because it is easy to assume a posture check protects both ends.", + "peers": ["peer-client", "peer-srv-old"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/golden/peer-client.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/golden/peer-client.json new file mode 100644 index 000000000..73182932b --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/golden/peer-client.json @@ -0,0 +1,93 @@ +{ + "Serial": "37", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-client.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "MdeD+cDSnurizeZ/Zd7rEdIhs9VZViEnutUwkodqb1s=", + "allowedIps": [ + "100.64.0.12/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv-new.netbird.test", + "agentVersion": "1.0.0" + }, + { + "wgPubKey": "ph1eqUTlSeLQ6V9zLEUpck25m5K5sOQq+AHY879HZME=", + "allowedIps": [ + "100.64.0.11/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv-old.netbird.test", + "agentVersion": "0.30.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-client.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv-new.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.12" + }, + { + "Name": "peer-srv-old.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.11" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.11", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRlc3Q=" + }, + { + "PeerIP": "100.64.0.11", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRlc3Q=" + }, + { + "PeerIP": "100.64.0.12", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRlc3Q=" + }, + { + "PeerIP": "100.64.0.12", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRlc3Q=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/golden/peer-srv-old.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/golden/peer-srv-old.json new file mode 100644 index 000000000..8d1ad4feb --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/golden/peer-srv-old.json @@ -0,0 +1,64 @@ +{ + "Serial": "37", + "peerConfig": { + "address": "100.64.0.11/10", + "sshConfig": {}, + "fqdn": "peer-srv-old.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "tKxuKEYQFPR8lCpcfVWBKVX0vGFKYXtTtFjXhoiu5zc=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-client.netbird.test", + "agentVersion": "1.0.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-client.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv-old.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.11" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRlc3Q=" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWRlc3Q=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/nmdata.json new file mode 100644 index 000000000..ca5ece21a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-destination-not-gated/nmdata.json @@ -0,0 +1,33 @@ +{ + "Network": {"Serial": 37}, + "Peers": { + "peer-client": {"IP": "100.64.0.1", "Meta": {"WtVersion": "1.0.0"}}, + "peer-srv-old": {"IP": "100.64.0.11", "Meta": {"WtVersion": "0.30.0"}}, + "peer-srv-new": {"IP": "100.64.0.12", "Meta": {"WtVersion": "1.0.0"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-client"]}, + "grp-srv": {"Peers": ["peer-srv-old", "peer-srv-new"]} + }, + "PostureChecks": { + "chk-version": {"Checks": {"NBVersionCheck": {"MinVersion": "0.45.0"}}} + }, + "Policies": [ + { + "ID": "pol-dest", + "Enabled": true, + "SourcePostureChecks": ["chk-version"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/case.json new file mode 100644 index 000000000..a661fa53e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/case.json @@ -0,0 +1,7 @@ +{ + "description": "Source-side NB-version posture check: peer-b (0.40.0) fails the 0.45.0 minimum, so peer-c must not see it and peer-b itself gets no policy connectivity.", + "peers": [ + "peer-b", + "peer-c" + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/golden/peer-b.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/golden/peer-b.json new file mode 100644 index 000000000..201be294f --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/golden/peer-b.json @@ -0,0 +1,34 @@ +{ + "Serial": "6", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/golden/peer-c.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/golden/peer-c.json new file mode 100644 index 000000000..009d00490 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/golden/peer-c.json @@ -0,0 +1,65 @@ +{ + "Serial": "6", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": {}, + "fqdn": "peer-c.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-c.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdhdGVk" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdhdGVk" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/nmdata.json new file mode 100644 index 000000000..4962e8b6a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-gated/nmdata.json @@ -0,0 +1,36 @@ +{ + "Network": {"Serial": 6}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.40.0"}}, + "peer-c": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]}, + "grp-ops": {"Peers": ["peer-c"]} + }, + "PostureChecks": { + "chk-ver": {"Checks": {"NBVersionCheck": {"MinVersion": "0.45.0"}}} + }, + "PostureCheckXIDToPublicID": {"chk-ver": "chk-ver-pub"}, + "Policies": [ + { + "ID": "pol-gated", + "PublicID": "pol-gated-pub", + "Enabled": true, + "SourcePostureChecks": ["chk-ver"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Bidirectional": true, + "Ports": ["443"], + "Sources": ["grp-dev"], + "Destinations": ["grp-ops"] + } + ] + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/case.json new file mode 100644 index 000000000..9f7f86860 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/case.json @@ -0,0 +1,4 @@ +{ + "description": "Geo location posture check in allow mode. An entry naming only a country matches the whole country, so peer-de passes; an entry naming a city must match that city exactly, so peer-us-ny passes while peer-us-bos does not. peer-fr matches nothing and fails. peer-nowhere has no location at all, which the check reports as an error, and an errored check denies — so it fails too.", + "peers": ["peer-srv", "peer-de", "peer-us-bos", "peer-nowhere"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-de.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-de.json new file mode 100644 index 000000000..9a07139fe --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-de.json @@ -0,0 +1,64 @@ +{ + "Serial": "31", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-de.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-de.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdlbw==" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdlbw==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-nowhere.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-nowhere.json new file mode 100644 index 000000000..aed97a69f --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-nowhere.json @@ -0,0 +1,33 @@ +{ + "Serial": "31", + "peerConfig": { + "address": "100.64.0.5/10", + "sshConfig": {}, + "fqdn": "peer-nowhere.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-nowhere.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.5" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-srv.json new file mode 100644 index 000000000..9f3fcb1ba --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-srv.json @@ -0,0 +1,93 @@ +{ + "Serial": "31", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "9nwvdE0wik6Fcs8Tw6WBnmOqGZmzdiTR4VZAdRBOJF4=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-us-ny.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "s/f5frZqT3DT1o9QCuhA14Pj5GUa4JUsF4M3twrprmk=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-de.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-de.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + }, + { + "Name": "peer-us-ny.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdlbw==" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdlbw==" + }, + { + "PeerIP": "100.64.0.2", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdlbw==" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWdlbw==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-us-bos.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-us-bos.json new file mode 100644 index 000000000..624666c36 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/golden/peer-us-bos.json @@ -0,0 +1,33 @@ +{ + "Serial": "31", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": {}, + "fqdn": "peer-us-bos.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-us-bos.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/nmdata.json new file mode 100644 index 000000000..b92840266 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-allow/nmdata.json @@ -0,0 +1,46 @@ +{ + "Network": {"Serial": 31}, + "Peers": { + "peer-de": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}, "Location": {"CountryCode": "DE", "CityName": "Berlin"}}, + "peer-us-ny": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}, "Location": {"CountryCode": "US", "CityName": "New York"}}, + "peer-us-bos": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0"}, "Location": {"CountryCode": "US", "CityName": "Boston"}}, + "peer-fr": {"IP": "100.64.0.4", "Meta": {"WtVersion": "0.60.0"}, "Location": {"CountryCode": "FR", "CityName": "Paris"}}, + "peer-nowhere": {"IP": "100.64.0.5", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-de", "peer-us-ny", "peer-us-bos", "peer-fr", "peer-nowhere"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "PostureChecks": { + "chk-geo": { + "Checks": { + "GeoLocationCheck": { + "Action": "allow", + "Locations": [ + {"CountryCode": "DE"}, + {"CountryCode": "US", "CityName": "New York"} + ] + } + } + } + }, + "Policies": [ + { + "ID": "pol-geo", + "Enabled": true, + "SourcePostureChecks": ["chk-geo"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/case.json new file mode 100644 index 000000000..f234b5280 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/case.json @@ -0,0 +1,4 @@ +{ + "description": "Geo location posture check in deny mode: matching the list rejects, not matching passes, so peer-ru is excluded and peer-de is admitted. peer-nowhere has no location and fails here as well — a missing location is an error and errors deny in both modes, so deny mode is not a way to admit peers whose location is unknown.", + "peers": ["peer-srv", "peer-ru", "peer-nowhere"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-nowhere.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-nowhere.json new file mode 100644 index 000000000..e73b77d9e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-nowhere.json @@ -0,0 +1,33 @@ +{ + "Serial": "32", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": {}, + "fqdn": "peer-nowhere.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-nowhere.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-ru.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-ru.json new file mode 100644 index 000000000..721267f37 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-ru.json @@ -0,0 +1,33 @@ +{ + "Serial": "32", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-ru.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-ru.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-srv.json new file mode 100644 index 000000000..93883369e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/golden/peer-srv.json @@ -0,0 +1,62 @@ +{ + "Serial": "32", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "s/f5frZqT3DT1o9QCuhA14Pj5GUa4JUsF4M3twrprmk=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-de.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-de.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.2", + "Protocol": "ALL", + "PolicyID": "cG9sLWdlby1kZW55" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLWdlby1kZW55" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/nmdata.json new file mode 100644 index 000000000..5edc50a38 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-geo-deny/nmdata.json @@ -0,0 +1,40 @@ +{ + "Network": {"Serial": 32}, + "Peers": { + "peer-ru": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}, "Location": {"CountryCode": "RU", "CityName": "Moscow"}}, + "peer-de": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}, "Location": {"CountryCode": "DE", "CityName": "Berlin"}}, + "peer-nowhere": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-ru", "peer-de", "peer-nowhere"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "PostureChecks": { + "chk-geo-deny": { + "Checks": { + "GeoLocationCheck": { + "Action": "deny", + "Locations": [{"CountryCode": "RU"}] + } + } + } + }, + "Policies": [ + { + "ID": "pol-geo-deny", + "Enabled": true, + "SourcePostureChecks": ["chk-geo-deny"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "all", + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/case.json new file mode 100644 index 000000000..c0eab5408 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/case.json @@ -0,0 +1,4 @@ +{ + "description": "One posture check bundle holding two different checks. All checks in a bundle must pass, so only peer-both is admitted: peer-badgeo satisfies the version rule and peer-badversion satisfies the location rule, and each is still rejected on the other. This pins the AND semantics of a bundle rather than any-of.", + "peers": ["peer-srv", "peer-both", "peer-badgeo"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-badgeo.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-badgeo.json new file mode 100644 index 000000000..6a6272909 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-badgeo.json @@ -0,0 +1,33 @@ +{ + "Serial": "35", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-badgeo.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-badgeo.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-both.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-both.json new file mode 100644 index 000000000..7e18e7cb8 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-both.json @@ -0,0 +1,64 @@ +{ + "Serial": "35", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-both.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "1.0.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-both.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWNvbWJv" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWNvbWJv" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-srv.json new file mode 100644 index 000000000..e91e8f777 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/golden/peer-srv.json @@ -0,0 +1,64 @@ +{ + "Serial": "35", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ilmSCJoVLTTY/Am7or8ES8R0hL/OdfE2FDK197pxc5o=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-both.netbird.test", + "agentVersion": "1.0.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-both.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWNvbWJv" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLWNvbWJv" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/nmdata.json new file mode 100644 index 000000000..fbf7a5f1e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-multiple-checks/nmdata.json @@ -0,0 +1,42 @@ +{ + "Network": {"Serial": 35}, + "Peers": { + "peer-both": {"IP": "100.64.0.1", "Meta": {"WtVersion": "1.0.0"}, "Location": {"CountryCode": "DE", "CityName": "Berlin"}}, + "peer-badgeo": {"IP": "100.64.0.2", "Meta": {"WtVersion": "1.0.0"}, "Location": {"CountryCode": "FR", "CityName": "Paris"}}, + "peer-badversion": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.30.0"}, "Location": {"CountryCode": "DE", "CityName": "Berlin"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "1.0.0"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-both", "peer-badgeo", "peer-badversion"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "PostureChecks": { + "chk-combo": { + "Checks": { + "NBVersionCheck": {"MinVersion": "0.45.0"}, + "GeoLocationCheck": { + "Action": "allow", + "Locations": [{"CountryCode": "DE"}] + } + } + } + }, + "Policies": [ + { + "ID": "pol-combo", + "Enabled": true, + "SourcePostureChecks": ["chk-combo"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/case.json new file mode 100644 index 000000000..d10bfc9ae --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/case.json @@ -0,0 +1,4 @@ +{ + "description": "Peer network range posture check in allow mode over 192.168.0.0/16. peer-office passes on its reported interface network, and peer-by-connip passes on the address it connected from, which the check folds in as a single-host prefix — so either source of address information can satisfy it. peer-remote is outside the range and peer-noaddr reports no address at all, which errors and therefore denies. Note this policy is tcp/22 without the peer's SSH flag, so no authorized users appear.", + "peers": ["peer-srv", "peer-office", "peer-by-connip", "peer-remote"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-by-connip.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-by-connip.json new file mode 100644 index 000000000..c9cb3ed0a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-by-connip.json @@ -0,0 +1,64 @@ +{ + "Serial": "33", + "peerConfig": { + "address": "100.64.0.3/10", + "sshConfig": {}, + "fqdn": "peer-by-connip.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-by-connip.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-office.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-office.json new file mode 100644 index 000000000..1b9834d12 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-office.json @@ -0,0 +1,64 @@ +{ + "Serial": "33", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-office.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-office.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-remote.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-remote.json new file mode 100644 index 000000000..134a0aff2 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-remote.json @@ -0,0 +1,33 @@ +{ + "Serial": "33", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-remote.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-remote.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-srv.json new file mode 100644 index 000000000..a8499e031 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/golden/peer-srv.json @@ -0,0 +1,93 @@ +{ + "Serial": "33", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "2Za6YHlJPJPv3hG/vDvdT0emXqIQADX9+wE8F1ZsgHU=", + "allowedIps": [ + "100.64.0.3/32" + ], + "sshConfig": {}, + "fqdn": "peer-by-connip.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "iVuRGJEIX4iqqF3zV01cxAEgyjXW3X4rrMoal6iA4fc=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-office.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-by-connip.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + }, + { + "Name": "peer-office.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.3", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + }, + { + "PeerIP": "100.64.0.3", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXJhbmdl" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/nmdata.json new file mode 100644 index 000000000..a258e855d --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-network-range/nmdata.json @@ -0,0 +1,42 @@ +{ + "Network": {"Serial": 33}, + "Peers": { + "peer-office": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0", "NetworkAddresses": [{"NetIP": "192.168.1.10/24"}]}}, + "peer-remote": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0", "NetworkAddresses": [{"NetIP": "10.0.0.5/8"}]}}, + "peer-by-connip": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0"}, "Location": {"ConnectionIP": "192.168.5.5"}}, + "peer-noaddr": {"IP": "100.64.0.4", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-office", "peer-remote", "peer-by-connip", "peer-noaddr"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "PostureChecks": { + "chk-range": { + "Checks": { + "PeerNetworkRangeCheck": { + "Action": "allow", + "Ranges": ["192.168.0.0/16"] + } + } + } + }, + "Policies": [ + { + "ID": "pol-range", + "Enabled": true, + "SourcePostureChecks": ["chk-range"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["22"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/case.json new file mode 100644 index 000000000..b23a187d4 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/case.json @@ -0,0 +1,4 @@ +{ + "description": "OS version posture check with per-OS minimums. peer-srv must see only the peers that satisfy their own platform's rule: the Linux peer on kernel 6.1 (the check compares the part before the first dash) and the macOS peer on 14.2. The old Linux and macOS peers fail. peer-win fails too even though its version looks modern, because the check defines no Windows minimum and a platform with no rule configured is treated as failing, not as unrestricted — a surprising rule worth freezing. Each rejected peer also loses its own view of peer-srv, since the policy is its only connectivity.", + "peers": ["peer-srv", "peer-lin-ok", "peer-lin-old", "peer-win"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-lin-ok.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-lin-ok.json new file mode 100644 index 000000000..f299b9fcf --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-lin-ok.json @@ -0,0 +1,64 @@ +{ + "Serial": "30", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-lin-ok.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-lin-ok.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLW9z" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLW9z" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-lin-old.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-lin-old.json new file mode 100644 index 000000000..c0a5412fa --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-lin-old.json @@ -0,0 +1,33 @@ +{ + "Serial": "30", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-lin-old.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-lin-old.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-srv.json new file mode 100644 index 000000000..91883d20b --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-srv.json @@ -0,0 +1,93 @@ +{ + "Serial": "30", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "XpdB4aptfFjgsQOHfEO65dNozY8R7EIw3/alAnXjl+k=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-lin-ok.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "gRO02HiaHUKq2xTbYJhPsmGj06bK0HGU2tgL0pKB2yQ=", + "allowedIps": [ + "100.64.0.3/32" + ], + "sshConfig": {}, + "fqdn": "peer-mac-ok.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-lin-ok.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-mac-ok.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLW9z" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLW9z" + }, + { + "PeerIP": "100.64.0.3", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLW9z" + }, + { + "PeerIP": "100.64.0.3", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "443", + "PolicyID": "cG9sLW9z" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-win.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-win.json new file mode 100644 index 000000000..0b0cafb26 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/golden/peer-win.json @@ -0,0 +1,33 @@ +{ + "Serial": "30", + "peerConfig": { + "address": "100.64.0.5/10", + "sshConfig": {}, + "fqdn": "peer-win.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-win.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.5" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/nmdata.json new file mode 100644 index 000000000..018c0cc21 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-os-version/nmdata.json @@ -0,0 +1,43 @@ +{ + "Network": {"Serial": 30}, + "Peers": { + "peer-lin-ok": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0", "GoOS": "linux", "KernelVersion": "6.1.0-arch1"}}, + "peer-lin-old": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0", "GoOS": "linux", "KernelVersion": "5.4.0-generic"}}, + "peer-mac-ok": {"IP": "100.64.0.3", "Meta": {"WtVersion": "0.60.0", "GoOS": "darwin", "OSVersion": "14.2"}}, + "peer-mac-old": {"IP": "100.64.0.4", "Meta": {"WtVersion": "0.60.0", "GoOS": "darwin", "OSVersion": "12.0"}}, + "peer-win": {"IP": "100.64.0.5", "Meta": {"WtVersion": "0.60.0", "GoOS": "windows", "KernelVersion": "10.0.19045"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0", "GoOS": "linux", "KernelVersion": "6.1.0-arch1"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-lin-ok", "peer-lin-old", "peer-mac-ok", "peer-mac-old", "peer-win"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "PostureChecks": { + "chk-os": { + "Checks": { + "OSVersionCheck": { + "Linux": {"MinKernelVersion": "6.0.0"}, + "Darwin": {"MinVersion": "13.0"} + } + } + } + }, + "Policies": [ + { + "ID": "pol-os", + "Enabled": true, + "SourcePostureChecks": ["chk-os"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/case.json new file mode 100644 index 000000000..b730389a6 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/case.json @@ -0,0 +1,4 @@ +{ + "description": "Process posture check, which picks the path for the peer's own platform. peer-lin-running and peer-mac-running each have their platform's process running and pass. peer-lin-stopped reports the same file but not running, so it fails — presence of the binary is not enough. peer-bsd runs an unsupported operating system, which the check reports as an error, and errors deny.", + "peers": ["peer-srv", "peer-lin-running", "peer-lin-stopped", "peer-bsd"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-bsd.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-bsd.json new file mode 100644 index 000000000..f75459d39 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-bsd.json @@ -0,0 +1,33 @@ +{ + "Serial": "34", + "peerConfig": { + "address": "100.64.0.4/10", + "sshConfig": {}, + "fqdn": "peer-bsd.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-bsd.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.4" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-lin-running.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-lin-running.json new file mode 100644 index 000000000..ddfa39472 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-lin-running.json @@ -0,0 +1,62 @@ +{ + "Serial": "34", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-lin-running.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-lin-running.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Protocol": "ALL", + "PolicyID": "cG9sLXByb2M=" + }, + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLXByb2M=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-lin-stopped.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-lin-stopped.json new file mode 100644 index 000000000..828a933f7 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-lin-stopped.json @@ -0,0 +1,33 @@ +{ + "Serial": "34", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-lin-stopped.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-lin-stopped.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-srv.json new file mode 100644 index 000000000..6eb409623 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/golden/peer-srv.json @@ -0,0 +1,89 @@ +{ + "Serial": "34", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "d9LCTf7vwqctprOKyF95j17uPWpRjEeirHfB75RIGlk=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-lin-running.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "rgP4xt50GcHp7fFgBoSt8yz5bp5AnOAVHMmOd+rTbR4=", + "allowedIps": [ + "100.64.0.3/32" + ], + "sshConfig": {}, + "fqdn": "peer-mac-running.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-lin-running.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-mac-running.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.3" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "ALL", + "PolicyID": "cG9sLXByb2M=" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLXByb2M=" + }, + { + "PeerIP": "100.64.0.3", + "Protocol": "ALL", + "PolicyID": "cG9sLXByb2M=" + }, + { + "PeerIP": "100.64.0.3", + "Direction": "OUT", + "Protocol": "ALL", + "PolicyID": "cG9sLXByb2M=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/nmdata.json new file mode 100644 index 000000000..b112e2235 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-process/nmdata.json @@ -0,0 +1,70 @@ +{ + "Network": {"Serial": 34}, + "Peers": { + "peer-lin-running": { + "IP": "100.64.0.1", + "Meta": { + "WtVersion": "0.60.0", + "GoOS": "linux", + "Files": [{"Path": "/usr/bin/agent", "ProcessIsRunning": true}] + } + }, + "peer-lin-stopped": { + "IP": "100.64.0.2", + "Meta": { + "WtVersion": "0.60.0", + "GoOS": "linux", + "Files": [{"Path": "/usr/bin/agent"}] + } + }, + "peer-mac-running": { + "IP": "100.64.0.3", + "Meta": { + "WtVersion": "0.60.0", + "GoOS": "darwin", + "Files": [{"Path": "/Applications/Agent.app", "ProcessIsRunning": true}] + } + }, + "peer-bsd": { + "IP": "100.64.0.4", + "Meta": { + "WtVersion": "0.60.0", + "GoOS": "freebsd", + "Files": [{"Path": "/usr/bin/agent", "ProcessIsRunning": true}] + } + }, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0", "GoOS": "linux"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-lin-running", "peer-lin-stopped", "peer-mac-running", "peer-bsd"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "PostureChecks": { + "chk-proc": { + "Checks": { + "ProcessCheck": { + "Processes": [ + {"LinuxPath": "/usr/bin/agent", "MacPath": "/Applications/Agent.app"} + ] + } + } + } + }, + "Policies": [ + { + "ID": "pol-proc", + "Enabled": true, + "SourcePostureChecks": ["chk-proc"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "all", + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/case.json new file mode 100644 index 000000000..b30b06243 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/case.json @@ -0,0 +1,4 @@ +{ + "description": "Posture check on a policy granting access to a network resource. peer-ok must receive the route to the resource through peer-r, while peer-bad fails the version check and must receive no route at all. The router's route firewall rule must narrow its SourceRanges to peer-ok's address only — a peer rejected by posture must not be permitted through the routing peer either, which is the enforcement that actually matters.", + "peers": ["peer-ok", "peer-bad", "peer-r"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-bad.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-bad.json new file mode 100644 index 000000000..b1aa1a494 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-bad.json @@ -0,0 +1,34 @@ +{ + "Serial": "38", + "peerConfig": { + "address": "100.64.0.2/10", + "sshConfig": {}, + "fqdn": "peer-bad.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-bad.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-ok.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-ok.json new file mode 100644 index 000000000..6aa688eef --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-ok.json @@ -0,0 +1,56 @@ +{ + "Serial": "38", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-ok.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "1.0.0" + } + ], + "Routes": [ + { + "ID": "res-db:peer-r", + "Network": "10.80.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "db-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-ok.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-r.json new file mode 100644 index 000000000..192304ca6 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/golden/peer-r.json @@ -0,0 +1,69 @@ +{ + "Serial": "38", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "XPXITqbev8jVtIkumbPt5vpohe2OHWvhNGO3z2mgcxM=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-ok.netbird.test", + "agentVersion": "1.0.0" + } + ], + "Routes": [ + { + "ID": "res-db:peer-r", + "Network": "10.80.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "db-subnet", + "keepRoute": true + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32" + ], + "destination": "10.80.0.0/24", + "protocol": "TCP", + "portInfo": { + "port": 5432 + }, + "PolicyID": "cG9sLXJlcw==", + "RouteID": "res-db:peer-r" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/nmdata.json new file mode 100644 index 000000000..840c5a7f8 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-resource-policy/nmdata.json @@ -0,0 +1,48 @@ +{ + "Network": {"Serial": 38}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-ok": {"IP": "100.64.0.1", "Meta": {"WtVersion": "1.0.0"}}, + "peer-bad": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.30.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "1.0.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-ok", "peer-bad"]} + }, + "PostureChecks": { + "chk-version": {"Checks": {"NBVersionCheck": {"MinVersion": "0.45.0"}}} + }, + "Policies": [ + { + "ID": "pol-res", + "Enabled": true, + "SourcePostureChecks": ["chk-version"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["5432"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-db", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": {"res-db": [{"ID": "pol-res"}]}, + "NetworkResources": [ + { + "ID": "res-db", + "NetworkID": "net-1", + "Name": "db-subnet", + "Type": "subnet", + "Prefix": "10.80.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"PublicID": "router-direct", "Masquerade": true, "Metric": 9999, "Enabled": true} + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/case.json new file mode 100644 index 000000000..203b89533 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/case.json @@ -0,0 +1,4 @@ +{ + "description": "The same peer under two policies carrying different posture checks. peer-x is on an old agent version but in an allowed country, so the version-gated policy rejects it while the location-gated one admits it: it must reach peer-srv-b on 8443 and not peer-srv-a at all. Failing one policy's check must not leak into another policy's decision. peer-srv-a correspondingly sees nobody, peer-srv-b sees peer-x.", + "peers": ["peer-x", "peer-srv-a", "peer-srv-b"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-srv-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-srv-a.json new file mode 100644 index 000000000..8008db39c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-srv-a.json @@ -0,0 +1,33 @@ +{ + "Serial": "36", + "peerConfig": { + "address": "100.64.0.11/10", + "sshConfig": {}, + "fqdn": "peer-srv-a.netbird.test", + "autoUpdate": {} + }, + "remotePeersIsEmpty": true, + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-srv-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.11" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "firewallRulesIsEmpty": true, + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-srv-b.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-srv-b.json new file mode 100644 index 000000000..c7bc45742 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-srv-b.json @@ -0,0 +1,64 @@ +{ + "Serial": "36", + "peerConfig": { + "address": "100.64.0.12/10", + "sshConfig": {}, + "fqdn": "peer-srv-b.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "N1oKwtIwXdTDF0HdKDss0gPhxkqz+/Z/91734QZVCng=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-x.netbird.test", + "agentVersion": "0.40.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-srv-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.12" + }, + { + "Name": "peer-x.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "8443", + "PolicyID": "cG9sLWxlbmllbnQ=" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8443", + "PolicyID": "cG9sLWxlbmllbnQ=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-x.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-x.json new file mode 100644 index 000000000..461454233 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/golden/peer-x.json @@ -0,0 +1,64 @@ +{ + "Serial": "36", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-x.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "fT/Mb0QBqXGx2q06gXqizvlXp5uz+ErGCaKmCEXVbMk=", + "allowedIps": [ + "100.64.0.12/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv-b.netbird.test", + "agentVersion": "1.0.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-srv-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.12" + }, + { + "Name": "peer-x.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + } + ] + } + ], + "ForwarderPort": "5353" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.12", + "Protocol": "TCP", + "Port": "8443", + "PolicyID": "cG9sLWxlbmllbnQ=" + }, + { + "PeerIP": "100.64.0.12", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8443", + "PolicyID": "cG9sLWxlbmllbnQ=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/nmdata.json new file mode 100644 index 000000000..2e0512194 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/posture-two-policies/nmdata.json @@ -0,0 +1,55 @@ +{ + "Network": {"Serial": 36}, + "Peers": { + "peer-x": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.40.0"}, "Location": {"CountryCode": "DE", "CityName": "Berlin"}}, + "peer-srv-a": {"IP": "100.64.0.11", "Meta": {"WtVersion": "1.0.0"}}, + "peer-srv-b": {"IP": "100.64.0.12", "Meta": {"WtVersion": "1.0.0"}} + }, + "Groups": { + "grp-clients": {"Peers": ["peer-x"]}, + "grp-srv-a": {"Peers": ["peer-srv-a"]}, + "grp-srv-b": {"Peers": ["peer-srv-b"]} + }, + "PostureChecks": { + "chk-version": {"Checks": {"NBVersionCheck": {"MinVersion": "0.45.0"}}}, + "chk-geo": { + "Checks": { + "GeoLocationCheck": {"Action": "allow", "Locations": [{"CountryCode": "DE"}]} + } + } + }, + "Policies": [ + { + "ID": "pol-strict", + "Enabled": true, + "SourcePostureChecks": ["chk-version"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["443"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv-a"] + } + ] + }, + { + "ID": "pol-lenient", + "Enabled": true, + "SourcePostureChecks": ["chk-geo"], + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["8443"], + "Bidirectional": true, + "Sources": ["grp-clients"], + "Destinations": ["grp-srv-b"] + } + ] + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/case.json new file mode 100644 index 000000000..8d99a239c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/case.json @@ -0,0 +1,4 @@ +{ + "description": "Classic route with access control groups: instead of the wide-open default permit, peer-r's route firewall rule must be narrowed to the policy that targets the ACL group — protocol and port from that rule, SourceRanges limited to the two source peers. peer-a still receives the route itself.", + "peers": ["peer-a", "peer-r"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/golden/peer-a.json new file mode 100644 index 000000000..c52aecce9 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/golden/peer-a.json @@ -0,0 +1,76 @@ +{ + "Serial": "27", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "rt-acl", + "Network": "10.70.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "db-net" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.9", + "Protocol": "TCP", + "Port": "3306", + "PolicyID": "cG9sLWFjbA==" + }, + { + "PeerIP": "100.64.0.9", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "3306", + "PolicyID": "cG9sLWFjbA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/golden/peer-r.json new file mode 100644 index 000000000..cc03ef08c --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/golden/peer-r.json @@ -0,0 +1,119 @@ +{ + "Serial": "27", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "rt-acl", + "Network": "10.70.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "db-net" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + }, + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "3306", + "PolicyID": "cG9sLWFjbA==" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "3306", + "PolicyID": "cG9sLWFjbA==" + }, + { + "PeerIP": "100.64.0.2", + "Protocol": "TCP", + "Port": "3306", + "PolicyID": "cG9sLWFjbA==" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "3306", + "PolicyID": "cG9sLWFjbA==" + } + ], + "routesFirewallRules": [ + { + "sourceRanges": [ + "100.64.0.1/32", + "100.64.0.2/32" + ], + "destination": "10.70.0.0/24", + "protocol": "TCP", + "portInfo": { + "port": 3306 + }, + "PolicyID": "cG9sLWFjbA==", + "RouteID": "rt-acl" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/nmdata.json new file mode 100644 index 000000000..d7bfd0d08 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-access-control-groups/nmdata.json @@ -0,0 +1,45 @@ +{ + "Network": {"Serial": 27}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]}, + "grp-acl": {"Peers": ["peer-r"]} + }, + "Policies": [ + { + "ID": "pol-acl", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["3306"], + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-acl"] + } + ] + } + ], + "Routes": [ + { + "ID": "rt-acl", + "NetID": "db-net", + "Network": "10.70.0.0/24", + "NetworkType": 1, + "Peer": "peer-r", + "PeerID": "peer-r", + "Groups": ["grp-dev"], + "AccessControlGroups": ["grp-acl"], + "Metric": 9999, + "Masquerade": true, + "Enabled": true + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/case.json new file mode 100644 index 000000000..52d45346a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/case.json @@ -0,0 +1,4 @@ +{ + "description": "Classic route served by a peer group instead of one peer: each routing peer's copy takes the route id with its own peer id appended and drops the PeerGroups field, and the distribution group's peer-a must receive both copies as an HA pair. Each router receives only its own copy plus a default-permit route firewall rule, because the route carries no access control groups. A policy connecting the two groups is required — route distribution follows peers the target may already talk to.", + "peers": ["peer-a", "peer-r1"] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/golden/peer-a.json new file mode 100644 index 000000000..fd9e32274 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/golden/peer-a.json @@ -0,0 +1,114 @@ +{ + "Serial": "26", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "Iu6Lj1HqDXfgDh5RppHCRI6RO3lMlZBCndRNrdS7QUI=", + "allowedIps": [ + "100.64.0.11/32" + ], + "sshConfig": {}, + "fqdn": "peer-r1.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "YC6sbWtvpB2d6W/XqsNIV9crsrXBoVDdwmhCoszjCps=", + "allowedIps": [ + "100.64.0.12/32" + ], + "sshConfig": {}, + "fqdn": "peer-r2.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "rt-ha:peer-r1", + "Network": "10.60.0.0/24", + "NetworkType": "1", + "Peer": "Iu6Lj1HqDXfgDh5RppHCRI6RO3lMlZBCndRNrdS7QUI=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-net" + }, + { + "ID": "rt-ha:peer-r2", + "Network": "10.60.0.0/24", + "NetworkType": "1", + "Peer": "YC6sbWtvpB2d6W/XqsNIV9crsrXBoVDdwmhCoszjCps=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-net" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-r1.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.11" + }, + { + "Name": "peer-r2.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.12" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.11", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.11", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.12", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.12", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/golden/peer-r1.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/golden/peer-r1.json new file mode 100644 index 000000000..bd8bdecd0 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/golden/peer-r1.json @@ -0,0 +1,93 @@ +{ + "Serial": "26", + "peerConfig": { + "address": "100.64.0.11/10", + "sshConfig": {}, + "fqdn": "peer-r1.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "rt-ha:peer-r1", + "Network": "10.60.0.0/24", + "NetworkType": "1", + "Peer": "Iu6Lj1HqDXfgDh5RppHCRI6RO3lMlZBCndRNrdS7QUI=", + "Metric": "9999", + "Masquerade": true, + "NetID": "ha-net" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-r1.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.11" + }, + { + "Name": "peer-r2.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.12" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + } + ], + "routesFirewallRules": [ + { + "sourceRanges": [ + "0.0.0.0/0" + ], + "destination": "10.60.0.0/24", + "protocol": "ALL", + "portInfo": {}, + "RouteID": "rt-ha:peer-r1" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/nmdata.json new file mode 100644 index 000000000..8b81a585f --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/route-peer-groups-ha/nmdata.json @@ -0,0 +1,43 @@ +{ + "Network": {"Serial": 26}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r1": {"IP": "100.64.0.11", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r2": {"IP": "100.64.0.12", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]}, + "grp-routers": {"Peers": ["peer-r1", "peer-r2"]} + }, + "Policies": [ + { + "ID": "pol-conn", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["8080"], + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-routers"] + } + ] + } + ], + "Routes": [ + { + "ID": "rt-ha", + "NetID": "ha-net", + "Network": "10.60.0.0/24", + "NetworkType": 1, + "PeerGroups": ["grp-routers"], + "Groups": ["grp-dev"], + "Metric": 9999, + "Masquerade": true, + "Enabled": true + } + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/case.json new file mode 100644 index 000000000..a94d3653f --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/case.json @@ -0,0 +1,7 @@ +{ + "description": "Classic route distributed to grp-dev plus a network resource behind router peer-r with a resource policy; peer-a gets routes and route firewall rules, peer-r gets the routing-peer view.", + "peers": [ + "peer-a", + "peer-r" + ] +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/golden/peer-a.json new file mode 100644 index 000000000..9426bd846 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/golden/peer-a.json @@ -0,0 +1,86 @@ +{ + "Serial": "7", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "allowedIps": [ + "100.64.0.9/32" + ], + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-db:peer-r", + "Network": "10.10.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "db", + "keepRoute": true + }, + { + "ID": "rt-1", + "Network": "10.20.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "office-net" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.9", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.9", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/golden/peer-r.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/golden/peer-r.json new file mode 100644 index 000000000..e5971796d --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/golden/peer-r.json @@ -0,0 +1,138 @@ +{ + "Serial": "7", + "peerConfig": { + "address": "100.64.0.9/10", + "sshConfig": {}, + "fqdn": "peer-r.netbird.test", + "RoutingPeerDnsResolutionEnabled": true, + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "Routes": [ + { + "ID": "res-db:peer-r", + "Network": "10.10.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "db", + "keepRoute": true + }, + { + "ID": "rt-1", + "Network": "10.20.0.0/24", + "NetworkType": "1", + "Peer": "ImPDKs2PJxHA24/N7umWi8lfEf2B0B5W/7dYZUzNS3s=", + "Metric": "9999", + "Masquerade": true, + "NetID": "office-net" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + }, + { + "Name": "peer-r.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.9" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.2", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + }, + { + "PeerIP": "100.64.0.2", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "8080", + "PolicyID": "cG9sLWNvbm4=" + } + ], + "routesFirewallRules": [ + { + "sourceRanges": [ + "0.0.0.0/0" + ], + "destination": "10.20.0.0/24", + "protocol": "ALL", + "portInfo": {}, + "RouteID": "rt-1" + }, + { + "sourceRanges": [ + "100.64.0.1/32", + "100.64.0.2/32" + ], + "destination": "10.10.0.0/24", + "protocol": "TCP", + "portInfo": { + "port": 5432 + }, + "PolicyID": "cG9sLWRi", + "RouteID": "res-db:peer-r" + } + ], + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/nmdata.json new file mode 100644 index 000000000..795047f50 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/routes-resources/nmdata.json @@ -0,0 +1,97 @@ +{ + "Network": {"Serial": 7}, + "AccountSettings": {"RoutingPeerDNSResolutionEnabled": true}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-r": {"IP": "100.64.0.9", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]}, + "grp-routers": {"Peers": ["peer-r"]} + }, + "Policies": [ + { + "ID": "pol-conn", + "PublicID": "pol-conn-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Bidirectional": true, + "Ports": ["8080"], + "Sources": ["grp-dev"], + "Destinations": ["grp-routers"] + } + ] + }, + { + "ID": "pol-db", + "PublicID": "pol-db-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["5432"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-db", "Type": "subnet"} + } + ] + } + ], + "ResourcePolicies": { + "res-db": [ + { + "ID": "pol-db", + "PublicID": "pol-db-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["5432"], + "Sources": ["grp-dev"], + "DestinationResource": {"ID": "res-db", "Type": "subnet"} + } + ] + } + ] + }, + "Routes": [ + { + "ID": "rt-1", + "PublicID": "rt-1-pub", + "NetID": "office-net", + "Network": "10.20.0.0/24", + "NetworkType": 1, + "Peer": "peer-r", + "PeerID": "peer-r", + "Metric": 9999, + "Masquerade": true, + "Enabled": true, + "Groups": ["grp-dev"] + } + ], + "NetworkResources": [ + { + "ID": "res-db", + "PublicID": "res-db-pub", + "NetworkID": "net-1", + "Name": "db", + "Type": "subnet", + "Prefix": "10.10.0.0/24", + "Enabled": true + } + ], + "Routers": { + "net-1": { + "peer-r": {"Masquerade": true, "Metric": 9999, "Enabled": true} + } + }, + "NetworkXIDToPublicID": {"net-1": "net-1-pub"} +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/case.json new file mode 100644 index 000000000..78ac576d9 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/case.json @@ -0,0 +1,4 @@ +{ + "description": "netbird-ssh with AuthorizedGroups: grp-admins members may log in as root, grp-oncall (empty local-user list) as any machine user; peer-srv must receive both mappings in SshAuth, clients get plain TCP firewall rules. THE ENVELOPE EXPECTATION CURRENTLY FAILS, and should: encodeAuthorizedGroups/encodeGroupIDToUserIDs translate group keys via components.Groups, which never holds user-only groups, so the wire loses every authorized user while PeerConfig still reports sshEnabled — the peer runs sshd and denies every login. Pre-existing on main since PR #6711, not a regression. Fix the encoder, do not weaken this expectation.", + "peers": ["peer-srv", "peer-a"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/golden/peer-a.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/golden/peer-a.json new file mode 100644 index 000000000..6560bc74d --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/golden/peer-a.json @@ -0,0 +1,63 @@ +{ + "Serial": "10", + "peerConfig": { + "address": "100.64.0.1/10", + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "jEboa3bOv65XRbq8+8JuagiwWz+mM7Fc5MUfBQbOf6Y=", + "allowedIps": [ + "100.64.0.10/32" + ], + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.10", + "Direction": "OUT", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 22022, + "end": 22022 + } + }, + "PolicyID": "cG9sLXNzaA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/golden/peer-srv.json new file mode 100644 index 000000000..0a66bcbf0 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/golden/peer-srv.json @@ -0,0 +1,109 @@ +{ + "Serial": "10", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": { + "sshEnabled": true + }, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "AvldyrZ12Pf90jzf3AXmhPwg3UcI+jtJHfbpBlupvko=", + "allowedIps": [ + "100.64.0.2/32" + ], + "sshConfig": {}, + "fqdn": "peer-b.netbird.test", + "agentVersion": "0.60.0" + }, + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-b.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.2" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 22022, + "end": 22022 + } + }, + "PolicyID": "cG9sLXNzaA==" + }, + { + "PeerIP": "100.64.0.2", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 22022, + "end": 22022 + } + }, + "PolicyID": "cG9sLXNzaA==" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub", + "AuthorizedUsers": [ + "CF6q+CJTtcJE8MVIcpPyOw==", + "zSsmm7BAxWD/EuunyETFXA==", + "0M0MizUGgS6HAaJa0LjGKQ==" + ], + "machineUsers": { + "*": { + "indexes": [ + 2 + ] + }, + "root": { + "indexes": [ + 0, + 1 + ] + } + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/nmdata.json new file mode 100644 index 000000000..de0788ac9 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-groups/nmdata.json @@ -0,0 +1,37 @@ +{ + "Network": {"Serial": 10}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-b": {"IP": "100.64.0.2", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a", "peer-b"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "GroupIDToUserIDs": { + "grp-admins": ["user-x", "user-y"], + "grp-oncall": ["user-z"] + }, + "Policies": [ + { + "ID": "pol-ssh", + "PublicID": "pol-ssh-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "netbird-ssh", + "PortRanges": [{"Start": 22022, "End": 22022}], + "Sources": ["grp-dev"], + "Destinations": ["grp-srv"], + "AuthorizedGroups": { + "grp-admins": ["root"], + "grp-oncall": [] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/case.json new file mode 100644 index 000000000..e4799638e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/case.json @@ -0,0 +1,4 @@ +{ + "description": "netbird-ssh with a single AuthorizedUser: peer-srv's SshAuth maps the wildcard machine user to exactly user-solo.", + "peers": ["peer-srv"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/golden/peer-srv.json new file mode 100644 index 000000000..dde0d4014 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/golden/peer-srv.json @@ -0,0 +1,74 @@ +{ + "Serial": "11", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": { + "sshEnabled": true + }, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 22022, + "end": 22022 + } + }, + "PolicyID": "cG9sLXNzaC11c2Vy" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub", + "AuthorizedUsers": [ + "/6cwl49UgLozU42NCr0RUA==" + ], + "machineUsers": { + "*": { + "indexes": [ + 0 + ] + } + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/nmdata.json new file mode 100644 index 000000000..1c92329a8 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-authorized-user/nmdata.json @@ -0,0 +1,29 @@ +{ + "Network": {"Serial": 11}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "Policies": [ + { + "ID": "pol-ssh-user", + "PublicID": "pol-ssh-user-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "netbird-ssh", + "PortRanges": [{"Start": 22022, "End": 22022}], + "Sources": ["grp-dev"], + "Destinations": ["grp-srv"], + "AuthorizedUser": "user-solo" + } + ] + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/case.json new file mode 100644 index 000000000..8d4fb6c1a --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/case.json @@ -0,0 +1,4 @@ +{ + "description": "netbird-ssh with neither AuthorizedGroups nor AuthorizedUser falls back to the account AllowedUserIDs under the wildcard machine user — and works with the peer's own SSHEnabled left off, unlike legacy SSH.", + "peers": ["peer-srv"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/golden/peer-srv.json new file mode 100644 index 000000000..e63768a95 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/golden/peer-srv.json @@ -0,0 +1,76 @@ +{ + "Serial": "12", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": { + "sshEnabled": true + }, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "PortInfo": { + "range": { + "start": 22022, + "end": 22022 + } + }, + "PolicyID": "cG9sLXNzaC1hbnk=" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub", + "AuthorizedUsers": [ + "O8fBfcakRSAM4gX+YBNe+w==", + "1vwcS03btOdBRX0dhz0NRg==" + ], + "machineUsers": { + "*": { + "indexes": [ + 0, + 1 + ] + } + } + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/nmdata.json new file mode 100644 index 000000000..97bfe3342 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-fallback-allowed-users/nmdata.json @@ -0,0 +1,29 @@ +{ + "Network": {"Serial": 12}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "AllowedUserIDs": {"user-1": {}, "user-2": {}}, + "Policies": [ + { + "ID": "pol-ssh-any", + "PublicID": "pol-ssh-any-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "netbird-ssh", + "PortRanges": [{"Start": 22022, "End": 22022}], + "Sources": ["grp-dev"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/case.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/case.json new file mode 100644 index 000000000..1427fbd19 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/case.json @@ -0,0 +1,4 @@ +{ + "description": "A tcp/22 policy implies legacy SSH only when the destination peer has SSHEnabled; here it does not, so peer-srv gets the firewall rules but no authorized users.", + "peers": ["peer-srv"] +} \ No newline at end of file diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/golden/peer-srv.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/golden/peer-srv.json new file mode 100644 index 000000000..1ab6d4697 --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/golden/peer-srv.json @@ -0,0 +1,64 @@ +{ + "Serial": "13", + "peerConfig": { + "address": "100.64.0.10/10", + "sshConfig": {}, + "fqdn": "peer-srv.netbird.test", + "autoUpdate": {} + }, + "remotePeers": [ + { + "wgPubKey": "vblMc9U8RAI6cVopcKEMTVT6lVC3D9nTTMSwot5d3L4=", + "allowedIps": [ + "100.64.0.1/32" + ], + "sshConfig": {}, + "fqdn": "peer-a.netbird.test", + "agentVersion": "0.60.0" + } + ], + "DNSConfig": { + "ServiceEnable": true, + "CustomZones": [ + { + "Domain": "netbird.test.", + "Records": [ + { + "Name": "peer-a.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.1" + }, + { + "Name": "peer-srv.netbird.test", + "Type": "1", + "Class": "IN", + "TTL": "300", + "RData": "100.64.0.10" + } + ] + } + ], + "ForwarderPort": "22054" + }, + "FirewallRules": [ + { + "PeerIP": "100.64.0.1", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXRjcDIy" + }, + { + "PeerIP": "100.64.0.1", + "Direction": "OUT", + "Protocol": "TCP", + "Port": "22", + "PolicyID": "cG9sLXRjcDIy" + } + ], + "routesFirewallRulesIsEmpty": true, + "sshAuth": { + "UserIDClaim": "sub" + } +} diff --git a/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/nmdata.json b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/nmdata.json new file mode 100644 index 000000000..f00d0f06e --- /dev/null +++ b/management/internals/controllers/network_map/nmaptest/testdata/cases/ssh-legacy-disabled/nmdata.json @@ -0,0 +1,30 @@ +{ + "Network": {"Serial": 13}, + "Peers": { + "peer-a": {"IP": "100.64.0.1", "Meta": {"WtVersion": "0.60.0"}}, + "peer-srv": {"IP": "100.64.0.10", "Meta": {"WtVersion": "0.60.0"}} + }, + "Groups": { + "grp-dev": {"Peers": ["peer-a"]}, + "grp-srv": {"Peers": ["peer-srv"]} + }, + "AllowedUserIDs": {"user-1": {}}, + "Policies": [ + { + "ID": "pol-tcp22", + "PublicID": "pol-tcp22-pub", + "Enabled": true, + "Rules": [ + { + "Enabled": true, + "Action": "accept", + "Protocol": "tcp", + "Ports": ["22"], + "Bidirectional": true, + "Sources": ["grp-dev"], + "Destinations": ["grp-srv"] + } + ] + } + ] +} \ No newline at end of file