mirror of
https://github.com/netbirdio/netbird.git
synced 2025-04-14 14:38:27 +02:00
Add private network posture check (#1606)
* wip: Add PrivateNetworkCheck checks interface implementation * use generic CheckAction constant * Add private network check to posture checks * Fix copy function target in posture checks * Add network check functionality to posture package * regenerate the openapi specs * Update Posture Check actions in test file * Remove unused function * Refactor network address handling in PrivateNetworkCheck * Refactor Prefixes to Ranges in private network checks * Implement private network checks in posture checks handler tests * Add test for check copy * Add gorm serializer for network range
This commit is contained in:
parent
bbea4c3cc3
commit
a47c69c472
@ -862,6 +862,8 @@ components:
|
|||||||
$ref: '#/components/schemas/OSVersionCheck'
|
$ref: '#/components/schemas/OSVersionCheck'
|
||||||
geo_location_check:
|
geo_location_check:
|
||||||
$ref: '#/components/schemas/GeoLocationCheck'
|
$ref: '#/components/schemas/GeoLocationCheck'
|
||||||
|
private_network_check:
|
||||||
|
$ref: '#/components/schemas/PrivateNetworkCheck'
|
||||||
NBVersionCheck:
|
NBVersionCheck:
|
||||||
description: Posture check for the version of NetBird
|
description: Posture check for the version of NetBird
|
||||||
type: object
|
type: object
|
||||||
@ -932,6 +934,24 @@ components:
|
|||||||
required:
|
required:
|
||||||
- locations
|
- locations
|
||||||
- action
|
- action
|
||||||
|
PrivateNetworkCheck:
|
||||||
|
description: Posture check for allow or deny private network
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ranges:
|
||||||
|
description: List of private network ranges in CIDR notation
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: ["192.168.1.0/24", "10.0.0.0/8"]
|
||||||
|
action:
|
||||||
|
description: Action to take upon policy match
|
||||||
|
type: string
|
||||||
|
enum: [ "allow", "deny" ]
|
||||||
|
example: "allow"
|
||||||
|
required:
|
||||||
|
- ranges
|
||||||
|
- action
|
||||||
Location:
|
Location:
|
||||||
description: Describe geographical location information
|
description: Describe geographical location information
|
||||||
type: object
|
type: object
|
||||||
|
@ -116,6 +116,12 @@ const (
|
|||||||
PolicyRuleUpdateProtocolUdp PolicyRuleUpdateProtocol = "udp"
|
PolicyRuleUpdateProtocolUdp PolicyRuleUpdateProtocol = "udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for PrivateNetworkCheckAction.
|
||||||
|
const (
|
||||||
|
PrivateNetworkCheckActionAllow PrivateNetworkCheckAction = "allow"
|
||||||
|
PrivateNetworkCheckActionDeny PrivateNetworkCheckAction = "deny"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for UserStatus.
|
// Defines values for UserStatus.
|
||||||
const (
|
const (
|
||||||
UserStatusActive UserStatus = "active"
|
UserStatusActive UserStatus = "active"
|
||||||
@ -186,10 +192,15 @@ type AccountSettings struct {
|
|||||||
type Checks struct {
|
type Checks struct {
|
||||||
// GeoLocationCheck Posture check for geo location
|
// GeoLocationCheck Posture check for geo location
|
||||||
GeoLocationCheck *GeoLocationCheck `json:"geo_location_check,omitempty"`
|
GeoLocationCheck *GeoLocationCheck `json:"geo_location_check,omitempty"`
|
||||||
NbVersionCheck *NBVersionCheck `json:"nb_version_check,omitempty"`
|
|
||||||
|
// NbVersionCheck Posture check for the version of operating system
|
||||||
|
NbVersionCheck *NBVersionCheck `json:"nb_version_check,omitempty"`
|
||||||
|
|
||||||
// OsVersionCheck Posture check for the version of operating system
|
// OsVersionCheck Posture check for the version of operating system
|
||||||
OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"`
|
OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"`
|
||||||
|
|
||||||
|
// PrivateNetworkCheck Posture check for allow or deny private network
|
||||||
|
PrivateNetworkCheck *PrivateNetworkCheck `json:"private_network_check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// City Describe city geographical location information
|
// City Describe city geographical location information
|
||||||
@ -324,13 +335,13 @@ type MinKernelVersionCheck struct {
|
|||||||
MinKernelVersion string `json:"min_kernel_version"`
|
MinKernelVersion string `json:"min_kernel_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MinVersionCheck defines model for MinVersionCheck.
|
// MinVersionCheck Posture check for the version of operating system
|
||||||
type MinVersionCheck struct {
|
type MinVersionCheck struct {
|
||||||
// MinVersion Minimum acceptable version
|
// MinVersion Minimum acceptable version
|
||||||
MinVersion string `json:"min_version"`
|
MinVersion string `json:"min_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NBVersionCheck defines model for NBVersionCheck.
|
// NBVersionCheck Posture check for the version of operating system
|
||||||
type NBVersionCheck = MinVersionCheck
|
type NBVersionCheck = MinVersionCheck
|
||||||
|
|
||||||
// Nameserver defines model for Nameserver.
|
// Nameserver defines model for Nameserver.
|
||||||
@ -407,9 +418,14 @@ type NameserverGroupRequest struct {
|
|||||||
|
|
||||||
// OSVersionCheck Posture check for the version of operating system
|
// OSVersionCheck Posture check for the version of operating system
|
||||||
type OSVersionCheck struct {
|
type OSVersionCheck struct {
|
||||||
|
// Android Posture check for the version of operating system
|
||||||
Android *MinVersionCheck `json:"android,omitempty"`
|
Android *MinVersionCheck `json:"android,omitempty"`
|
||||||
Darwin *MinVersionCheck `json:"darwin,omitempty"`
|
|
||||||
Ios *MinVersionCheck `json:"ios,omitempty"`
|
// Darwin Posture check for the version of operating system
|
||||||
|
Darwin *MinVersionCheck `json:"darwin,omitempty"`
|
||||||
|
|
||||||
|
// Ios Posture check for the version of operating system
|
||||||
|
Ios *MinVersionCheck `json:"ios,omitempty"`
|
||||||
|
|
||||||
// Linux Posture check with the kernel version
|
// Linux Posture check with the kernel version
|
||||||
Linux *MinKernelVersionCheck `json:"linux,omitempty"`
|
Linux *MinKernelVersionCheck `json:"linux,omitempty"`
|
||||||
@ -427,22 +443,22 @@ type Peer struct {
|
|||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||||
|
|
||||||
// CityName Commonly used English name of the city
|
// CityName Commonly used English name of the city
|
||||||
CityName *CityName `json:"city_name,omitempty"`
|
CityName CityName `json:"city_name"`
|
||||||
|
|
||||||
// Connected Peer to Management connection status
|
// Connected Peer to Management connection status
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
|
|
||||||
// ConnectionIp Peer's public connection IP address
|
// ConnectionIp Peer's public connection IP address
|
||||||
ConnectionIp *string `json:"connection_ip,omitempty"`
|
ConnectionIp string `json:"connection_ip"`
|
||||||
|
|
||||||
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
||||||
CountryCode *CountryCode `json:"country_code,omitempty"`
|
CountryCode CountryCode `json:"country_code"`
|
||||||
|
|
||||||
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
DnsLabel string `json:"dns_label"`
|
DnsLabel string `json:"dns_label"`
|
||||||
|
|
||||||
// GeonameId Unique identifier from the GeoNames database for a specific geographical location.
|
// GeonameId Unique identifier from the GeoNames database for a specific geographical location.
|
||||||
GeonameId *int `json:"geoname_id,omitempty"`
|
GeonameId int `json:"geoname_id"`
|
||||||
|
|
||||||
// Groups Groups that the peer belongs to
|
// Groups Groups that the peer belongs to
|
||||||
Groups []GroupMinimum `json:"groups"`
|
Groups []GroupMinimum `json:"groups"`
|
||||||
@ -457,7 +473,7 @@ type Peer struct {
|
|||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
// KernelVersion Peer's operating system kernel version
|
// KernelVersion Peer's operating system kernel version
|
||||||
KernelVersion *string `json:"kernel_version,omitempty"`
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
|
||||||
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
||||||
LastLogin time.Time `json:"last_login"`
|
LastLogin time.Time `json:"last_login"`
|
||||||
@ -481,10 +497,10 @@ type Peer struct {
|
|||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
|
|
||||||
// UiVersion Peer's desktop UI version
|
// UiVersion Peer's desktop UI version
|
||||||
UiVersion *string `json:"ui_version,omitempty"`
|
UiVersion string `json:"ui_version"`
|
||||||
|
|
||||||
// UserId User ID of the user that enrolled this peer
|
// UserId User ID of the user that enrolled this peer
|
||||||
UserId *string `json:"user_id,omitempty"`
|
UserId string `json:"user_id"`
|
||||||
|
|
||||||
// Version Peer's daemon or cli version
|
// Version Peer's daemon or cli version
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@ -496,22 +512,22 @@ type PeerBase struct {
|
|||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||||
|
|
||||||
// CityName Commonly used English name of the city
|
// CityName Commonly used English name of the city
|
||||||
CityName *CityName `json:"city_name,omitempty"`
|
CityName CityName `json:"city_name"`
|
||||||
|
|
||||||
// Connected Peer to Management connection status
|
// Connected Peer to Management connection status
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
|
|
||||||
// ConnectionIp Peer's public connection IP address
|
// ConnectionIp Peer's public connection IP address
|
||||||
ConnectionIp *string `json:"connection_ip,omitempty"`
|
ConnectionIp string `json:"connection_ip"`
|
||||||
|
|
||||||
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
||||||
CountryCode *CountryCode `json:"country_code,omitempty"`
|
CountryCode CountryCode `json:"country_code"`
|
||||||
|
|
||||||
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
DnsLabel string `json:"dns_label"`
|
DnsLabel string `json:"dns_label"`
|
||||||
|
|
||||||
// GeonameId Unique identifier from the GeoNames database for a specific geographical location.
|
// GeonameId Unique identifier from the GeoNames database for a specific geographical location.
|
||||||
GeonameId *int `json:"geoname_id,omitempty"`
|
GeonameId int `json:"geoname_id"`
|
||||||
|
|
||||||
// Groups Groups that the peer belongs to
|
// Groups Groups that the peer belongs to
|
||||||
Groups []GroupMinimum `json:"groups"`
|
Groups []GroupMinimum `json:"groups"`
|
||||||
@ -526,7 +542,7 @@ type PeerBase struct {
|
|||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
// KernelVersion Peer's operating system kernel version
|
// KernelVersion Peer's operating system kernel version
|
||||||
KernelVersion *string `json:"kernel_version,omitempty"`
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
|
||||||
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
||||||
LastLogin time.Time `json:"last_login"`
|
LastLogin time.Time `json:"last_login"`
|
||||||
@ -550,10 +566,10 @@ type PeerBase struct {
|
|||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
|
|
||||||
// UiVersion Peer's desktop UI version
|
// UiVersion Peer's desktop UI version
|
||||||
UiVersion *string `json:"ui_version,omitempty"`
|
UiVersion string `json:"ui_version"`
|
||||||
|
|
||||||
// UserId User ID of the user that enrolled this peer
|
// UserId User ID of the user that enrolled this peer
|
||||||
UserId *string `json:"user_id,omitempty"`
|
UserId string `json:"user_id"`
|
||||||
|
|
||||||
// Version Peer's daemon or cli version
|
// Version Peer's daemon or cli version
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@ -568,22 +584,22 @@ type PeerBatch struct {
|
|||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||||
|
|
||||||
// CityName Commonly used English name of the city
|
// CityName Commonly used English name of the city
|
||||||
CityName *CityName `json:"city_name,omitempty"`
|
CityName CityName `json:"city_name"`
|
||||||
|
|
||||||
// Connected Peer to Management connection status
|
// Connected Peer to Management connection status
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
|
|
||||||
// ConnectionIp Peer's public connection IP address
|
// ConnectionIp Peer's public connection IP address
|
||||||
ConnectionIp *string `json:"connection_ip,omitempty"`
|
ConnectionIp string `json:"connection_ip"`
|
||||||
|
|
||||||
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
||||||
CountryCode *CountryCode `json:"country_code,omitempty"`
|
CountryCode CountryCode `json:"country_code"`
|
||||||
|
|
||||||
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
DnsLabel string `json:"dns_label"`
|
DnsLabel string `json:"dns_label"`
|
||||||
|
|
||||||
// GeonameId Unique identifier from the GeoNames database for a specific geographical location.
|
// GeonameId Unique identifier from the GeoNames database for a specific geographical location.
|
||||||
GeonameId *int `json:"geoname_id,omitempty"`
|
GeonameId int `json:"geoname_id"`
|
||||||
|
|
||||||
// Groups Groups that the peer belongs to
|
// Groups Groups that the peer belongs to
|
||||||
Groups []GroupMinimum `json:"groups"`
|
Groups []GroupMinimum `json:"groups"`
|
||||||
@ -598,7 +614,7 @@ type PeerBatch struct {
|
|||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
// KernelVersion Peer's operating system kernel version
|
// KernelVersion Peer's operating system kernel version
|
||||||
KernelVersion *string `json:"kernel_version,omitempty"`
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
|
||||||
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
||||||
LastLogin time.Time `json:"last_login"`
|
LastLogin time.Time `json:"last_login"`
|
||||||
@ -622,10 +638,10 @@ type PeerBatch struct {
|
|||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
|
|
||||||
// UiVersion Peer's desktop UI version
|
// UiVersion Peer's desktop UI version
|
||||||
UiVersion *string `json:"ui_version,omitempty"`
|
UiVersion string `json:"ui_version"`
|
||||||
|
|
||||||
// UserId User ID of the user that enrolled this peer
|
// UserId User ID of the user that enrolled this peer
|
||||||
UserId *string `json:"user_id,omitempty"`
|
UserId string `json:"user_id"`
|
||||||
|
|
||||||
// Version Peer's daemon or cli version
|
// Version Peer's daemon or cli version
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@ -882,6 +898,18 @@ type PostureCheckUpdate struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrivateNetworkCheck Posture check for allow or deny private network
|
||||||
|
type PrivateNetworkCheck struct {
|
||||||
|
// Action Action to take upon policy match
|
||||||
|
Action PrivateNetworkCheckAction `json:"action"`
|
||||||
|
|
||||||
|
// Ranges List of private network ranges in CIDR notation
|
||||||
|
Ranges []string `json:"ranges"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateNetworkCheckAction Action to take upon policy match
|
||||||
|
type PrivateNetworkCheckAction string
|
||||||
|
|
||||||
// Route defines model for Route.
|
// Route defines model for Route.
|
||||||
type Route struct {
|
type Route struct {
|
||||||
// Description Route description
|
// Description Route description
|
||||||
|
@ -3,7 +3,6 @@ package http
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@ -231,44 +230,36 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin
|
|||||||
return groupsInfo
|
return groupsInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectionIPoString(ip net.IP) *string {
|
|
||||||
publicIP := ""
|
|
||||||
if ip != nil {
|
|
||||||
publicIP = ip.String()
|
|
||||||
}
|
|
||||||
return &publicIP
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
|
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
|
||||||
osVersion := peer.Meta.OSVersion
|
osVersion := peer.Meta.OSVersion
|
||||||
if osVersion == "" {
|
if osVersion == "" {
|
||||||
osVersion = peer.Meta.Core
|
osVersion = peer.Meta.Core
|
||||||
}
|
}
|
||||||
geonameID := int(peer.Location.GeoNameID)
|
|
||||||
return &api.Peer{
|
return &api.Peer{
|
||||||
Id: peer.ID,
|
Id: peer.ID,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Ip: peer.IP.String(),
|
Ip: peer.IP.String(),
|
||||||
ConnectionIp: connectionIPoString(peer.Location.ConnectionIP),
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
Connected: peer.Status.Connected,
|
Connected: peer.Status.Connected,
|
||||||
LastSeen: peer.Status.LastSeen,
|
LastSeen: peer.Status.LastSeen,
|
||||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
KernelVersion: &peer.Meta.KernelVersion,
|
KernelVersion: peer.Meta.KernelVersion,
|
||||||
GeonameId: &geonameID,
|
GeonameId: int(peer.Location.GeoNameID),
|
||||||
Version: peer.Meta.WtVersion,
|
Version: peer.Meta.WtVersion,
|
||||||
Groups: groupsInfo,
|
Groups: groupsInfo,
|
||||||
SshEnabled: peer.SSHEnabled,
|
SshEnabled: peer.SSHEnabled,
|
||||||
Hostname: peer.Meta.Hostname,
|
Hostname: peer.Meta.Hostname,
|
||||||
UserId: &peer.UserID,
|
UserId: peer.UserID,
|
||||||
UiVersion: &peer.Meta.UIVersion,
|
UiVersion: peer.Meta.UIVersion,
|
||||||
DnsLabel: fqdn(peer, dnsDomain),
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
LoginExpired: peer.Status.LoginExpired,
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
AccessiblePeers: accessiblePeer,
|
AccessiblePeers: accessiblePeer,
|
||||||
ApprovalRequired: &peer.Status.RequiresApproval,
|
ApprovalRequired: &peer.Status.RequiresApproval,
|
||||||
CountryCode: &peer.Location.CountryCode,
|
CountryCode: peer.Location.CountryCode,
|
||||||
CityName: &peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,31 +268,31 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
|||||||
if osVersion == "" {
|
if osVersion == "" {
|
||||||
osVersion = peer.Meta.Core
|
osVersion = peer.Meta.Core
|
||||||
}
|
}
|
||||||
geonameID := int(peer.Location.GeoNameID)
|
|
||||||
return &api.PeerBatch{
|
return &api.PeerBatch{
|
||||||
Id: peer.ID,
|
Id: peer.ID,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Ip: peer.IP.String(),
|
Ip: peer.IP.String(),
|
||||||
ConnectionIp: connectionIPoString(peer.Location.ConnectionIP),
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
Connected: peer.Status.Connected,
|
Connected: peer.Status.Connected,
|
||||||
LastSeen: peer.Status.LastSeen,
|
LastSeen: peer.Status.LastSeen,
|
||||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
KernelVersion: &peer.Meta.KernelVersion,
|
KernelVersion: peer.Meta.KernelVersion,
|
||||||
GeonameId: &geonameID,
|
GeonameId: int(peer.Location.GeoNameID),
|
||||||
Version: peer.Meta.WtVersion,
|
Version: peer.Meta.WtVersion,
|
||||||
Groups: groupsInfo,
|
Groups: groupsInfo,
|
||||||
SshEnabled: peer.SSHEnabled,
|
SshEnabled: peer.SSHEnabled,
|
||||||
Hostname: peer.Meta.Hostname,
|
Hostname: peer.Meta.Hostname,
|
||||||
UserId: &peer.UserID,
|
UserId: peer.UserID,
|
||||||
UiVersion: &peer.Meta.UIVersion,
|
UiVersion: peer.Meta.UIVersion,
|
||||||
DnsLabel: fqdn(peer, dnsDomain),
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
LoginExpired: peer.Status.LoginExpired,
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
AccessiblePeersCount: accessiblePeersCount,
|
AccessiblePeersCount: accessiblePeersCount,
|
||||||
ApprovalRequired: &peer.Status.RequiresApproval,
|
ApprovalRequired: &peer.Status.RequiresApproval,
|
||||||
CountryCode: &peer.Location.CountryCode,
|
CountryCode: peer.Location.CountryCode,
|
||||||
CityName: &peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package http
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
@ -212,6 +213,14 @@ func (p *PostureChecksHandler) savePostureChecks(
|
|||||||
postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck)
|
postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if privateNetworkCheck := req.Checks.PrivateNetworkCheck; privateNetworkCheck != nil {
|
||||||
|
postureChecks.Checks.PrivateNetworkCheck, err = toPrivateNetworkCheck(privateNetworkCheck)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid network prefix"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := p.accountManager.SavePostureChecks(account.Id, user.Id, &postureChecks); err != nil {
|
if err := p.accountManager.SavePostureChecks(account.Id, user.Id, &postureChecks); err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@ -226,7 +235,7 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Checks == nil || (req.Checks.NbVersionCheck == nil && req.Checks.OsVersionCheck == nil &&
|
if req.Checks == nil || (req.Checks.NbVersionCheck == nil && req.Checks.OsVersionCheck == nil &&
|
||||||
req.Checks.GeoLocationCheck == nil) {
|
req.Checks.GeoLocationCheck == nil && req.Checks.PrivateNetworkCheck == nil) {
|
||||||
return status.Errorf(status.InvalidArgument, "posture checks shouldn't be empty")
|
return status.Errorf(status.InvalidArgument, "posture checks shouldn't be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +276,20 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error {
|
|||||||
return status.Errorf(status.InvalidArgument, "country code must be 2 letters (ISO 3166-1 alpha-2 format)")
|
return status.Errorf(status.InvalidArgument, "country code must be 2 letters (ISO 3166-1 alpha-2 format)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateNetworkCheck := req.Checks.PrivateNetworkCheck; privateNetworkCheck != nil {
|
||||||
|
if privateNetworkCheck.Action == "" {
|
||||||
|
return status.Errorf(status.InvalidArgument, "action for private network check shouldn't be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedActions := []api.PrivateNetworkCheckAction{api.PrivateNetworkCheckActionAllow, api.PrivateNetworkCheckActionDeny}
|
||||||
|
if !slices.Contains(allowedActions, privateNetworkCheck.Action) {
|
||||||
|
return status.Errorf(status.InvalidArgument, "action for private network check is not valid value")
|
||||||
|
}
|
||||||
|
if len(privateNetworkCheck.Ranges) == 0 {
|
||||||
|
return status.Errorf(status.InvalidArgument, "network ranges for private network check shouldn't be empty")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -296,6 +318,10 @@ func toPostureChecksResponse(postureChecks *posture.Checks) *api.PostureCheck {
|
|||||||
checks.GeoLocationCheck = toGeoLocationCheckResponse(postureChecks.Checks.GeoLocationCheck)
|
checks.GeoLocationCheck = toGeoLocationCheckResponse(postureChecks.Checks.GeoLocationCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if postureChecks.Checks.PrivateNetworkCheck != nil {
|
||||||
|
checks.PrivateNetworkCheck = toPrivateNetworkCheckResponse(postureChecks.Checks.PrivateNetworkCheck)
|
||||||
|
}
|
||||||
|
|
||||||
return &api.PostureCheck{
|
return &api.PostureCheck{
|
||||||
Id: postureChecks.ID,
|
Id: postureChecks.ID,
|
||||||
Name: postureChecks.Name,
|
Name: postureChecks.Name,
|
||||||
@ -342,3 +368,31 @@ func toPostureGeoLocationCheck(apiGeoLocationCheck *api.GeoLocationCheck) *postu
|
|||||||
Locations: locations,
|
Locations: locations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPrivateNetworkCheckResponse(check *posture.PrivateNetworkCheck) *api.PrivateNetworkCheck {
|
||||||
|
netPrefixes := make([]string, 0, len(check.Ranges))
|
||||||
|
for _, netPrefix := range check.Ranges {
|
||||||
|
netPrefixes = append(netPrefixes, netPrefix.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.PrivateNetworkCheck{
|
||||||
|
Ranges: netPrefixes,
|
||||||
|
Action: api.PrivateNetworkCheckAction(check.Action),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPrivateNetworkCheck(check *api.PrivateNetworkCheck) (*posture.PrivateNetworkCheck, error) {
|
||||||
|
prefixes := make([]netip.Prefix, 0)
|
||||||
|
for _, prefix := range check.Ranges {
|
||||||
|
parsedPrefix, err := netip.ParsePrefix(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prefixes = append(prefixes, parsedPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &posture.PrivateNetworkCheck{
|
||||||
|
Ranges: prefixes,
|
||||||
|
Action: string(check.Action),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -122,7 +123,19 @@ func TestGetPostureCheck(t *testing.T) {
|
|||||||
CityName: "Berlin",
|
CityName: "Berlin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: posture.GeoLocationActionAllow,
|
Action: posture.CheckActionAllow,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
privateNetworkCheck := &posture.Checks{
|
||||||
|
ID: "privateNetworkPostureCheck",
|
||||||
|
Name: "privateNetwork",
|
||||||
|
Checks: posture.ChecksDefinition{
|
||||||
|
PrivateNetworkCheck: &posture.PrivateNetworkCheck{
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
},
|
||||||
|
Action: posture.CheckActionAllow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -156,6 +169,13 @@ func TestGetPostureCheck(t *testing.T) {
|
|||||||
checkName: geoPostureCheck.Name,
|
checkName: geoPostureCheck.Name,
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "GetPostureCheck PrivateNetwork OK",
|
||||||
|
expectedBody: true,
|
||||||
|
id: privateNetworkCheck.ID,
|
||||||
|
checkName: privateNetworkCheck.Name,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "GetPostureCheck Not Found",
|
name: "GetPostureCheck Not Found",
|
||||||
id: "not-exists",
|
id: "not-exists",
|
||||||
@ -163,7 +183,7 @@ func TestGetPostureCheck(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p := initPostureChecksTestData(postureCheck, osPostureCheck, geoPostureCheck)
|
p := initPostureChecksTestData(postureCheck, osPostureCheck, geoPostureCheck, privateNetworkCheck)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@ -354,6 +374,39 @@ func TestPostureCheckUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Create Posture Checks Private Network",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/posture-checks",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(`{
|
||||||
|
"name": "default",
|
||||||
|
"description": "default",
|
||||||
|
"checks": {
|
||||||
|
"private_network_check": {
|
||||||
|
"action": "allow",
|
||||||
|
"ranges": [
|
||||||
|
"10.0.0.0/8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedPostureCheck: &api.PostureCheck{
|
||||||
|
Id: "postureCheck",
|
||||||
|
Name: "default",
|
||||||
|
Description: str("default"),
|
||||||
|
Checks: api.Checks{
|
||||||
|
PrivateNetworkCheck: &api.PrivateNetworkCheck{
|
||||||
|
Ranges: []string{
|
||||||
|
"10.0.0.0/8",
|
||||||
|
},
|
||||||
|
Action: api.PrivateNetworkCheckActionAllow,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Create Posture Checks Geo Location with No geolocation DB",
|
name: "Create Posture Checks Geo Location with No geolocation DB",
|
||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
@ -661,6 +714,38 @@ func TestPostureCheckUpdate(t *testing.T) {
|
|||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Update Posture Checks Private Network",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/posture-checks/privateNetworkPostureCheck",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(`{
|
||||||
|
"name": "default",
|
||||||
|
"checks": {
|
||||||
|
"private_network_check": {
|
||||||
|
"action": "deny",
|
||||||
|
"ranges": [
|
||||||
|
"192.168.1.0/24"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedPostureCheck: &api.PostureCheck{
|
||||||
|
Id: "postureCheck",
|
||||||
|
Name: "default",
|
||||||
|
Description: str(""),
|
||||||
|
Checks: api.Checks{
|
||||||
|
PrivateNetworkCheck: &api.PrivateNetworkCheck{
|
||||||
|
Ranges: []string{
|
||||||
|
"192.168.1.0/24",
|
||||||
|
},
|
||||||
|
Action: api.PrivateNetworkCheckActionDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p := initPostureChecksTestData(&posture.Checks{
|
p := initPostureChecksTestData(&posture.Checks{
|
||||||
@ -694,7 +779,19 @@ func TestPostureCheckUpdate(t *testing.T) {
|
|||||||
CityName: "Berlin",
|
CityName: "Berlin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: posture.GeoLocationActionDeny,
|
Action: posture.CheckActionDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&posture.Checks{
|
||||||
|
ID: "privateNetworkPostureCheck",
|
||||||
|
Name: "privateNetwork",
|
||||||
|
Checks: posture.ChecksDefinition{
|
||||||
|
PrivateNetworkCheck: &posture.PrivateNetworkCheck{
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
},
|
||||||
|
Action: posture.CheckActionAllow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -793,4 +890,30 @@ func TestPostureCheck_validatePostureChecksUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{OsVersionCheck: &osVersionCheck}})
|
err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{OsVersionCheck: &osVersionCheck}})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// valid private network check
|
||||||
|
privateNetworkCheck := api.PrivateNetworkCheck{
|
||||||
|
Action: api.PrivateNetworkCheckActionAllow,
|
||||||
|
Ranges: []string{
|
||||||
|
"192.168.1.0/24", "10.0.0.0/8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{PrivateNetworkCheck: &privateNetworkCheck}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// invalid private network check
|
||||||
|
privateNetworkCheck = api.PrivateNetworkCheck{
|
||||||
|
Action: api.PrivateNetworkCheckActionDeny,
|
||||||
|
Ranges: []string{},
|
||||||
|
}
|
||||||
|
err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{PrivateNetworkCheck: &privateNetworkCheck}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// invalid private network check
|
||||||
|
privateNetworkCheck = api.PrivateNetworkCheck{
|
||||||
|
Action: "unknownAction",
|
||||||
|
Ranges: []string{},
|
||||||
|
}
|
||||||
|
err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{PrivateNetworkCheck: &privateNetworkCheck}})
|
||||||
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package posture
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
|
|
||||||
@ -9,9 +10,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NBVersionCheckName = "NBVersionCheck"
|
NBVersionCheckName = "NBVersionCheck"
|
||||||
OSVersionCheckName = "OSVersionCheck"
|
OSVersionCheckName = "OSVersionCheck"
|
||||||
GeoLocationCheckName = "GeoLocationCheck"
|
GeoLocationCheckName = "GeoLocationCheck"
|
||||||
|
PrivateNetworkCheckName = "PrivateNetworkCheck"
|
||||||
|
|
||||||
|
CheckActionAllow string = "allow"
|
||||||
|
CheckActionDeny string = "deny"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check represents an interface for performing a check on a peer.
|
// Check represents an interface for performing a check on a peer.
|
||||||
@ -39,9 +44,10 @@ type Checks struct {
|
|||||||
|
|
||||||
// ChecksDefinition contains definition of actual check
|
// ChecksDefinition contains definition of actual check
|
||||||
type ChecksDefinition struct {
|
type ChecksDefinition struct {
|
||||||
NBVersionCheck *NBVersionCheck `json:",omitempty"`
|
NBVersionCheck *NBVersionCheck `json:",omitempty"`
|
||||||
OSVersionCheck *OSVersionCheck `json:",omitempty"`
|
OSVersionCheck *OSVersionCheck `json:",omitempty"`
|
||||||
GeoLocationCheck *GeoLocationCheck `json:",omitempty"`
|
GeoLocationCheck *GeoLocationCheck `json:",omitempty"`
|
||||||
|
PrivateNetworkCheck *PrivateNetworkCheck `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of a checks definition.
|
// Copy returns a copy of a checks definition.
|
||||||
@ -54,7 +60,7 @@ func (cd ChecksDefinition) Copy() ChecksDefinition {
|
|||||||
}
|
}
|
||||||
if cd.OSVersionCheck != nil {
|
if cd.OSVersionCheck != nil {
|
||||||
cdCopy.OSVersionCheck = &OSVersionCheck{}
|
cdCopy.OSVersionCheck = &OSVersionCheck{}
|
||||||
osCheck := cdCopy.OSVersionCheck
|
osCheck := cd.OSVersionCheck
|
||||||
if osCheck.Android != nil {
|
if osCheck.Android != nil {
|
||||||
cdCopy.OSVersionCheck.Android = &MinVersionCheck{MinVersion: osCheck.Android.MinVersion}
|
cdCopy.OSVersionCheck.Android = &MinVersionCheck{MinVersion: osCheck.Android.MinVersion}
|
||||||
}
|
}
|
||||||
@ -79,6 +85,14 @@ func (cd ChecksDefinition) Copy() ChecksDefinition {
|
|||||||
}
|
}
|
||||||
copy(cdCopy.GeoLocationCheck.Locations, geoCheck.Locations)
|
copy(cdCopy.GeoLocationCheck.Locations, geoCheck.Locations)
|
||||||
}
|
}
|
||||||
|
if cd.PrivateNetworkCheck != nil {
|
||||||
|
privateNetCheck := cd.PrivateNetworkCheck
|
||||||
|
cdCopy.PrivateNetworkCheck = &PrivateNetworkCheck{
|
||||||
|
Action: privateNetCheck.Action,
|
||||||
|
Ranges: make([]netip.Prefix, len(privateNetCheck.Ranges)),
|
||||||
|
}
|
||||||
|
copy(cdCopy.PrivateNetworkCheck.Ranges, privateNetCheck.Ranges)
|
||||||
|
}
|
||||||
return cdCopy
|
return cdCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +130,9 @@ func (pc *Checks) GetChecks() []Check {
|
|||||||
if pc.Checks.GeoLocationCheck != nil {
|
if pc.Checks.GeoLocationCheck != nil {
|
||||||
checks = append(checks, pc.Checks.GeoLocationCheck)
|
checks = append(checks, pc.Checks.GeoLocationCheck)
|
||||||
}
|
}
|
||||||
|
if pc.Checks.PrivateNetworkCheck != nil {
|
||||||
|
checks = append(checks, pc.Checks.PrivateNetworkCheck)
|
||||||
|
}
|
||||||
return checks
|
return checks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package posture
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -216,3 +217,62 @@ func TestChecks_Validate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChecks_Copy(t *testing.T) {
|
||||||
|
check := &Checks{
|
||||||
|
ID: "1",
|
||||||
|
Name: "default",
|
||||||
|
Description: "description",
|
||||||
|
AccountID: "accountID",
|
||||||
|
Checks: ChecksDefinition{
|
||||||
|
NBVersionCheck: &NBVersionCheck{
|
||||||
|
MinVersion: "0.25.0",
|
||||||
|
},
|
||||||
|
OSVersionCheck: &OSVersionCheck{
|
||||||
|
Android: &MinVersionCheck{
|
||||||
|
MinVersion: "13",
|
||||||
|
},
|
||||||
|
Darwin: &MinVersionCheck{
|
||||||
|
MinVersion: "14.2.0",
|
||||||
|
},
|
||||||
|
Ios: &MinVersionCheck{
|
||||||
|
MinVersion: "17.3.0",
|
||||||
|
},
|
||||||
|
Linux: &MinKernelVersionCheck{
|
||||||
|
MinKernelVersion: "6.5.11-linuxkit",
|
||||||
|
},
|
||||||
|
Windows: &MinKernelVersionCheck{
|
||||||
|
MinKernelVersion: "10.0.14393",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GeoLocationCheck: &GeoLocationCheck{
|
||||||
|
Locations: []Location{
|
||||||
|
{
|
||||||
|
CountryCode: "DE",
|
||||||
|
CityName: "Berlin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: CheckActionAllow,
|
||||||
|
},
|
||||||
|
PrivateNetworkCheck: &PrivateNetworkCheck{
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
Action: CheckActionDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
checkCopy := check.Copy()
|
||||||
|
|
||||||
|
assert.Equal(t, check.ID, checkCopy.ID)
|
||||||
|
assert.Equal(t, check.Name, checkCopy.Name)
|
||||||
|
assert.Equal(t, check.Description, checkCopy.Description)
|
||||||
|
assert.Equal(t, check.AccountID, checkCopy.AccountID)
|
||||||
|
assert.Equal(t, check.Checks.Copy(), checkCopy.Checks.Copy())
|
||||||
|
assert.ElementsMatch(t, check.GetChecks(), checkCopy.GetChecks())
|
||||||
|
|
||||||
|
// Updating the original check should not take effect on copy
|
||||||
|
check.Name = "name"
|
||||||
|
assert.NotSame(t, check, checkCopy)
|
||||||
|
}
|
||||||
|
@ -6,11 +6,6 @@ import (
|
|||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
GeoLocationActionAllow string = "allow"
|
|
||||||
GeoLocationActionDeny string = "deny"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
|
||||||
CountryCode string
|
CountryCode string
|
||||||
@ -39,9 +34,9 @@ func (g *GeoLocationCheck) Check(peer nbpeer.Peer) (bool, error) {
|
|||||||
if loc.CountryCode == peer.Location.CountryCode {
|
if loc.CountryCode == peer.Location.CountryCode {
|
||||||
if loc.CityName == "" || loc.CityName == peer.Location.CityName {
|
if loc.CityName == "" || loc.CityName == peer.Location.CityName {
|
||||||
switch g.Action {
|
switch g.Action {
|
||||||
case GeoLocationActionDeny:
|
case CheckActionDeny:
|
||||||
return false, nil
|
return false, nil
|
||||||
case GeoLocationActionAllow:
|
case CheckActionAllow:
|
||||||
return true, nil
|
return true, nil
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
|
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
|
||||||
@ -51,11 +46,11 @@ func (g *GeoLocationCheck) Check(peer nbpeer.Peer) (bool, error) {
|
|||||||
}
|
}
|
||||||
// At this point, no location in the list matches the peer's location
|
// At this point, no location in the list matches the peer's location
|
||||||
// For action deny and no location match, allow the peer
|
// For action deny and no location match, allow the peer
|
||||||
if g.Action == GeoLocationActionDeny {
|
if g.Action == CheckActionDeny {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
// For action allow and no location match, deny the peer
|
// For action allow and no location match, deny the peer
|
||||||
if g.Action == GeoLocationActionAllow {
|
if g.Action == CheckActionAllow {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Berlin",
|
CityName: "Berlin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionAllow,
|
Action: CheckActionAllow,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: true,
|
isValid: true,
|
||||||
@ -54,7 +54,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CountryCode: "DE",
|
CountryCode: "DE",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionAllow,
|
Action: CheckActionAllow,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: true,
|
isValid: true,
|
||||||
@ -78,7 +78,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Los Angeles",
|
CityName: "Los Angeles",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionAllow,
|
Action: CheckActionAllow,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
@ -97,7 +97,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CountryCode: "US",
|
CountryCode: "US",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionAllow,
|
Action: CheckActionAllow,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
@ -121,7 +121,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Los Angeles",
|
CityName: "Los Angeles",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionDeny,
|
Action: CheckActionDeny,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
@ -143,7 +143,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CountryCode: "US",
|
CountryCode: "US",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionDeny,
|
Action: CheckActionDeny,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
@ -167,7 +167,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Los Angeles",
|
CityName: "Los Angeles",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionDeny,
|
Action: CheckActionDeny,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: true,
|
isValid: true,
|
||||||
@ -187,7 +187,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Los Angeles",
|
CityName: "Los Angeles",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionDeny,
|
Action: CheckActionDeny,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
isValid: true,
|
isValid: true,
|
||||||
@ -202,7 +202,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Berlin",
|
CityName: "Berlin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionAllow,
|
Action: CheckActionAllow,
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
@ -217,7 +217,7 @@ func TestGeoLocationCheck_Check(t *testing.T) {
|
|||||||
CityName: "Berlin",
|
CityName: "Berlin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: GeoLocationActionDeny,
|
Action: CheckActionDeny,
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
|
54
management/server/posture/network.go
Normal file
54
management/server/posture/network.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package posture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrivateNetworkCheck struct {
|
||||||
|
Action string
|
||||||
|
Ranges []netip.Prefix `gorm:"serializer:json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Check = (*PrivateNetworkCheck)(nil)
|
||||||
|
|
||||||
|
func (p *PrivateNetworkCheck) Check(peer nbpeer.Peer) (bool, error) {
|
||||||
|
if len(peer.Meta.NetworkAddresses) == 0 {
|
||||||
|
return false, fmt.Errorf("peer's does not contain private network addresses")
|
||||||
|
}
|
||||||
|
|
||||||
|
maskedPrefixes := make([]netip.Prefix, 0, len(p.Ranges))
|
||||||
|
for _, prefix := range p.Ranges {
|
||||||
|
maskedPrefixes = append(maskedPrefixes, prefix.Masked())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, peerNetAddr := range peer.Meta.NetworkAddresses {
|
||||||
|
peerMaskedPrefix := peerNetAddr.NetIP.Masked()
|
||||||
|
if slices.Contains(maskedPrefixes, peerMaskedPrefix) {
|
||||||
|
switch p.Action {
|
||||||
|
case CheckActionDeny:
|
||||||
|
return false, nil
|
||||||
|
case CheckActionAllow:
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("invalid private network check action: %s", p.Action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Action == CheckActionDeny {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if p.Action == CheckActionAllow {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("invalid private network check action: %s", p.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrivateNetworkCheck) Name() string {
|
||||||
|
return PrivateNetworkCheckName
|
||||||
|
}
|
149
management/server/posture/network_test.go
Normal file
149
management/server/posture/network_test.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package posture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivateNetworkCheck_Check(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
check PrivateNetworkCheck
|
||||||
|
peer nbpeer.Peer
|
||||||
|
wantErr bool
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Peer private networks matches the allowed range",
|
||||||
|
check: PrivateNetworkCheck{
|
||||||
|
Action: CheckActionAllow,
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
peer: nbpeer.Peer{
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
NetworkAddresses: []nbpeer.NetworkAddress{
|
||||||
|
{
|
||||||
|
NetIP: netip.MustParsePrefix("192.168.0.123/24"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NetIP: netip.MustParsePrefix("fe80::6089:eaff:fe0c:232f/64"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer private networks doesn't matches the allowed range",
|
||||||
|
check: PrivateNetworkCheck{
|
||||||
|
Action: CheckActionAllow,
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
peer: nbpeer.Peer{
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
NetworkAddresses: []nbpeer.NetworkAddress{
|
||||||
|
{
|
||||||
|
NetIP: netip.MustParsePrefix("198.19.249.3/24"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer with no privates network in the allow range",
|
||||||
|
check: PrivateNetworkCheck{
|
||||||
|
Action: CheckActionAllow,
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/16"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
peer: nbpeer.Peer{},
|
||||||
|
wantErr: true,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer private networks matches the denied range",
|
||||||
|
check: PrivateNetworkCheck{
|
||||||
|
Action: CheckActionDeny,
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
peer: nbpeer.Peer{
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
NetworkAddresses: []nbpeer.NetworkAddress{
|
||||||
|
{
|
||||||
|
NetIP: netip.MustParsePrefix("192.168.0.123/24"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NetIP: netip.MustParsePrefix("fe80::6089:eaff:fe0c:232f/64"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer private networks doesn't matches the denied range",
|
||||||
|
check: PrivateNetworkCheck{
|
||||||
|
Action: CheckActionDeny,
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
peer: nbpeer.Peer{
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
NetworkAddresses: []nbpeer.NetworkAddress{
|
||||||
|
{
|
||||||
|
NetIP: netip.MustParsePrefix("198.19.249.3/24"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer with no private networks in the denied range",
|
||||||
|
check: PrivateNetworkCheck{
|
||||||
|
Action: CheckActionDeny,
|
||||||
|
Ranges: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.0.0/16"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
peer: nbpeer.Peer{},
|
||||||
|
wantErr: true,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
isValid, err := tt.check.Check(tt.peer)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.isValid, isValid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user