mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-07 23:41:29 +02:00
Merge branch 'main' into embedded-vnc
This commit is contained in:
18
shared/management/networkmap/nmdata/account_settings.go
Normal file
18
shared/management/networkmap/nmdata/account_settings.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package nmdata
|
||||
|
||||
import "time"
|
||||
|
||||
// AccountSettingsInfo is the slim twin of types.AccountSettingsInfo.
|
||||
type AccountSettingsInfo struct {
|
||||
PeerLoginExpirationEnabled bool
|
||||
PeerLoginExpiration time.Duration
|
||||
PeerInactivityExpirationEnabled bool
|
||||
PeerInactivityExpiration time.Duration
|
||||
DNSDomain string
|
||||
IPv6EnabledGroups []string
|
||||
RoutingPeerDNSResolutionEnabled bool
|
||||
LazyConnectionEnabled bool
|
||||
AutoUpdateVersion string
|
||||
AutoUpdateAlways bool
|
||||
MetricsPushEnabled bool
|
||||
}
|
||||
18
shared/management/networkmap/nmdata/dns.go
Normal file
18
shared/management/networkmap/nmdata/dns.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package nmdata
|
||||
|
||||
// SimpleRecord is the slim twin of dns.SimpleRecord.
|
||||
type SimpleRecord struct {
|
||||
Name string
|
||||
Type int
|
||||
Class string
|
||||
TTL int
|
||||
RData string
|
||||
}
|
||||
|
||||
// CustomZone is the slim twin of dns.CustomZone.
|
||||
type CustomZone struct {
|
||||
Domain string
|
||||
Records []SimpleRecord
|
||||
SearchDomainDisabled bool
|
||||
NonAuthoritative bool
|
||||
}
|
||||
6
shared/management/networkmap/nmdata/dns_settings.go
Normal file
6
shared/management/networkmap/nmdata/dns_settings.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package nmdata
|
||||
|
||||
// DNSSettings is the slim twin of types.DNSSettings.
|
||||
type DNSSettings struct {
|
||||
DisabledManagementGroups []string
|
||||
}
|
||||
30
shared/management/networkmap/nmdata/group.go
Normal file
30
shared/management/networkmap/nmdata/group.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package nmdata
|
||||
|
||||
import "slices"
|
||||
|
||||
// GroupAllName is the reserved name of the default group that contains every
|
||||
// peer in an account.
|
||||
const GroupAllName = "All"
|
||||
|
||||
// Group is the slim twin of types.Group.
|
||||
type Group struct {
|
||||
ID string
|
||||
Name string
|
||||
PublicID string
|
||||
Peers []string
|
||||
Resources []Resource
|
||||
}
|
||||
|
||||
func (g *Group) IsGroupAll() bool {
|
||||
return g.Name == GroupAllName
|
||||
}
|
||||
|
||||
func (g *Group) Copy() *Group {
|
||||
return &Group{
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
PublicID: g.PublicID,
|
||||
Peers: slices.Clone(g.Peers),
|
||||
Resources: slices.Clone(g.Resources),
|
||||
}
|
||||
}
|
||||
84
shared/management/networkmap/nmdata/group_test.go
Normal file
84
shared/management/networkmap/nmdata/group_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGroupCopy_AllFieldsCopied fills every Group field with a unique non-zero
|
||||
// value derived from its field path, so a field added to Group but forgotten
|
||||
// in Copy fails here by name without the test needing an update. The unique
|
||||
// per-path values also catch fields swapped inside Copy.
|
||||
func TestGroupCopy_AllFieldsCopied(t *testing.T) {
|
||||
src := &Group{}
|
||||
seed := 0
|
||||
fillValue(t, reflect.ValueOf(src).Elem(), "Group", &seed)
|
||||
|
||||
copied := src.Copy()
|
||||
|
||||
srcV := reflect.ValueOf(src).Elem()
|
||||
copiedV := reflect.ValueOf(copied).Elem()
|
||||
for i := 0; i < srcV.NumField(); i++ {
|
||||
name := srcV.Type().Field(i).Name
|
||||
if !reflect.DeepEqual(srcV.Field(i).Interface(), copiedV.Field(i).Interface()) {
|
||||
t.Errorf("field %s not copied: src=%#v copy=%#v",
|
||||
name, srcV.Field(i).Interface(), copiedV.Field(i).Interface())
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < srcV.NumField(); i++ {
|
||||
f := srcV.Field(i)
|
||||
if f.Kind() != reflect.Slice || f.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
name := srcV.Type().Field(i).Name
|
||||
fillValue(t, f.Index(0), name+"-mutated", &seed)
|
||||
if reflect.DeepEqual(f.Interface(), copiedV.Field(i).Interface()) {
|
||||
t.Errorf("field %s shares memory with the copy", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillValue sets v to a deterministic non-zero value derived from its field
|
||||
// path. Kinds it does not handle fail the test loudly, so the filler is
|
||||
// extended together with the struct instead of silently under-testing new
|
||||
// fields.
|
||||
func fillValue(t *testing.T, v reflect.Value, path string, seed *int) {
|
||||
t.Helper()
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
v.SetString(path)
|
||||
case reflect.Bool:
|
||||
v.SetBool(true)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
*seed++
|
||||
v.SetInt(int64(*seed))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
*seed++
|
||||
v.SetUint(uint64(*seed))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
*seed++
|
||||
v.SetFloat(float64(*seed))
|
||||
case reflect.Slice:
|
||||
s := reflect.MakeSlice(v.Type(), 2, 2)
|
||||
fillValue(t, s.Index(0), path+"[0]", seed)
|
||||
fillValue(t, s.Index(1), path+"[1]", seed)
|
||||
v.Set(s)
|
||||
case reflect.Struct:
|
||||
settable := 0
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := v.Field(i)
|
||||
if !f.CanSet() {
|
||||
continue
|
||||
}
|
||||
settable++
|
||||
fillValue(t, f, path+"."+v.Type().Field(i).Name, seed)
|
||||
}
|
||||
if settable == 0 {
|
||||
t.Fatalf("struct %s at %s has no settable fields — extend fillValue to construct it", v.Type(), path)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unsupported kind %s at %s — extend fillValue", v.Kind(), path)
|
||||
}
|
||||
}
|
||||
24
shared/management/networkmap/nmdata/nameserver.go
Normal file
24
shared/management/networkmap/nmdata/nameserver.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package nmdata
|
||||
|
||||
import "net/netip"
|
||||
|
||||
// NameServerGroup is the slim twin of dns.NameServerGroup.
|
||||
type NameServerGroup struct {
|
||||
ID string
|
||||
PublicID string
|
||||
Name string
|
||||
Description string
|
||||
NameServers []NameServer
|
||||
Groups []string
|
||||
Primary bool
|
||||
Domains []string
|
||||
Enabled bool
|
||||
SearchDomainsEnabled bool
|
||||
}
|
||||
|
||||
// NameServer is the slim twin of dns.NameServer.
|
||||
type NameServer struct {
|
||||
IP netip.Addr
|
||||
NSType int
|
||||
Port int
|
||||
}
|
||||
16
shared/management/networkmap/nmdata/network.go
Normal file
16
shared/management/networkmap/nmdata/network.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package nmdata
|
||||
|
||||
import "net"
|
||||
|
||||
// Network is the slim twin of types.Network.
|
||||
type Network struct {
|
||||
Identifier string
|
||||
Net net.IPNet
|
||||
NetV6 net.IPNet
|
||||
Dns string
|
||||
Serial int64
|
||||
}
|
||||
|
||||
func (n *Network) CurrentSerial() uint64 {
|
||||
return uint64(n.Serial)
|
||||
}
|
||||
18
shared/management/networkmap/nmdata/network_resource.go
Normal file
18
shared/management/networkmap/nmdata/network_resource.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package nmdata
|
||||
|
||||
import "net/netip"
|
||||
|
||||
// NetworkResource is the slim twin of resources/types.NetworkResource.
|
||||
type NetworkResource struct {
|
||||
ID string
|
||||
NetworkID string
|
||||
AccountID string
|
||||
PublicID string
|
||||
Name string
|
||||
Description string
|
||||
Type string
|
||||
Address string // TODO: isn't persisted in the DB
|
||||
Domain string
|
||||
Prefix netip.Prefix
|
||||
Enabled bool
|
||||
}
|
||||
10
shared/management/networkmap/nmdata/network_router.go
Normal file
10
shared/management/networkmap/nmdata/network_router.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package nmdata
|
||||
|
||||
// NetworkRouter is the slim twin of routers/types.NetworkRouter.
|
||||
type NetworkRouter struct {
|
||||
PublicID string
|
||||
PeerGroups []string
|
||||
Masquerade bool
|
||||
Metric int
|
||||
Enabled bool
|
||||
}
|
||||
129
shared/management/networkmap/nmdata/peer.go
Normal file
129
shared/management/networkmap/nmdata/peer.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Peer capability constants mirror the proto enum values.
|
||||
const (
|
||||
PeerCapabilitySourcePrefixes int32 = 1
|
||||
PeerCapabilityIPv6Overlay int32 = 2
|
||||
PeerCapabilityComponentNetworkMap int32 = 3
|
||||
)
|
||||
|
||||
// Peer is the slim twin of peer.Peer.
|
||||
type Peer struct {
|
||||
ID string
|
||||
Key string
|
||||
SSHKey string
|
||||
DNSLabel string
|
||||
UserID string
|
||||
SSHEnabled bool
|
||||
LoginExpirationEnabled bool
|
||||
LastLogin *time.Time
|
||||
IP netip.Addr
|
||||
IPv6 netip.Addr
|
||||
RequiresApproval bool
|
||||
ExtraDNSLabels []string
|
||||
Meta PeerSystemMeta
|
||||
ProxyMeta ProxyMeta
|
||||
Location PeerLocation
|
||||
}
|
||||
|
||||
// ProxyMeta is the slim twin of peer.ProxyMeta.
|
||||
type ProxyMeta struct {
|
||||
Embedded bool
|
||||
Cluster string
|
||||
}
|
||||
|
||||
// PeerSystemMeta is the slim twin of peer.PeerSystemMeta.
|
||||
type PeerSystemMeta struct {
|
||||
WtVersion string
|
||||
GoOS string
|
||||
OSVersion string
|
||||
KernelVersion string
|
||||
NetworkAddresses []NetworkAddress
|
||||
Files []File
|
||||
Capabilities []int32
|
||||
Flags Flags
|
||||
SyncMessageVersion int
|
||||
}
|
||||
|
||||
// Flags is the slim twin of peer.Flags.
|
||||
type Flags struct {
|
||||
ServerSSHAllowed bool
|
||||
DisableIPv6 bool
|
||||
}
|
||||
|
||||
// NetworkAddress is the slim twin of peer.NetworkAddress.
|
||||
type NetworkAddress struct {
|
||||
NetIP netip.Prefix
|
||||
}
|
||||
|
||||
// File is the slim twin of peer.File.
|
||||
type File struct {
|
||||
Path string
|
||||
ProcessIsRunning bool
|
||||
}
|
||||
|
||||
// PeerLocation is the slim twin of peer.Location.
|
||||
type PeerLocation struct {
|
||||
CountryCode string
|
||||
CityName string
|
||||
ConnectionIP net.IP
|
||||
}
|
||||
|
||||
func (p *Peer) HasCapability(capability int32) bool {
|
||||
return slices.Contains(p.Meta.Capabilities, capability)
|
||||
}
|
||||
|
||||
func (p *Peer) SupportsIPv6() bool {
|
||||
return !p.Meta.Flags.DisableIPv6 && p.HasCapability(PeerCapabilityIPv6Overlay)
|
||||
}
|
||||
|
||||
func (p *Peer) SupportsSourcePrefixes() bool {
|
||||
return p.HasCapability(PeerCapabilitySourcePrefixes)
|
||||
}
|
||||
|
||||
func (p *Peer) AddedWithSSOLogin() bool {
|
||||
return p.UserID != ""
|
||||
}
|
||||
|
||||
func (p *Peer) FQDN(dnsDomain string) string {
|
||||
if dnsDomain == "" {
|
||||
return ""
|
||||
}
|
||||
return p.DNSLabel + "." + dnsDomain
|
||||
}
|
||||
|
||||
func (p *Peer) GetLastLogin() time.Time {
|
||||
if p.LastLogin != nil {
|
||||
return *p.LastLogin
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// SessionExpiresAt mirrors peer.Peer.SessionExpiresAt.
|
||||
func (p *Peer) SessionExpiresAt(accountExpirationEnabled bool, expiresIn time.Duration) time.Time {
|
||||
if !accountExpirationEnabled || !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
|
||||
return time.Time{}
|
||||
}
|
||||
last := p.GetLastLogin()
|
||||
if last.IsZero() {
|
||||
return time.Time{}
|
||||
}
|
||||
return last.Add(expiresIn).UTC()
|
||||
}
|
||||
|
||||
func (p *Peer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) {
|
||||
if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
|
||||
return false, 0
|
||||
}
|
||||
expiresAt := p.GetLastLogin().Add(expiresIn)
|
||||
now := time.Now()
|
||||
timeLeft := expiresAt.Sub(now)
|
||||
return timeLeft <= 0, timeLeft
|
||||
}
|
||||
98
shared/management/networkmap/nmdata/policy.go
Normal file
98
shared/management/networkmap/nmdata/policy.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package nmdata
|
||||
|
||||
const (
|
||||
policyRuleProtocolALL = "all"
|
||||
policyRuleProtocolTCP = "tcp"
|
||||
|
||||
defaultSSHPortString = "22"
|
||||
nativeSSHPortString = "22022"
|
||||
defaultSSHPortNumber uint16 = 22
|
||||
nativeSSHPortNumber uint16 = 22022
|
||||
)
|
||||
|
||||
// Policy is the slim twin of types.Policy.
|
||||
type Policy struct {
|
||||
ID string
|
||||
PublicID string
|
||||
Enabled bool
|
||||
SourcePostureChecks []string
|
||||
Rules []*PolicyRule
|
||||
}
|
||||
|
||||
// PolicyRule is the slim twin of types.PolicyRule.
|
||||
type PolicyRule struct {
|
||||
ID string
|
||||
PolicyID string
|
||||
Enabled bool
|
||||
Action string
|
||||
Protocol string
|
||||
Bidirectional bool
|
||||
Sources []string
|
||||
Destinations []string
|
||||
SourceResource Resource
|
||||
DestinationResource Resource
|
||||
Ports []string
|
||||
PortRanges []RulePortRange
|
||||
AuthorizedGroups map[string][]string
|
||||
AuthorizedUser string
|
||||
SessionPubKey string
|
||||
SessionDisplayName string
|
||||
}
|
||||
|
||||
// RulePortRange is the slim twin of types.RulePortRange.
|
||||
type RulePortRange struct {
|
||||
Start uint16
|
||||
End uint16
|
||||
}
|
||||
|
||||
// Resource is the slim twin of types.Resource.
|
||||
type Resource struct {
|
||||
ID string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (p *Policy) SourceGroups() []string {
|
||||
if len(p.Rules) == 1 && p.Rules[0] != nil {
|
||||
return p.Rules[0].Sources
|
||||
}
|
||||
groups := make(map[string]struct{}, len(p.Rules))
|
||||
for _, rule := range p.Rules {
|
||||
if rule == nil {
|
||||
continue
|
||||
}
|
||||
for _, source := range rule.Sources {
|
||||
groups[source] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
groupIDs := make([]string, 0, len(groups))
|
||||
for groupID := range groups {
|
||||
groupIDs = append(groupIDs, groupID)
|
||||
}
|
||||
|
||||
return groupIDs
|
||||
}
|
||||
|
||||
// PolicyRuleImpliesLegacySSH is the twin-typed sibling of types.PolicyRuleImpliesLegacySSH.
|
||||
func PolicyRuleImpliesLegacySSH(rule *PolicyRule) bool {
|
||||
return rule.Protocol == policyRuleProtocolALL ||
|
||||
(rule.Protocol == policyRuleProtocolTCP && (portsIncludesSSH(rule.Ports) || portRangeIncludesSSH(rule.PortRanges)))
|
||||
}
|
||||
|
||||
func portRangeIncludesSSH(portRanges []RulePortRange) bool {
|
||||
for _, pr := range portRanges {
|
||||
if (pr.Start <= defaultSSHPortNumber && pr.End >= defaultSSHPortNumber) || (pr.Start <= nativeSSHPortNumber && pr.End >= nativeSSHPortNumber) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func portsIncludesSSH(ports []string) bool {
|
||||
for _, port := range ports {
|
||||
if port == defaultSSHPortString || port == nativeSSHPortString {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
67
shared/management/networkmap/nmdata/posture.go
Normal file
67
shared/management/networkmap/nmdata/posture.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package nmdata
|
||||
|
||||
const (
|
||||
checkActionAllow = "allow"
|
||||
checkActionDeny = "deny"
|
||||
)
|
||||
|
||||
// PostureChecks is the slim twin of posture.Checks.
|
||||
type PostureChecks struct {
|
||||
ID string
|
||||
Checks ChecksDefinition
|
||||
}
|
||||
|
||||
// ChecksDefinition is the slim twin of posture.ChecksDefinition.
|
||||
type ChecksDefinition struct {
|
||||
NBVersionCheck *NBVersionCheck
|
||||
OSVersionCheck *OSVersionCheck
|
||||
GeoLocationCheck *GeoLocationCheck
|
||||
PeerNetworkRangeCheck *PeerNetworkRangeCheck
|
||||
ProcessCheck *ProcessCheck
|
||||
}
|
||||
|
||||
// Check is the slim twin of posture.Check. It is sealed: only the check types
|
||||
// in this package implement it.
|
||||
type Check interface {
|
||||
check(peer *Peer) (bool, error)
|
||||
}
|
||||
|
||||
// Passes reports whether the peer satisfies every check in this bundle. It
|
||||
// mirrors the server posture path: a check returning (false, _) — including on
|
||||
// an evaluation error — fails the bundle.
|
||||
func (pc *PostureChecks) Passes(peer *Peer) bool {
|
||||
return PassesChecks(pc.GetChecks(), peer)
|
||||
}
|
||||
|
||||
// PassesChecks is Passes over an already built check set, for callers that
|
||||
// evaluate many peers against the same bundle.
|
||||
func PassesChecks(checks []Check, peer *Peer) bool {
|
||||
for _, c := range checks {
|
||||
valid, _ := c.check(peer)
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetChecks returns the initialized checks in the same order as posture.Checks.GetChecks.
|
||||
func (pc *PostureChecks) GetChecks() []Check {
|
||||
var checks []Check
|
||||
if pc.Checks.NBVersionCheck != nil {
|
||||
checks = append(checks, pc.Checks.NBVersionCheck)
|
||||
}
|
||||
if pc.Checks.OSVersionCheck != nil {
|
||||
checks = append(checks, pc.Checks.OSVersionCheck)
|
||||
}
|
||||
if pc.Checks.GeoLocationCheck != nil {
|
||||
checks = append(checks, pc.Checks.GeoLocationCheck)
|
||||
}
|
||||
if pc.Checks.PeerNetworkRangeCheck != nil {
|
||||
checks = append(checks, pc.Checks.PeerNetworkRangeCheck)
|
||||
}
|
||||
if pc.Checks.ProcessCheck != nil {
|
||||
checks = append(checks, pc.Checks.ProcessCheck)
|
||||
}
|
||||
return checks
|
||||
}
|
||||
45
shared/management/networkmap/nmdata/posture_geo_location.go
Normal file
45
shared/management/networkmap/nmdata/posture_geo_location.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package nmdata
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GeoLocation is the slim twin of posture.Location.
|
||||
type GeoLocation struct {
|
||||
CountryCode string
|
||||
CityName string
|
||||
}
|
||||
|
||||
// GeoLocationCheck is the slim twin of posture.GeoLocationCheck.
|
||||
type GeoLocationCheck struct {
|
||||
Locations []GeoLocation
|
||||
Action string
|
||||
}
|
||||
|
||||
func (g *GeoLocationCheck) check(peer *Peer) (bool, error) {
|
||||
if peer.Location.CountryCode == "" && peer.Location.CityName == "" {
|
||||
return false, fmt.Errorf("peer's location is not set")
|
||||
}
|
||||
|
||||
for _, loc := range g.Locations {
|
||||
if loc.CountryCode == peer.Location.CountryCode {
|
||||
if loc.CityName == "" || loc.CityName == peer.Location.CityName {
|
||||
switch g.Action {
|
||||
case checkActionDeny:
|
||||
return false, nil
|
||||
case checkActionAllow:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if g.Action == checkActionDeny {
|
||||
return true, nil
|
||||
}
|
||||
if g.Action == checkActionAllow {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
|
||||
}
|
||||
38
shared/management/networkmap/nmdata/posture_nb_version.go
Normal file
38
shared/management/networkmap/nmdata/posture_nb_version.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// NBVersionCheck is the slim twin of posture.NBVersionCheck.
|
||||
type NBVersionCheck struct {
|
||||
MinVersion string
|
||||
}
|
||||
|
||||
func (n *NBVersionCheck) check(peer *Peer) (bool, error) {
|
||||
return meetsMinVersion(n.MinVersion, peer.Meta.WtVersion)
|
||||
}
|
||||
|
||||
func meetsMinVersion(minVer, peerVer string) (bool, error) {
|
||||
peerVer = sanitizeVersion(peerVer)
|
||||
minVer = sanitizeVersion(minVer)
|
||||
|
||||
peerNBVer, err := version.NewVersion(peerVer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
constraints, err := version.NewConstraint(">= " + minVer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return constraints.Check(peerNBVer), nil
|
||||
}
|
||||
|
||||
func sanitizeVersion(v string) string {
|
||||
parts := strings.Split(v, "-")
|
||||
return parts[0]
|
||||
}
|
||||
62
shared/management/networkmap/nmdata/posture_network.go
Normal file
62
shared/management/networkmap/nmdata/posture_network.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// PeerNetworkRangeCheck is the slim twin of posture.PeerNetworkRangeCheck.
|
||||
type PeerNetworkRangeCheck struct {
|
||||
Action string
|
||||
Ranges []netip.Prefix
|
||||
}
|
||||
|
||||
func (p *PeerNetworkRangeCheck) check(peer *Peer) (bool, error) {
|
||||
peerPrefixes := make([]netip.Prefix, 0, len(peer.Meta.NetworkAddresses)+1)
|
||||
for _, peerNetAddr := range peer.Meta.NetworkAddresses {
|
||||
peerPrefixes = append(peerPrefixes, peerNetAddr.NetIP)
|
||||
}
|
||||
if connIP := peer.Location.ConnectionIP; len(connIP) > 0 {
|
||||
if addr, ok := netip.AddrFromSlice(connIP); ok {
|
||||
addr = addr.Unmap()
|
||||
peerPrefixes = append(peerPrefixes, netip.PrefixFrom(addr, addr.BitLen()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(peerPrefixes) == 0 {
|
||||
return false, fmt.Errorf("peer's does not contain peer network range addresses")
|
||||
}
|
||||
|
||||
for _, peerPrefix := range peerPrefixes {
|
||||
for _, rangePrefix := range p.Ranges {
|
||||
if !prefixContains(rangePrefix, peerPrefix) {
|
||||
continue
|
||||
}
|
||||
switch p.Action {
|
||||
case checkActionDeny:
|
||||
return false, nil
|
||||
case checkActionAllow:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("invalid peer network range check action: %s", p.Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.Action == checkActionDeny {
|
||||
return true, nil
|
||||
}
|
||||
if p.Action == checkActionAllow {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("invalid peer network range check action: %s", p.Action)
|
||||
}
|
||||
|
||||
func prefixContains(outer, inner netip.Prefix) bool {
|
||||
outer = outer.Masked()
|
||||
inner = inner.Masked()
|
||||
return outer.Bits() <= inner.Bits() &&
|
||||
outer.Addr().BitLen() == inner.Addr().BitLen() &&
|
||||
outer.Contains(inner.Addr())
|
||||
}
|
||||
79
shared/management/networkmap/nmdata/posture_os_version.go
Normal file
79
shared/management/networkmap/nmdata/posture_os_version.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// MinVersionCheck is the slim twin of posture.MinVersionCheck.
|
||||
type MinVersionCheck struct {
|
||||
MinVersion string
|
||||
}
|
||||
|
||||
// MinKernelVersionCheck is the slim twin of posture.MinKernelVersionCheck.
|
||||
type MinKernelVersionCheck struct {
|
||||
MinKernelVersion string
|
||||
}
|
||||
|
||||
// OSVersionCheck is the slim twin of posture.OSVersionCheck.
|
||||
type OSVersionCheck struct {
|
||||
Android *MinVersionCheck
|
||||
Darwin *MinVersionCheck
|
||||
Ios *MinVersionCheck
|
||||
Linux *MinKernelVersionCheck
|
||||
Windows *MinKernelVersionCheck
|
||||
}
|
||||
|
||||
func (c *OSVersionCheck) check(peer *Peer) (bool, error) {
|
||||
switch peer.Meta.GoOS {
|
||||
case "android":
|
||||
return checkMinVersion(peer.Meta.OSVersion, c.Android)
|
||||
case "darwin":
|
||||
return checkMinVersion(peer.Meta.OSVersion, c.Darwin)
|
||||
case "ios":
|
||||
return checkMinVersion(peer.Meta.OSVersion, c.Ios)
|
||||
case "linux":
|
||||
kernelVersion := strings.Split(peer.Meta.KernelVersion, "-")[0]
|
||||
return checkMinKernelVersion(kernelVersion, c.Linux)
|
||||
case "windows":
|
||||
return checkMinKernelVersion(peer.Meta.KernelVersion, c.Windows)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func checkMinVersion(peerVersion string, check *MinVersionCheck) (bool, error) {
|
||||
if check == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
peerNBVersion, err := version.NewVersion(peerVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
constraints, err := version.NewConstraint(">= " + check.MinVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return constraints.Check(peerNBVersion), nil
|
||||
}
|
||||
|
||||
func checkMinKernelVersion(peerVersion string, check *MinKernelVersionCheck) (bool, error) {
|
||||
if check == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
peerNBVersion, err := version.NewVersion(peerVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
constraints, err := version.NewConstraint(">= " + check.MinKernelVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return constraints.Check(peerNBVersion), nil
|
||||
}
|
||||
56
shared/management/networkmap/nmdata/posture_process.go
Normal file
56
shared/management/networkmap/nmdata/posture_process.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Process is the slim twin of posture.Process.
|
||||
type Process struct {
|
||||
LinuxPath string
|
||||
MacPath string
|
||||
WindowsPath string
|
||||
}
|
||||
|
||||
// ProcessCheck is the slim twin of posture.ProcessCheck.
|
||||
type ProcessCheck struct {
|
||||
Processes []Process
|
||||
}
|
||||
|
||||
func (p *ProcessCheck) check(peer *Peer) (bool, error) {
|
||||
peerActiveProcesses := extractPeerActiveProcesses(peer.Meta.Files)
|
||||
|
||||
var pathSelector func(Process) string
|
||||
switch peer.Meta.GoOS {
|
||||
case "linux":
|
||||
pathSelector = func(process Process) string { return process.LinuxPath }
|
||||
case "darwin":
|
||||
pathSelector = func(process Process) string { return process.MacPath }
|
||||
case "windows":
|
||||
pathSelector = func(process Process) string { return process.WindowsPath }
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported peer's operating system: %s", peer.Meta.GoOS)
|
||||
}
|
||||
|
||||
return p.areAllProcessesRunning(peerActiveProcesses, pathSelector), nil
|
||||
}
|
||||
|
||||
func (p *ProcessCheck) areAllProcessesRunning(activeProcesses []string, pathSelector func(Process) string) bool {
|
||||
for _, process := range p.Processes {
|
||||
path := pathSelector(process)
|
||||
if path == "" || !slices.Contains(activeProcesses, path) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func extractPeerActiveProcesses(files []File) []string {
|
||||
activeProcesses := make([]string, 0, len(files))
|
||||
for _, file := range files {
|
||||
if file.ProcessIsRunning {
|
||||
activeProcesses = append(activeProcesses, file.Path)
|
||||
}
|
||||
}
|
||||
return activeProcesses
|
||||
}
|
||||
108
shared/management/networkmap/nmdata/route.go
Normal file
108
shared/management/networkmap/nmdata/route.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/domain"
|
||||
)
|
||||
|
||||
// NetworkType mirrors route.NetworkType iota values.
|
||||
const (
|
||||
NetworkTypeInvalid = 0
|
||||
NetworkTypeIPv4 = 1
|
||||
NetworkTypeIPv6 = 2
|
||||
NetworkTypeDomain = 3
|
||||
|
||||
haSeparator = "|"
|
||||
)
|
||||
|
||||
// Route is the slim twin of route.Route.
|
||||
type Route struct {
|
||||
ID string
|
||||
AccountID string
|
||||
PublicID string
|
||||
Network netip.Prefix
|
||||
Domains domain.List
|
||||
KeepRoute bool
|
||||
NetID string
|
||||
Description string
|
||||
Peer string
|
||||
PeerID string
|
||||
PeerGroups []string
|
||||
NetworkType int
|
||||
Masquerade bool
|
||||
Metric int
|
||||
Enabled bool
|
||||
Groups []string
|
||||
AccessControlGroups []string
|
||||
SkipAutoApply bool
|
||||
}
|
||||
|
||||
func (r *Route) Equal(other *Route) bool {
|
||||
if r == nil && other == nil {
|
||||
return true
|
||||
} else if r == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return other.ID == r.ID &&
|
||||
other.Description == r.Description &&
|
||||
other.NetID == r.NetID &&
|
||||
other.Network == r.Network &&
|
||||
slices.Equal(r.Domains, other.Domains) &&
|
||||
other.KeepRoute == r.KeepRoute &&
|
||||
other.NetworkType == r.NetworkType &&
|
||||
other.Peer == r.Peer &&
|
||||
other.PeerID == r.PeerID &&
|
||||
other.Metric == r.Metric &&
|
||||
other.Masquerade == r.Masquerade &&
|
||||
other.Enabled == r.Enabled &&
|
||||
slices.Equal(r.Groups, other.Groups) &&
|
||||
slices.Equal(r.PeerGroups, other.PeerGroups) &&
|
||||
slices.Equal(r.AccessControlGroups, other.AccessControlGroups) &&
|
||||
other.SkipAutoApply == r.SkipAutoApply
|
||||
}
|
||||
|
||||
func (r *Route) IsDynamic() bool {
|
||||
return r.NetworkType == NetworkTypeDomain
|
||||
}
|
||||
|
||||
func (r *Route) NetString() string {
|
||||
if r.IsDynamic() && r.Domains != nil {
|
||||
return r.Domains.SafeString()
|
||||
}
|
||||
return r.Network.String()
|
||||
}
|
||||
|
||||
func (r *Route) GetHAUniqueID() string {
|
||||
return r.NetID + haSeparator + r.NetString()
|
||||
}
|
||||
|
||||
func (r *Route) GetResourceID() string {
|
||||
return strings.Split(r.ID, ":")[0]
|
||||
}
|
||||
|
||||
func (r *Route) Copy() *Route {
|
||||
return &Route{
|
||||
ID: r.ID,
|
||||
AccountID: r.AccountID,
|
||||
PublicID: r.PublicID,
|
||||
Network: r.Network,
|
||||
Domains: slices.Clone(r.Domains),
|
||||
KeepRoute: r.KeepRoute,
|
||||
NetID: r.NetID,
|
||||
Description: r.Description,
|
||||
Peer: r.Peer,
|
||||
PeerID: r.PeerID,
|
||||
PeerGroups: slices.Clone(r.PeerGroups),
|
||||
NetworkType: r.NetworkType,
|
||||
Masquerade: r.Masquerade,
|
||||
Metric: r.Metric,
|
||||
Enabled: r.Enabled,
|
||||
Groups: slices.Clone(r.Groups),
|
||||
AccessControlGroups: slices.Clone(r.AccessControlGroups),
|
||||
SkipAutoApply: r.SkipAutoApply,
|
||||
}
|
||||
}
|
||||
25
shared/management/networkmap/nmdata/service.go
Normal file
25
shared/management/networkmap/nmdata/service.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package nmdata
|
||||
|
||||
// Service is the slim twin of the reverse-proxy service.Service. It carries
|
||||
// only the state proxy-policy injection reads: the persisted reverse-proxy
|
||||
// services and the in-memory ones synthesised from agent-network state, which
|
||||
// are never written to the database.
|
||||
type Service struct {
|
||||
ID string
|
||||
Enabled bool
|
||||
Private bool
|
||||
Mode string
|
||||
ProxyCluster string
|
||||
AccessGroups []string
|
||||
Targets []*ServiceTarget
|
||||
}
|
||||
|
||||
// ServiceTarget is the slim twin of service.Target.
|
||||
type ServiceTarget struct {
|
||||
Enabled bool
|
||||
Path string
|
||||
Port uint16
|
||||
Protocol string
|
||||
TargetID string
|
||||
TargetType string
|
||||
}
|
||||
Reference in New Issue
Block a user