Merge branch 'main' into feature/add-ios-support

This commit is contained in:
pascal-fischer
2023-12-06 11:52:27 +01:00
committed by GitHub
136 changed files with 3434 additions and 1130 deletions
+12
View File
@@ -10,6 +10,18 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
codespell:
name: codespell
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: codespell
uses: codespell-project/actions-codespell@v2
with:
ignore_words_list: erro,clienta
skip: go.mod,go.sum
only_warn: 1
golangci: golangci:
strategy: strategy:
fail-fast: false fail-fast: false
+1 -1
View File
@@ -20,7 +20,7 @@ on:
- 'client/ui/**' - 'client/ui/**'
env: env:
SIGN_PIPE_VER: "v0.0.9" SIGN_PIPE_VER: "v0.0.10"
GORELEASER_VER: "v1.14.1" GORELEASER_VER: "v1.14.1"
concurrency: concurrency:
+73 -4
View File
@@ -12,6 +12,50 @@ linters-settings:
# Default: false # Default: false
check-type-assertions: false check-type-assertions: false
gosec:
includes:
- G101 # Look for hard coded credentials
#- G102 # Bind to all interfaces
- G103 # Audit the use of unsafe block
- G104 # Audit errors not checked
- G106 # Audit the use of ssh.InsecureIgnoreHostKey
#- G107 # Url provided to HTTP request as taint input
- G108 # Profiling endpoint automatically exposed on /debug/pprof
- G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32
- G110 # Potential DoS vulnerability via decompression bomb
- G111 # Potential directory traversal
#- G112 # Potential slowloris attack
- G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772)
#- G114 # Use of net/http serve function that has no support for setting timeouts
- G201 # SQL query construction using format string
- G202 # SQL query construction using string concatenation
- G203 # Use of unescaped data in HTML templates
#- G204 # Audit use of command execution
- G301 # Poor file permissions used when creating a directory
- G302 # Poor file permissions used with chmod
- G303 # Creating tempfile using a predictable path
- G304 # File path provided as taint input
- G305 # File traversal when extracting zip/tar archive
- G306 # Poor file permissions used when writing to a new file
- G307 # Poor file permissions used when creating a file with os.Create
#- G401 # Detect the usage of DES, RC4, MD5 or SHA1
#- G402 # Look for bad TLS connection settings
- G403 # Ensure minimum RSA key length of 2048 bits
#- G404 # Insecure random number source (rand)
#- G501 # Import blocklist: crypto/md5
- G502 # Import blocklist: crypto/des
- G503 # Import blocklist: crypto/rc4
- G504 # Import blocklist: net/http/cgi
#- G505 # Import blocklist: crypto/sha1
- G601 # Implicit memory aliasing of items from a range statement
- G602 # Slice access out of bounds
gocritic:
disabled-checks:
- commentFormatting
- captLocal
- deprecatedComment
govet: govet:
# Enable all analyzers. # Enable all analyzers.
# Default: false # Default: false
@@ -19,6 +63,12 @@ linters-settings:
enable: enable:
- nilness - nilness
tenv:
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
# Default: false
all: true
linters: linters:
disable-all: true disable-all: true
enable: enable:
@@ -28,13 +78,23 @@ linters:
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # detects when assignments to existing variables are not used - ineffassign # detects when assignments to existing variables are not used
- staticcheck # is a go vet on steroids, applying a ton of static analysis checks - staticcheck # is a go vet on steroids, applying a ton of static analysis checks
- tenv # Tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17.
- typecheck # like the front-end of a Go compiler, parses and type-checks Go code - typecheck # like the front-end of a Go compiler, parses and type-checks Go code
- unused # checks for unused constants, variables, functions and types - unused # checks for unused constants, variables, functions and types
## disable by default but the have interesting results so lets add them ## disable by default but the have interesting results so lets add them
- bodyclose # checks whether HTTP response body is closed successfully - bodyclose # checks whether HTTP response body is closed successfully
- dupword # dupword checks for duplicate words in the source code
- durationcheck # durationcheck checks for two durations multiplied together
- forbidigo # forbidigo forbids identifiers
- gocritic # provides diagnostics that check for bugs, performance and style issues
- gosec # inspects source code for security problems
- mirror # mirror reports wrong mirror patterns of bytes/strings usage
- misspell # misspess finds commonly misspelled English words in comments
- nilerr # finds the code that returns nil even if it checks that the error is not nil - nilerr # finds the code that returns nil even if it checks that the error is not nil
- nilnil # checks that there is no simultaneous return of nil error and an invalid value - nilnil # checks that there is no simultaneous return of nil error and an invalid value
- predeclared # predeclared finds code that shadows one of Go's predeclared identifiers
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
- thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers.
- wastedassign # wastedassign finds wasted assignment statements - wastedassign # wastedassign finds wasted assignment statements
issues: issues:
# Maximum count of issues with the same text. # Maximum count of issues with the same text.
@@ -43,12 +103,21 @@ issues:
max-same-issues: 5 max-same-issues: 5
exclude-rules: exclude-rules:
- path: sharedsock/filter.go # allow fmt
- path: management/cmd/root\.go
linters: forbidigo
- path: signal/cmd/root\.go
linters: forbidigo
- path: sharedsock/filter\.go
linters: linters:
- unused - unused
- path: client/firewall/iptables/rule.go - path: client/firewall/iptables/rule\.go
linters: linters:
- unused - unused
- path: mock.go - path: test\.go
linters: linters:
- nilnil - mirror
- gosec
- path: mock\.go
linters:
- nilnil
+36
View File
@@ -183,6 +183,42 @@ To start NetBird the management service:
./management management --log-level debug --log-file console --config ./management.json ./management management --log-level debug --log-file console --config ./management.json
``` ```
#### Windows Netbird Installer
Create dist directory
```shell
mkdir -p dist/netbird_windows_amd64
```
UI client
```shell
CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o netbird-ui.exe -ldflags "-s -w -H windowsgui" ./client/ui
mv netbird-ui.exe ./dist/netbird_windows_amd64/
```
Client
```shell
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o netbird.exe ./client/
mv netbird.exe ./dist/netbird_windows_amd64/
```
> Windows clients have a Wireguard driver requirement. You can download the wintun driver from https://www.wintun.net/builds/wintun-0.14.1.zip, after decompressing, you can copy the file `windtun\bin\ARCH\wintun.dll` to `./dist/netbird_windows_amd64/`.
NSIS compiler
- [Windows-nsis]( https://nsis.sourceforge.io/Download)
- [MacOS-makensis](https://formulae.brew.sh/formula/makensis#default)
- [Linux-makensis](https://manpages.ubuntu.com/manpages/trusty/man1/makensis.1.html)
NSIS Plugins. Download and move them to the NSIS plugins folder.
- [EnVar](https://nsis.sourceforge.io/mediawiki/images/7/7f/EnVar_plugin.zip)
- [ShellExecAsUser](https://nsis.sourceforge.io/mediawiki/images/6/68/ShellExecAsUser_amd64-Unicode.7z)
Windows Installer
```shell
export APPVER=0.0.0.1
makensis -V4 client/installer.nsis
```
The installer `netbird-installer.exe` will be created in root directory.
### Test suite ### Test suite
The tests can be started via: The tests can be started via:
+2 -2
View File
@@ -205,8 +205,8 @@ func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, err
go urlOpener.Open(flowInfo.VerificationURIComplete) go urlOpener.Open(flowInfo.VerificationURIComplete)
waitTimeout := time.Duration(flowInfo.ExpiresIn) waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second) waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout)
defer cancel() defer cancel()
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
if err != nil { if err != nil {
+4 -3
View File
@@ -85,6 +85,7 @@ var loginCmd = &cobra.Command{
PreSharedKey: preSharedKey, PreSharedKey: preSharedKey,
ManagementUrl: managementURL, ManagementUrl: managementURL,
IsLinuxDesktopClient: isLinuxRunningDesktop(), IsLinuxDesktopClient: isLinuxRunningDesktop(),
Hostname: hostName,
} }
var loginErr error var loginErr error
@@ -114,7 +115,7 @@ var loginCmd = &cobra.Command{
if loginResp.NeedsSSOLogin { if loginResp.NeedsSSOLogin {
openURL(cmd, loginResp.VerificationURIComplete, loginResp.UserCode) openURL(cmd, loginResp.VerificationURIComplete, loginResp.UserCode)
_, err = client.WaitSSOLogin(ctx, &proto.WaitSSOLoginRequest{UserCode: loginResp.UserCode}) _, err = client.WaitSSOLogin(ctx, &proto.WaitSSOLoginRequest{UserCode: loginResp.UserCode, Hostname: hostName})
if err != nil { if err != nil {
return fmt.Errorf("waiting sso login failed with: %v", err) return fmt.Errorf("waiting sso login failed with: %v", err)
} }
@@ -177,8 +178,8 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int
openURL(cmd, flowInfo.VerificationURIComplete, flowInfo.UserCode) openURL(cmd, flowInfo.VerificationURIComplete, flowInfo.UserCode)
waitTimeout := time.Duration(flowInfo.ExpiresIn) waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second
waitCTX, c := context.WithTimeout(context.TODO(), waitTimeout*time.Second) waitCTX, c := context.WithTimeout(context.TODO(), waitTimeout)
defer c() defer c()
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
+1 -1
View File
@@ -92,7 +92,7 @@ func init() {
rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL)) rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL))
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location") rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level") rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout") rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)") rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.") rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device") rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device")
+2 -2
View File
@@ -234,7 +234,7 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput {
continue continue
} }
if isPeerConnected { if isPeerConnected {
peersConnected = peersConnected + 1 peersConnected++
localICE = pbPeerState.GetLocalIceCandidateType() localICE = pbPeerState.GetLocalIceCandidateType()
remoteICE = pbPeerState.GetRemoteIceCandidateType() remoteICE = pbPeerState.GetRemoteIceCandidateType()
@@ -407,7 +407,7 @@ func parsePeers(peers peersStateOutput) string {
peerState.LastStatusUpdate.Format("2006-01-02 15:04:05"), peerState.LastStatusUpdate.Format("2006-01-02 15:04:05"),
) )
peersString = peersString + peerString peersString += peerString
} }
return peersString return peersString
} }
+5 -1
View File
@@ -22,6 +22,7 @@ import (
) )
func startTestingServices(t *testing.T) string { func startTestingServices(t *testing.T) string {
t.Helper()
config := &mgmt.Config{} config := &mgmt.Config{}
_, err := util.ReadJson("../testdata/management.json", config) _, err := util.ReadJson("../testdata/management.json", config)
if err != nil { if err != nil {
@@ -44,6 +45,7 @@ func startTestingServices(t *testing.T) string {
} }
func startSignal(t *testing.T) (*grpc.Server, net.Listener) { func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
t.Helper()
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -60,6 +62,7 @@ func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
} }
func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) { func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) {
t.Helper()
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -70,7 +73,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
t.Fatal(err) t.Fatal(err)
} }
peersUpdateManager := mgmt.NewPeersUpdateManager() peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
if err != nil { if err != nil {
return nil, nil return nil, nil
@@ -98,6 +101,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
func startClientDaemon( func startClientDaemon(
t *testing.T, ctx context.Context, managementURL, configPath string, t *testing.T, ctx context.Context, managementURL, configPath string,
) (*grpc.Server, net.Listener) { ) (*grpc.Server, net.Listener) {
t.Helper()
lis, err := net.Listen("tcp", "127.0.0.1:0") lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+2 -1
View File
@@ -149,6 +149,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0, CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
CustomDNSAddress: customDNSAddressConverted, CustomDNSAddress: customDNSAddressConverted,
IsLinuxDesktopClient: isLinuxRunningDesktop(), IsLinuxDesktopClient: isLinuxRunningDesktop(),
Hostname: hostName,
} }
var loginErr error var loginErr error
@@ -179,7 +180,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
openURL(cmd, loginResp.VerificationURIComplete, loginResp.UserCode) openURL(cmd, loginResp.VerificationURIComplete, loginResp.UserCode)
_, err = client.WaitSSOLogin(ctx, &proto.WaitSSOLoginRequest{UserCode: loginResp.UserCode}) _, err = client.WaitSSOLogin(ctx, &proto.WaitSSOLoginRequest{UserCode: loginResp.UserCode, Hostname: hostName})
if err != nil { if err != nil {
return fmt.Errorf("waiting sso login failed with: %v", err) return fmt.Errorf("waiting sso login failed with: %v", err)
} }
+7 -5
View File
@@ -463,14 +463,16 @@ func (m *Manager) actionToStr(action fw.Action) string {
} }
func (m *Manager) transformIPsetName(ipsetName string, sPort, dPort string) string { func (m *Manager) transformIPsetName(ipsetName string, sPort, dPort string) string {
if ipsetName == "" { switch {
case ipsetName == "":
return "" return ""
} else if sPort != "" && dPort != "" { case sPort != "" && dPort != "":
return ipsetName + "-sport-dport" return ipsetName + "-sport-dport"
} else if sPort != "" { case sPort != "":
return ipsetName + "-sport" return ipsetName + "-sport"
} else if dPort != "" { case dPort != "":
return ipsetName + "-dport" return ipsetName + "-dport"
default:
return ipsetName
} }
return ipsetName
} }
@@ -206,6 +206,7 @@ func TestIptablesManagerIPSet(t *testing.T) {
} }
func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName string, mustExists bool, rulespec ...string) { func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName string, mustExists bool, rulespec ...string) {
t.Helper()
exists, err := ipv4Client.Exists("filter", chainName, rulespec...) exists, err := ipv4Client.Exists("filter", chainName, rulespec...)
require.NoError(t, err, "failed to check rule") require.NoError(t, err, "failed to check rule")
require.Falsef(t, !exists && mustExists, "rule '%v' does not exist", rulespec) require.Falsef(t, !exists && mustExists, "rule '%v' does not exist", rulespec)
+1 -1
View File
@@ -791,7 +791,7 @@ func (m *Manager) flushWithBackoff() (err error) {
return err return err
} }
time.Sleep(backoffTime) time.Sleep(backoffTime)
backoffTime = backoffTime * 2 backoffTime *= 2
continue continue
} }
break break
+4 -2
View File
@@ -355,14 +355,16 @@ func (m *Manager) RemovePacketHook(hookID string) error {
for _, arr := range m.incomingRules { for _, arr := range m.incomingRules {
for _, r := range arr { for _, r := range arr {
if r.id == hookID { if r.id == hookID {
return m.DeleteRule(&r) rule := r
return m.DeleteRule(&rule)
} }
} }
} }
for _, arr := range m.outgoingRules { for _, arr := range m.outgoingRules {
for _, r := range arr { for _, r := range arr {
if r.id == hookID { if r.id == hookID {
return m.DeleteRule(&r) rule := r
return m.DeleteRule(&rule)
} }
} }
} }
+3 -6
View File
@@ -166,10 +166,9 @@ WriteRegStr ${REG_ROOT} "${UI_REG_APP_PATH}" "" "$INSTDIR\${UI_APP_EXE}"
EnVar::SetHKLM EnVar::SetHKLM
EnVar::AddValueEx "path" "$INSTDIR" EnVar::AddValueEx "path" "$INSTDIR"
SetShellVarContext current SetShellVarContext all
CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}" CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}" CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
SetShellVarContext all
SectionEnd SectionEnd
Section -Post Section -Post
@@ -196,10 +195,9 @@ Delete "$INSTDIR\${MAIN_APP_EXE}"
Delete "$INSTDIR\wintun.dll" Delete "$INSTDIR\wintun.dll"
RmDir /r "$INSTDIR" RmDir /r "$INSTDIR"
SetShellVarContext current SetShellVarContext all
Delete "$DESKTOP\${APP_NAME}.lnk" Delete "$DESKTOP\${APP_NAME}.lnk"
Delete "$SMPROGRAMS\${APP_NAME}.lnk" Delete "$SMPROGRAMS\${APP_NAME}.lnk"
SetShellVarContext all
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}" DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}" DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
@@ -209,8 +207,7 @@ SectionEnd
Function LaunchLink Function LaunchLink
SetShellVarContext current SetShellVarContext all
SetOutPath $INSTDIR SetOutPath $INSTDIR
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk" ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
SetShellVarContext all
FunctionEnd FunctionEnd
+11 -9
View File
@@ -189,31 +189,33 @@ func TestDefaultManagerSquashRules(t *testing.T) {
} }
r := rules[0] r := rules[0]
if r.PeerIP != "0.0.0.0" { switch {
case r.PeerIP != "0.0.0.0":
t.Errorf("IP should be 0.0.0.0, got: %v", r.PeerIP) t.Errorf("IP should be 0.0.0.0, got: %v", r.PeerIP)
return return
} else if r.Direction != mgmProto.FirewallRule_IN { case r.Direction != mgmProto.FirewallRule_IN:
t.Errorf("direction should be IN, got: %v", r.Direction) t.Errorf("direction should be IN, got: %v", r.Direction)
return return
} else if r.Protocol != mgmProto.FirewallRule_ALL { case r.Protocol != mgmProto.FirewallRule_ALL:
t.Errorf("protocol should be ALL, got: %v", r.Protocol) t.Errorf("protocol should be ALL, got: %v", r.Protocol)
return return
} else if r.Action != mgmProto.FirewallRule_ACCEPT { case r.Action != mgmProto.FirewallRule_ACCEPT:
t.Errorf("action should be ACCEPT, got: %v", r.Action) t.Errorf("action should be ACCEPT, got: %v", r.Action)
return return
} }
r = rules[1] r = rules[1]
if r.PeerIP != "0.0.0.0" { switch {
case r.PeerIP != "0.0.0.0":
t.Errorf("IP should be 0.0.0.0, got: %v", r.PeerIP) t.Errorf("IP should be 0.0.0.0, got: %v", r.PeerIP)
return return
} else if r.Direction != mgmProto.FirewallRule_OUT { case r.Direction != mgmProto.FirewallRule_OUT:
t.Errorf("direction should be OUT, got: %v", r.Direction) t.Errorf("direction should be OUT, got: %v", r.Direction)
return return
} else if r.Protocol != mgmProto.FirewallRule_ALL { case r.Protocol != mgmProto.FirewallRule_ALL:
t.Errorf("protocol should be ALL, got: %v", r.Protocol) t.Errorf("protocol should be ALL, got: %v", r.Protocol)
return return
} else if r.Action != mgmProto.FirewallRule_ACCEPT { case r.Action != mgmProto.FirewallRule_ACCEPT:
t.Errorf("action should be ACCEPT, got: %v", r.Action) t.Errorf("action should be ACCEPT, got: %v", r.Action)
return return
} }
@@ -281,7 +283,7 @@ func TestDefaultManagerSquashRulesNoAffect(t *testing.T) {
manager := &DefaultManager{} manager := &DefaultManager{}
if rules, _ := manager.squashAcceptRules(networkMap); len(rules) != len(networkMap.FirewallRules) { if rules, _ := manager.squashAcceptRules(networkMap); len(rules) != len(networkMap.FirewallRules) {
t.Errorf("we should got same amount of rules as intput, got %v", len(rules)) t.Errorf("we should get the same amount of rules as output, got %v", len(rules))
} }
} }
+3 -2
View File
@@ -4,12 +4,13 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/netbirdio/netbird/client/internal"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
"github.com/netbirdio/netbird/client/internal"
) )
// HostedGrantType grant type for device flow on Hosted // HostedGrantType grant type for device flow on Hosted
@@ -174,7 +175,7 @@ func (d *DeviceAuthorizationFlow) WaitToken(ctx context.Context, info AuthFlowIn
if tokenResponse.Error == "authorization_pending" { if tokenResponse.Error == "authorization_pending" {
continue continue
} else if tokenResponse.Error == "slow_down" { } else if tokenResponse.Error == "slow_down" {
interval = interval + (3 * time.Second) interval += (3 * time.Second)
ticker.Reset(interval) ticker.Reset(interval)
continue continue
} }
+4 -4
View File
@@ -92,15 +92,15 @@ func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAu
func authenticateWithDeviceCodeFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) { func authenticateWithDeviceCodeFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
if err != nil { if err != nil {
s, ok := gstatus.FromError(err) switch s, ok := gstatus.FromError(err); {
if ok && s.Code() == codes.NotFound { case ok && s.Code() == codes.NotFound:
return nil, fmt.Errorf("no SSO provider returned from management. " + return nil, fmt.Errorf("no SSO provider returned from management. " +
"Please proceed with setting up this device using setup keys " + "Please proceed with setting up this device using setup keys " +
"https://docs.netbird.io/how-to/register-machines-using-setup-keys") "https://docs.netbird.io/how-to/register-machines-using-setup-keys")
} else if ok && s.Code() == codes.Unimplemented { case ok && s.Code() == codes.Unimplemented:
return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+ return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+
"please update your server or use Setup Keys to login", config.ManagementURL) "please update your server or use Setup Keys to login", config.ManagementURL)
} else { default:
return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err) return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err)
} }
} }
+2 -2
View File
@@ -273,9 +273,9 @@ func parseURL(serviceName, serviceURL string) (*url.URL, error) {
if parsedMgmtURL.Port() == "" { if parsedMgmtURL.Port() == "" {
switch parsedMgmtURL.Scheme { switch parsedMgmtURL.Scheme {
case "https": case "https":
parsedMgmtURL.Host = parsedMgmtURL.Host + ":443" parsedMgmtURL.Host += ":443"
case "http": case "http":
parsedMgmtURL.Host = parsedMgmtURL.Host + ":80" parsedMgmtURL.Host += ":80"
default: default:
log.Infof("unable to determine a default port for schema %s in URL %s", parsedMgmtURL.Scheme, serviceURL) log.Infof("unable to determine a default port for schema %s in URL %s", parsedMgmtURL.Scheme, serviceURL)
} }
+3 -8
View File
@@ -7,12 +7,12 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net/netip" "net/netip"
"regexp"
"time" "time"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
"github.com/miekg/dns" "github.com/miekg/dns"
nbversion "github.com/netbirdio/netbird/version"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -122,7 +122,7 @@ func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig) er
searchDomains = append(searchDomains, dns.Fqdn(dConf.domain)) searchDomains = append(searchDomains, dns.Fqdn(dConf.domain))
} }
newDomainList := append(searchDomains, matchDomains...) newDomainList := append(searchDomains, matchDomains...) //nolint:gocritic
priority := networkManagerDbusSearchDomainOnlyPriority priority := networkManagerDbusSearchDomainOnlyPriority
switch { switch {
@@ -289,12 +289,7 @@ func isNetworkManagerSupportedVersion() bool {
} }
func parseVersion(inputVersion string) (*version.Version, error) { func parseVersion(inputVersion string) (*version.Version, error) {
reg, err := regexp.Compile(version.SemverRegexpRaw) if inputVersion == "" || !nbversion.SemverRegexp.MatchString(inputVersion) {
if err != nil {
return nil, err
}
if inputVersion == "" || !reg.MatchString(inputVersion) {
return nil, fmt.Errorf("couldn't parse the provided version: Not SemVer") return nil, fmt.Errorf("couldn't parse the provided version: Not SemVer")
} }
+13 -2
View File
@@ -267,7 +267,7 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
if err != nil { if err != nil {
return fmt.Errorf("not applying dns update, error: %v", err) return fmt.Errorf("not applying dns update, error: %v", err)
} }
muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) //nolint:gocritic
s.updateMux(muxUpdates) s.updateMux(muxUpdates)
s.updateLocalResolver(localRecords) s.updateLocalResolver(localRecords)
@@ -503,7 +503,18 @@ func (s *DefaultServer) addHostRootZone() {
handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr) handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr)
handler.upstreamServers = make([]string, len(s.hostsDnsList)) handler.upstreamServers = make([]string, len(s.hostsDnsList))
for n, ua := range s.hostsDnsList { for n, ua := range s.hostsDnsList {
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ua) a, err := netip.ParseAddr(ua)
if err != nil {
log.Errorf("invalid upstream IP address: %s, error: %s", ua, err)
continue
}
ipString := ua
if !a.Is4() {
ipString = fmt.Sprintf("[%s]", ua)
}
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString)
} }
handler.deactivate = func() {} handler.deactivate = func() {}
handler.reactivate = func() {} handler.reactivate = func() {}
+7 -6
View File
@@ -322,9 +322,9 @@ func TestUpdateDNSServer(t *testing.T) {
func TestDNSFakeResolverHandleUpdates(t *testing.T) { func TestDNSFakeResolverHandleUpdates(t *testing.T) {
ov := os.Getenv("NB_WG_KERNEL_DISABLED") ov := os.Getenv("NB_WG_KERNEL_DISABLED")
defer os.Setenv("NB_WG_KERNEL_DISABLED", ov) defer t.Setenv("NB_WG_KERNEL_DISABLED", ov)
_ = os.Setenv("NB_WG_KERNEL_DISABLED", "true") t.Setenv("NB_WG_KERNEL_DISABLED", "true")
newNet, err := stdnet.NewNet(nil) newNet, err := stdnet.NewNet(nil)
if err != nil { if err != nil {
t.Errorf("create stdnet: %v", err) t.Errorf("create stdnet: %v", err)
@@ -339,7 +339,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
err = wgIface.Create() err = wgIface.Create()
if err != nil { if err != nil {
t.Errorf("crate and init wireguard interface: %v", err) t.Errorf("create and init wireguard interface: %v", err)
return return
} }
defer func() { defer func() {
@@ -771,10 +771,11 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
} }
func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
t.Helper()
ov := os.Getenv("NB_WG_KERNEL_DISABLED") ov := os.Getenv("NB_WG_KERNEL_DISABLED")
defer os.Setenv("NB_WG_KERNEL_DISABLED", ov) defer t.Setenv("NB_WG_KERNEL_DISABLED", ov)
_ = os.Setenv("NB_WG_KERNEL_DISABLED", "true") t.Setenv("NB_WG_KERNEL_DISABLED", "true")
newNet, err := stdnet.NewNet(nil) newNet, err := stdnet.NewNet(nil)
if err != nil { if err != nil {
t.Fatalf("create stdnet: %v", err) t.Fatalf("create stdnet: %v", err)
@@ -789,7 +790,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
err = wgIface.Create() err = wgIface.Create()
if err != nil { if err != nil {
t.Fatalf("crate and init wireguard interface: %v", err) t.Fatalf("create and init wireguard interface: %v", err)
return nil, err return nil, err
} }
+1 -1
View File
@@ -50,7 +50,7 @@ func GetEbpfManagerInstance() manager.Manager {
} }
func (tf *GeneralManager) setFeatureFlag(feature uint16) { func (tf *GeneralManager) setFeatureFlag(feature uint16) {
tf.featureFlags = tf.featureFlags | feature tf.featureFlags |= feature
} }
func (tf *GeneralManager) loadXdp() error { func (tf *GeneralManager) loadXdp() error {
@@ -8,12 +8,12 @@ func TestManager_setFeatureFlag(t *testing.T) {
mgr := GeneralManager{} mgr := GeneralManager{}
mgr.setFeatureFlag(featureFlagWGProxy) mgr.setFeatureFlag(featureFlagWGProxy)
if mgr.featureFlags != 1 { if mgr.featureFlags != 1 {
t.Errorf("invalid faeture state") t.Errorf("invalid feature state")
} }
mgr.setFeatureFlag(featureFlagDnsForwarder) mgr.setFeatureFlag(featureFlagDnsForwarder)
if mgr.featureFlags != 3 { if mgr.featureFlags != 3 {
t.Errorf("invalid faeture state") t.Errorf("invalid feature state")
} }
} }
@@ -27,7 +27,7 @@ func TestManager_unsetFeatureFlag(t *testing.T) {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
if mgr.featureFlags != 2 { if mgr.featureFlags != 2 {
t.Errorf("invalid faeture state, expected: %d, got: %d", 2, mgr.featureFlags) t.Errorf("invalid feature state, expected: %d, got: %d", 2, mgr.featureFlags)
} }
err = mgr.unsetFeatureFlag(featureFlagDnsForwarder) err = mgr.unsetFeatureFlag(featureFlagDnsForwarder)
@@ -35,6 +35,6 @@ func TestManager_unsetFeatureFlag(t *testing.T) {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
if mgr.featureFlags != 0 { if mgr.featureFlags != 0 {
t.Errorf("invalid faeture state, expected: %d, got: %d", 0, mgr.featureFlags) t.Errorf("invalid feature state, expected: %d, got: %d", 0, mgr.featureFlags)
} }
} }
+8 -13
View File
@@ -204,16 +204,13 @@ func (e *Engine) Start() error {
e.dnsServer = dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener) e.dnsServer = dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener)
go e.mobileDep.DnsReadyListener.OnReady() go e.mobileDep.DnsReadyListener.OnReady()
} }
} else { } else if e.dnsServer == nil {
// todo fix custom address
if e.dnsServer == nil {
e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.mobileDep.InterfaceName, wgAddr) e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.mobileDep.InterfaceName, wgAddr)
if err != nil { if err != nil {
e.close() e.close()
return err return err
} }
} }
}
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes) e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes)
e.mobileDep.NetworkChangeListener.SetInterfaceIP(wgAddr) e.mobileDep.NetworkChangeListener.SetInterfaceIP(wgAddr)
@@ -498,15 +495,13 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error {
} else { } else {
log.Debugf("SSH server is already running") log.Debugf("SSH server is already running")
} }
} else { } else if !isNil(e.sshServer) {
// Disable SSH server request, so stop it if it was running // Disable SSH server request, so stop it if it was running
if !isNil(e.sshServer) { err := e.sshServer.Stop()
err := e.sshServer.Stop() if err != nil {
if err != nil { log.Warnf("failed to stop SSH server %v", err)
log.Warnf("failed to stop SSH server %v", err)
}
e.sshServer = nil
} }
e.sshServer = nil
} }
return nil return nil
} }
@@ -648,7 +643,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
if config.GetSshConfig() != nil && config.GetSshConfig().GetSshPubKey() != nil { if config.GetSshConfig() != nil && config.GetSshConfig().GetSshPubKey() != nil {
err := e.sshServer.AddAuthorizedKey(config.WgPubKey, string(config.GetSshConfig().GetSshPubKey())) err := e.sshServer.AddAuthorizedKey(config.WgPubKey, string(config.GetSshConfig().GetSshPubKey()))
if err != nil { if err != nil {
log.Warnf("failed adding authroized key to SSH DefaultServer %v", err) log.Warnf("failed adding authorized key to SSH DefaultServer %v", err)
} }
} }
} }
+2 -2
View File
@@ -869,7 +869,7 @@ loop:
case <-ticker.C: case <-ticker.C:
totalConnected := 0 totalConnected := 0
for _, engine := range engines { for _, engine := range engines {
totalConnected = totalConnected + getConnectedPeers(engine) totalConnected += getConnectedPeers(engine)
} }
if totalConnected == expectedConnected { if totalConnected == expectedConnected {
log.Infof("total connected=%d", totalConnected) log.Infof("total connected=%d", totalConnected)
@@ -1044,7 +1044,7 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
return nil, "", err return nil, "", err
} }
peersUpdateManager := server.NewPeersUpdateManager() peersUpdateManager := server.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
if err != nil { if err != nil {
return nil, "", err return nil, "", err
+2 -2
View File
@@ -247,7 +247,7 @@ func (conn *Conn) Open() error {
} }
err := conn.statusRecorder.UpdatePeerState(peerState) err := conn.statusRecorder.UpdatePeerState(peerState)
if err != nil { if err != nil {
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err) log.Warnf("error while updating the state of peer %s,err: %v", conn.config.Key, err)
} }
defer func() { defer func() {
@@ -306,7 +306,7 @@ func (conn *Conn) Open() error {
} }
err = conn.statusRecorder.UpdatePeerState(peerState) err = conn.statusRecorder.UpdatePeerState(peerState)
if err != nil { if err != nil {
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err) log.Warnf("error while updating the state of peer %s,err: %v", conn.config.Key, err)
} }
err = conn.agent.GatherCandidates() err = conn.agent.GatherCandidates()
+4 -4
View File
@@ -174,13 +174,13 @@ func (d *Status) UpdatePeerState(receivedState State) error {
return nil return nil
} }
func shouldSkipNotify(new, curr State) bool { func shouldSkipNotify(received, curr State) bool {
switch { switch {
case new.ConnStatus == StatusConnecting: case received.ConnStatus == StatusConnecting:
return true return true
case new.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusConnecting: case received.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusConnecting:
return true return true
case new.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusDisconnected: case received.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusDisconnected:
return curr.IP != "" return curr.IP != ""
default: default:
return false return false
+2
View File
@@ -12,6 +12,8 @@ import (
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
const minRangeBits = 7
type routerPeerStatus struct { type routerPeerStatus struct {
connected bool connected bool
relayed bool relayed bool
@@ -173,7 +173,7 @@ func (i *iptablesManager) addJumpRules() error {
return err return err
} }
if i.ipv4Client != nil { if i.ipv4Client != nil {
rule := append(iptablesDefaultForwardingRule, ipv4Forwarding) rule := append(iptablesDefaultForwardingRule, ipv4Forwarding) //nolint:gocritic
err = i.ipv4Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...) err = i.ipv4Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...)
if err != nil { if err != nil {
@@ -181,7 +181,7 @@ func (i *iptablesManager) addJumpRules() error {
} }
i.rules[ipv4][ipv4Forwarding] = rule i.rules[ipv4][ipv4Forwarding] = rule
rule = append(iptablesDefaultNatRule, ipv4Nat) rule = append(iptablesDefaultNatRule, ipv4Nat) //nolint:gocritic
err = i.ipv4Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...) err = i.ipv4Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...)
if err != nil { if err != nil {
return err return err
@@ -190,14 +190,14 @@ func (i *iptablesManager) addJumpRules() error {
} }
if i.ipv6Client != nil { if i.ipv6Client != nil {
rule := append(iptablesDefaultForwardingRule, ipv6Forwarding) rule := append(iptablesDefaultForwardingRule, ipv6Forwarding) //nolint:gocritic
err = i.ipv6Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...) err = i.ipv6Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...)
if err != nil { if err != nil {
return err return err
} }
i.rules[ipv6][ipv6Forwarding] = rule i.rules[ipv6][ipv6Forwarding] = rule
rule = append(iptablesDefaultNatRule, ipv6Nat) rule = append(iptablesDefaultNatRule, ipv6Nat) //nolint:gocritic
err = i.ipv6Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...) err = i.ipv6Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...)
if err != nil { if err != nil {
return err return err
+1 -1
View File
@@ -155,7 +155,7 @@ func (m *DefaultManager) classifiesRoutes(newRoutes []*route.Route) (map[string]
if !ownNetworkIDs[networkID] { if !ownNetworkIDs[networkID] {
// if prefix is too small, lets assume is a possible default route which is not yet supported // if prefix is too small, lets assume is a possible default route which is not yet supported
// we skip this route management // we skip this route management
if newRoute.Network.Bits() < 7 { if newRoute.Network.Bits() < minRangeBits {
log.Errorf("this agent version: %s, doesn't support default routes, received %s, skipping this route", log.Errorf("this agent version: %s, doesn't support default routes, received %s, skipping this route",
version.NetbirdVersion(), newRoute.Network) version.NetbirdVersion(), newRoute.Network)
continue continue
@@ -300,7 +300,7 @@ func (n *nftablesManager) acceptForwardRule(sourceNetwork string) error {
dst := generateCIDRMatcherExpressions("destination", "0.0.0.0/0") dst := generateCIDRMatcherExpressions("destination", "0.0.0.0/0")
var exprs []expr.Any var exprs []expr.Any
exprs = append(src, append(dst, &expr.Verdict{ exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic
Kind: expr.VerdictAccept, Kind: expr.VerdictAccept,
})...) })...)
@@ -322,7 +322,7 @@ func (n *nftablesManager) acceptForwardRule(sourceNetwork string) error {
src = generateCIDRMatcherExpressions("source", "0.0.0.0/0") src = generateCIDRMatcherExpressions("source", "0.0.0.0/0")
dst = generateCIDRMatcherExpressions("destination", sourceNetwork) dst = generateCIDRMatcherExpressions("destination", sourceNetwork)
exprs = append(src, append(dst, &expr.Verdict{ exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic
Kind: expr.VerdictAccept, Kind: expr.VerdictAccept,
})...) })...)
@@ -421,9 +421,9 @@ func (n *nftablesManager) insertRoutingRule(format, chain string, pair routerPai
var expression []expr.Any var expression []expr.Any
if isNat { if isNat {
expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) //nolint:gocritic
} else { } else {
expression = append(sourceExp, append(destExp, exprCounterAccept...)...) expression = append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
} }
ruleKey := genKey(format, pair.ID) ruleKey := genKey(format, pair.ID)
@@ -44,7 +44,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
sourceExp := generateCIDRMatcherExpressions("source", pair.source) sourceExp := generateCIDRMatcherExpressions("source", pair.source)
destExp := generateCIDRMatcherExpressions("destination", pair.destination) destExp := generateCIDRMatcherExpressions("destination", pair.destination)
forward4Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) forward4Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
forward4RuleKey := genKey(forwardingFormat, pair.ID) forward4RuleKey := genKey(forwardingFormat, pair.ID)
inserted4Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ inserted4Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
Table: manager.tableIPv4, Table: manager.tableIPv4,
@@ -53,7 +53,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
UserData: []byte(forward4RuleKey), UserData: []byte(forward4RuleKey),
}) })
nat4Exp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) nat4Exp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) //nolint:gocritic
nat4RuleKey := genKey(natFormat, pair.ID) nat4RuleKey := genKey(natFormat, pair.ID)
inserted4Nat := nftablesTestingClient.InsertRule(&nftables.Rule{ inserted4Nat := nftablesTestingClient.InsertRule(&nftables.Rule{
@@ -76,7 +76,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
sourceExp = generateCIDRMatcherExpressions("source", pair.source) sourceExp = generateCIDRMatcherExpressions("source", pair.source)
destExp = generateCIDRMatcherExpressions("destination", pair.destination) destExp = generateCIDRMatcherExpressions("destination", pair.destination)
forward6Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) forward6Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
forward6RuleKey := genKey(forwardingFormat, pair.ID) forward6RuleKey := genKey(forwardingFormat, pair.ID)
inserted6Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ inserted6Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
Table: manager.tableIPv6, Table: manager.tableIPv6,
@@ -85,7 +85,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
UserData: []byte(forward6RuleKey), UserData: []byte(forward6RuleKey),
}) })
nat6Exp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) nat6Exp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) //nolint:gocritic
nat6RuleKey := genKey(natFormat, pair.ID) nat6RuleKey := genKey(natFormat, pair.ID)
inserted6Nat := nftablesTestingClient.InsertRule(&nftables.Rule{ inserted6Nat := nftablesTestingClient.InsertRule(&nftables.Rule{
@@ -149,7 +149,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source) sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source)
destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination) destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination)
testingExpression := append(sourceExp, destExp...) testingExpression := append(sourceExp, destExp...) //nolint:gocritic
fwdRuleKey := genKey(forwardingFormat, testCase.inputPair.ID) fwdRuleKey := genKey(forwardingFormat, testCase.inputPair.ID)
found := 0 found := 0
@@ -188,7 +188,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source) sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination) destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
testingExpression = append(sourceExp, destExp...) testingExpression = append(sourceExp, destExp...) //nolint:gocritic
inFwdRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID) inFwdRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
found = 0 found = 0
@@ -252,7 +252,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source) sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source)
destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination) destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination)
forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...) forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
forwardRuleKey := genKey(forwardingFormat, testCase.inputPair.ID) forwardRuleKey := genKey(forwardingFormat, testCase.inputPair.ID)
insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
Table: table, Table: table,
@@ -261,7 +261,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
UserData: []byte(forwardRuleKey), UserData: []byte(forwardRuleKey),
}) })
natExp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) natExp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) //nolint:gocritic
natRuleKey := genKey(natFormat, testCase.inputPair.ID) natRuleKey := genKey(natFormat, testCase.inputPair.ID)
insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{ insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{
@@ -274,7 +274,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source) sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination) destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...) forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID) inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
Table: table, Table: table,
@@ -283,7 +283,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
UserData: []byte(inForwardRuleKey), UserData: []byte(inForwardRuleKey),
}) })
natExp = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) natExp = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) //nolint:gocritic
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID) inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{ insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{
+33 -22
View File
@@ -27,24 +27,24 @@ const (
RTF_MULTICAST = 0x800000 RTF_MULTICAST = 0x800000
) )
func existsInRouteTable(prefix netip.Prefix) (bool, error) { func getRoutesFromTable() ([]netip.Prefix, error) {
tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0) tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0)
if err != nil { if err != nil {
return false, err return nil, err
} }
msgs, err := route.ParseRIB(route.RIBTypeRoute, tab) msgs, err := route.ParseRIB(route.RIBTypeRoute, tab)
if err != nil { if err != nil {
return false, err return nil, err
} }
var prefixList []netip.Prefix
for _, msg := range msgs { for _, msg := range msgs {
m := msg.(*route.RouteMessage) m := msg.(*route.RouteMessage)
if m.Version < 3 || m.Version > 5 { if m.Version < 3 || m.Version > 5 {
return false, fmt.Errorf("unexpected RIB message version: %d", m.Version) return nil, fmt.Errorf("unexpected RIB message version: %d", m.Version)
} }
if m.Type != 4 /* RTM_GET */ { if m.Type != 4 /* RTM_GET */ {
return true, fmt.Errorf("unexpected RIB message type: %d", m.Type) return nil, fmt.Errorf("unexpected RIB message type: %d", m.Type)
} }
if m.Flags&RTF_UP == 0 || if m.Flags&RTF_UP == 0 ||
@@ -52,31 +52,42 @@ func existsInRouteTable(prefix netip.Prefix) (bool, error) {
continue continue
} }
dst, err := toIPAddr(m.Addrs[0]) addr, ok := toNetIPAddr(m.Addrs[0])
if err != nil { if !ok {
return true, fmt.Errorf("unexpected RIB destination: %v", err) continue
} }
mask, _ := toIPAddr(m.Addrs[2]) mask, ok := toNetIPMASK(m.Addrs[2])
cidr, _ := net.IPMask(mask.To4()).Size() if !ok {
if dst.String() == prefix.Addr().String() && cidr == prefix.Bits() { continue
return true, nil }
cidr, _ := mask.Size()
routePrefix := netip.PrefixFrom(addr, cidr)
if routePrefix.IsValid() {
prefixList = append(prefixList, routePrefix)
} }
} }
return prefixList, nil
return false, nil
} }
func toIPAddr(a route.Addr) (net.IP, error) { func toNetIPAddr(a route.Addr) (netip.Addr, bool) {
switch t := a.(type) { switch t := a.(type) {
case *route.Inet4Addr: case *route.Inet4Addr:
ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3]) ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
return ip, nil addr := netip.MustParseAddr(ip.String())
case *route.Inet6Addr: return addr, true
ip := make(net.IP, net.IPv6len)
copy(ip, t.IP[:])
return ip, nil
default: default:
return net.IP{}, fmt.Errorf("unknown family: %v", t) return netip.Addr{}, false
}
}
func toNetIPMASK(a route.Addr) (net.IPMask, bool) {
switch t := a.(type) {
case *route.Inet4Addr:
mask := net.IPv4Mask(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
return mask, true
default:
return nil, false
} }
} }
+28 -11
View File
@@ -60,15 +60,26 @@ func addToRouteTable(prefix netip.Prefix, addr string) error {
return nil return nil
} }
func removeFromRouteTable(prefix netip.Prefix) error { func removeFromRouteTable(prefix netip.Prefix, addr string) error {
_, ipNet, err := net.ParseCIDR(prefix.String()) _, ipNet, err := net.ParseCIDR(prefix.String())
if err != nil { if err != nil {
return err return err
} }
addrMask := "/32"
if prefix.Addr().Unmap().Is6() {
addrMask = "/128"
}
ip, _, err := net.ParseCIDR(addr + addrMask)
if err != nil {
return err
}
route := &netlink.Route{ route := &netlink.Route{
Scope: netlink.SCOPE_UNIVERSE, Scope: netlink.SCOPE_UNIVERSE,
Dst: ipNet, Dst: ipNet,
Gw: ip,
} }
err = netlink.RouteDel(route) err = netlink.RouteDel(route)
@@ -79,15 +90,16 @@ func removeFromRouteTable(prefix netip.Prefix) error {
return nil return nil
} }
func existsInRouteTable(prefix netip.Prefix) (bool, error) { func getRoutesFromTable() ([]netip.Prefix, error) {
tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC) tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC)
if err != nil { if err != nil {
return true, err return nil, err
} }
msgs, err := syscall.ParseNetlinkMessage(tab) msgs, err := syscall.ParseNetlinkMessage(tab)
if err != nil { if err != nil {
return true, err return nil, err
} }
var prefixList []netip.Prefix
loop: loop:
for _, m := range msgs { for _, m := range msgs {
switch m.Header.Type { switch m.Header.Type {
@@ -95,9 +107,10 @@ loop:
break loop break loop
case syscall.RTM_NEWROUTE: case syscall.RTM_NEWROUTE:
rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0]))
attrs, err := syscall.ParseNetlinkRouteAttr(&m) msg := m
attrs, err := syscall.ParseNetlinkRouteAttr(&msg)
if err != nil { if err != nil {
return true, err return nil, err
} }
if rt.Family != syscall.AF_INET { if rt.Family != syscall.AF_INET {
continue loop continue loop
@@ -105,17 +118,21 @@ loop:
for _, attr := range attrs { for _, attr := range attrs {
if attr.Attr.Type == syscall.RTA_DST { if attr.Attr.Type == syscall.RTA_DST {
ip := net.IP(attr.Value) addr, ok := netip.AddrFromSlice(attr.Value)
if !ok {
continue
}
mask := net.CIDRMask(int(rt.DstLen), len(attr.Value)*8) mask := net.CIDRMask(int(rt.DstLen), len(attr.Value)*8)
cidr, _ := mask.Size() cidr, _ := mask.Size()
if ip.String() == prefix.Addr().String() && cidr == prefix.Bits() { routePrefix := netip.PrefixFrom(addr, cidr)
return true, nil if routePrefix.IsValid() && routePrefix.Addr().Is4() {
prefixList = append(prefixList, routePrefix)
} }
} }
} }
} }
} }
return false, nil return prefixList, nil
} }
func enableIPForwarding() error { func enableIPForwarding() error {
@@ -130,5 +147,5 @@ func enableIPForwarding() error {
return nil return nil
} }
return os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644) return os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644) //nolint:gosec
} }
@@ -14,17 +14,6 @@ import (
var errRouteNotFound = fmt.Errorf("route not found") var errRouteNotFound = fmt.Errorf("route not found")
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
if err != nil && err != errRouteNotFound {
return err
}
gatewayIP := netip.MustParseAddr(defaultGateway.String())
if prefix.Contains(gatewayIP) {
log.Warnf("skipping adding a new route for network %s because it overlaps with the default gateway: %s", prefix, gatewayIP)
return nil
}
ok, err := existsInRouteTable(prefix) ok, err := existsInRouteTable(prefix)
if err != nil { if err != nil {
return err return err
@@ -34,20 +23,82 @@ func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
return nil return nil
} }
return addToRouteTable(prefix, addr) ok, err = isSubRange(prefix)
}
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error {
addrIP := net.ParseIP(addr)
prefixGateway, err := getExistingRIBRouteGateway(prefix)
if err != nil { if err != nil {
return err return err
} }
if prefixGateway != nil && !prefixGateway.Equal(addrIP) {
log.Warnf("route for network %s is pointing to a different gateway: %s, should be pointing to: %s, not removing", prefix, prefixGateway, addrIP) if ok {
err := addRouteForCurrentDefaultGateway(prefix)
if err != nil {
log.Warnf("unable to add route for current default gateway route. Will proceed without it. error: %s", err)
}
}
return addToRouteTable(prefix, addr)
}
func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
if err != nil && err != errRouteNotFound {
return err
}
addr := netip.MustParseAddr(defaultGateway.String())
if !prefix.Contains(addr) {
log.Debugf("skipping adding a new route for gateway %s because it is not in the network %s", addr, prefix)
return nil return nil
} }
return removeFromRouteTable(prefix)
gatewayPrefix := netip.PrefixFrom(addr, 32)
ok, err := existsInRouteTable(gatewayPrefix)
if err != nil {
return fmt.Errorf("unable to check if there is an existing route for gateway %s. error: %s", gatewayPrefix, err)
}
if ok {
log.Debugf("skipping adding a new route for gateway %s because it already exists", gatewayPrefix)
return nil
}
gatewayHop, err := getExistingRIBRouteGateway(gatewayPrefix)
if err != nil && err != errRouteNotFound {
return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err)
}
log.Debugf("adding a new route for gateway %s with next hop %s", gatewayPrefix, gatewayHop)
return addToRouteTable(gatewayPrefix, gatewayHop.String())
}
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
routes, err := getRoutesFromTable()
if err != nil {
return false, err
}
for _, tableRoute := range routes {
if tableRoute == prefix {
return true, nil
}
}
return false, nil
}
func isSubRange(prefix netip.Prefix) (bool, error) {
routes, err := getRoutesFromTable()
if err != nil {
return false, err
}
for _, tableRoute := range routes {
if tableRoute.Bits() > minRangeBits && tableRoute.Contains(prefix.Addr()) && tableRoute.Bits() < prefix.Bits() {
return true, nil
}
}
return false, nil
}
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error {
return removeFromRouteTable(prefix, addr)
} }
func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) { func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) {
@@ -24,13 +24,13 @@ func TestAddRemoveRoutes(t *testing.T) {
shouldBeRemoved bool shouldBeRemoved bool
}{ }{
{ {
name: "Should Add And Remove Route", name: "Should Add And Remove Route 100.66.120.0/24",
prefix: netip.MustParsePrefix("100.66.120.0/24"), prefix: netip.MustParsePrefix("100.66.120.0/24"),
shouldRouteToWireguard: true, shouldRouteToWireguard: true,
shouldBeRemoved: true, shouldBeRemoved: true,
}, },
{ {
name: "Should Not Add Or Remove Route", name: "Should Not Add Or Remove Route 127.0.0.1/32",
prefix: netip.MustParsePrefix("127.0.0.1/32"), prefix: netip.MustParsePrefix("127.0.0.1/32"),
shouldRouteToWireguard: false, shouldRouteToWireguard: false,
shouldBeRemoved: false, shouldBeRemoved: false,
@@ -51,29 +51,32 @@ func TestAddRemoveRoutes(t *testing.T) {
require.NoError(t, err, "should create testing wireguard interface") require.NoError(t, err, "should create testing wireguard interface")
err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.Address().IP.String()) err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.Address().IP.String())
require.NoError(t, err, "should not return err") require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
prefixGateway, err := getExistingRIBRouteGateway(testCase.prefix) prefixGateway, err := getExistingRIBRouteGateway(testCase.prefix)
require.NoError(t, err, "should not return err") require.NoError(t, err, "getExistingRIBRouteGateway should not return err")
if testCase.shouldRouteToWireguard { if testCase.shouldRouteToWireguard {
require.Equal(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP") require.Equal(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP")
} else { } else {
require.NotEqual(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to a different interface") require.NotEqual(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to a different interface")
} }
exists, err := existsInRouteTable(testCase.prefix)
require.NoError(t, err, "existsInRouteTable should not return err")
if exists && testCase.shouldRouteToWireguard {
err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String())
require.NoError(t, err, "removeFromRouteTableIfNonSystem should not return err")
err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String()) prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix)
require.NoError(t, err, "should not return err") require.NoError(t, err, "getExistingRIBRouteGateway should not return err")
prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix) internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
require.NoError(t, err, "should not return err") require.NoError(t, err)
internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) if testCase.shouldBeRemoved {
require.NoError(t, err) require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway")
} else {
if testCase.shouldBeRemoved { require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway")
require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway") }
} else {
require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway")
} }
}) })
} }
@@ -123,7 +126,7 @@ func TestGetExistingRIBRouteGateway(t *testing.T) {
func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
fmt.Println("defaultGateway: ", defaultGateway) t.Log("defaultGateway: ", defaultGateway)
if err != nil { if err != nil {
t.Fatal("shouldn't return error when fetching the gateway: ", err) t.Fatal("shouldn't return error when fetching the gateway: ", err)
} }
@@ -207,7 +210,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
// route should either not have been added or should have been removed // route should either not have been added or should have been removed
// In case of already existing route, it should not have been added (but still exist) // In case of already existing route, it should not have been added (but still exist)
ok, err := existsInRouteTable(testCase.prefix) ok, err := existsInRouteTable(testCase.prefix)
fmt.Println("Buffer string: ", buf.String()) t.Log("Buffer string: ", buf.String())
require.NoError(t, err, "should not return err") require.NoError(t, err, "should not return err")
if !strings.Contains(buf.String(), "because it already exists") { if !strings.Contains(buf.String(), "because it already exists") {
require.False(t, ok, "route should not exist") require.False(t, ok, "route should not exist")
@@ -215,3 +218,66 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
}) })
} }
} }
func TestExistsInRouteTable(t *testing.T) {
addresses, err := net.InterfaceAddrs()
if err != nil {
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
}
var addressPrefixes []netip.Prefix
for _, address := range addresses {
p := netip.MustParsePrefix(address.String())
if p.Addr().Is4() {
addressPrefixes = append(addressPrefixes, p.Masked())
}
}
for _, prefix := range addressPrefixes {
exists, err := existsInRouteTable(prefix)
if err != nil {
t.Fatal("shouldn't return error when checking if address exists in route table: ", err)
}
if !exists {
t.Fatalf("address %s should exist in route table", prefix)
}
}
}
func TestIsSubRange(t *testing.T) {
addresses, err := net.InterfaceAddrs()
if err != nil {
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
}
var subRangeAddressPrefixes []netip.Prefix
var nonSubRangeAddressPrefixes []netip.Prefix
for _, address := range addresses {
p := netip.MustParsePrefix(address.String())
if !p.Addr().IsLoopback() && p.Addr().Is4() && p.Bits() < 32 {
p2 := netip.PrefixFrom(p.Masked().Addr(), p.Bits()+1)
subRangeAddressPrefixes = append(subRangeAddressPrefixes, p2)
nonSubRangeAddressPrefixes = append(nonSubRangeAddressPrefixes, p.Masked())
}
}
for _, prefix := range subRangeAddressPrefixes {
isSubRangePrefix, err := isSubRange(prefix)
if err != nil {
t.Fatal("shouldn't return error when checking if address is sub-range: ", err)
}
if !isSubRangePrefix {
t.Fatalf("address %s should be sub-range of an existing route in the table", prefix)
}
}
for _, prefix := range nonSubRangeAddressPrefixes {
isSubRangePrefix, err := isSubRange(prefix)
if err != nil {
t.Fatal("shouldn't return error when checking if address is sub-range: ", err)
}
if isSubRangePrefix {
t.Fatalf("address %s should not be sub-range of an existing route in the table", prefix)
}
}
}
@@ -21,8 +21,12 @@ func addToRouteTable(prefix netip.Prefix, addr string) error {
return nil return nil
} }
func removeFromRouteTable(prefix netip.Prefix) error { func removeFromRouteTable(prefix netip.Prefix, addr string) error {
cmd := exec.Command("route", "delete", prefix.String()) args := []string{"delete", prefix.String()}
if runtime.GOOS == "darwin" {
args = append(args, addr)
}
cmd := exec.Command("route", args...)
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
return err return err
@@ -15,23 +15,32 @@ type Win32_IP4RouteTable struct {
Mask string Mask string
} }
func existsInRouteTable(prefix netip.Prefix) (bool, error) { func getRoutesFromTable() ([]netip.Prefix, error) {
var routes []Win32_IP4RouteTable var routes []Win32_IP4RouteTable
query := "SELECT Destination, Mask FROM Win32_IP4RouteTable" query := "SELECT Destination, Mask FROM Win32_IP4RouteTable"
err := wmi.Query(query, &routes) err := wmi.Query(query, &routes)
if err != nil { if err != nil {
return true, err return nil, err
} }
var prefixList []netip.Prefix
for _, route := range routes { for _, route := range routes {
ip := net.ParseIP(route.Mask) addr, err := netip.ParseAddr(route.Destination)
ip = ip.To4() if err != nil {
mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]) continue
}
maskSlice := net.ParseIP(route.Mask).To4()
if maskSlice == nil {
continue
}
mask := net.IPv4Mask(maskSlice[0], maskSlice[1], maskSlice[2], maskSlice[3])
cidr, _ := mask.Size() cidr, _ := mask.Size()
if route.Destination == prefix.Addr().String() && cidr == prefix.Bits() {
return true, nil routePrefix := netip.PrefixFrom(addr, cidr)
if routePrefix.IsValid() && routePrefix.Addr().Is4() {
prefixList = append(prefixList, routePrefix)
} }
} }
return false, nil return prefixList, nil
} }
+142 -122
View File
@@ -43,6 +43,7 @@ type LoginRequest struct {
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"` CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"` CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
IsLinuxDesktopClient bool `protobuf:"varint,8,opt,name=isLinuxDesktopClient,proto3" json:"isLinuxDesktopClient,omitempty"` IsLinuxDesktopClient bool `protobuf:"varint,8,opt,name=isLinuxDesktopClient,proto3" json:"isLinuxDesktopClient,omitempty"`
Hostname string `protobuf:"bytes,9,opt,name=hostname,proto3" json:"hostname,omitempty"`
} }
func (x *LoginRequest) Reset() { func (x *LoginRequest) Reset() {
@@ -133,6 +134,13 @@ func (x *LoginRequest) GetIsLinuxDesktopClient() bool {
return false return false
} }
func (x *LoginRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
type LoginResponse struct { type LoginResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -210,6 +218,7 @@ type WaitSSOLoginRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
UserCode string `protobuf:"bytes,1,opt,name=userCode,proto3" json:"userCode,omitempty"` UserCode string `protobuf:"bytes,1,opt,name=userCode,proto3" json:"userCode,omitempty"`
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
} }
func (x *WaitSSOLoginRequest) Reset() { func (x *WaitSSOLoginRequest) Reset() {
@@ -251,6 +260,13 @@ func (x *WaitSSOLoginRequest) GetUserCode() string {
return "" return ""
} }
func (x *WaitSSOLoginRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
type WaitSSOLoginResponse struct { type WaitSSOLoginResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -1051,7 +1067,7 @@ var file_daemon_proto_rawDesc = []byte{
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xca, 0x02, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe6, 0x02, 0x0a, 0x0c, 0x4c, 0x6f,
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
@@ -1072,128 +1088,132 @@ var file_daemon_proto_rawDesc = []byte{
0x73, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73,
0x6b, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x6b, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
0x52, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x52, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70,
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x6d, 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f,
0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65,
0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x61,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c,
0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d,
0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c,
0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65,
0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e,
0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12,
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20,
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12,
0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65,
0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a,
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20,
0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65,
0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64,
0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79,
0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65,
0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63,
0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79,
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49,
0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64,
0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64,
0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c,
0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a,
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a,
0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49,
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61,
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64,
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e,
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e,
0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27,
0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67,
0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69,
0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c,
0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d,
0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a,
0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e,
0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65,
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65,
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
} }
var ( var (
+3
View File
@@ -52,6 +52,8 @@ message LoginRequest {
bytes customDNSAddress = 7; bytes customDNSAddress = 7;
bool isLinuxDesktopClient = 8; bool isLinuxDesktopClient = 8;
string hostname = 9;
} }
message LoginResponse { message LoginResponse {
@@ -63,6 +65,7 @@ message LoginResponse {
message WaitSSOLoginRequest { message WaitSSOLoginRequest {
string userCode = 1; string userCode = 1;
string hostname = 2;
} }
message WaitSSOLoginResponse {} message WaitSSOLoginResponse {}
+11
View File
@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/netbirdio/netbird/client/internal/auth" "github.com/netbirdio/netbird/client/internal/auth"
"github.com/netbirdio/netbird/client/system"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@@ -181,6 +182,11 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
s.latestConfigInput.CustomDNSAddress = []byte{} s.latestConfigInput.CustomDNSAddress = []byte{}
} }
if msg.Hostname != "" {
// nolint
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, msg.Hostname)
}
s.mutex.Unlock() s.mutex.Unlock()
inputConfig.PreSharedKey = &msg.PreSharedKey inputConfig.PreSharedKey = &msg.PreSharedKey
@@ -275,6 +281,11 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin
ctx = metadata.NewOutgoingContext(ctx, md) ctx = metadata.NewOutgoingContext(ctx, md)
} }
if msg.Hostname != "" {
// nolint
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, msg.Hostname)
}
s.actCancel = cancel s.actCancel = cancel
s.mutex.Unlock() s.mutex.Unlock()
+4 -4
View File
@@ -2,11 +2,12 @@ package ssh
import ( import (
"fmt" "fmt"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
"net" "net"
"os" "os"
"time" "time"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
) )
// Client wraps crypto/ssh Client to simplify usage // Client wraps crypto/ssh Client to simplify usage
@@ -73,8 +74,7 @@ func (c *Client) OpenTerminal() error {
if err := session.Wait(); err != nil { if err := session.Wait(); err != nil {
if e, ok := err.(*ssh.ExitError); ok { if e, ok := err.(*ssh.ExitError); ok {
switch e.ExitStatus() { if e.ExitStatus() == 130 {
case 130:
return nil return nil
} }
} }
+3 -2
View File
@@ -9,9 +9,10 @@ import (
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"strings"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
"strings"
) )
// KeyType is a type of SSH key // KeyType is a type of SSH key
@@ -42,7 +43,7 @@ func GeneratePrivateKey(keyType KeyType) ([]byte, error) {
case RSA: case RSA:
key, err = rsa.GenerateKey(rand.Reader, RSAKeySize) key, err = rsa.GenerateKey(rand.Reader, RSAKeySize)
default: default:
return nil, fmt.Errorf("unsupported ket type %s", keyType) return nil, fmt.Errorf("unsupported key type %s", keyType)
} }
if err != nil { if err != nil {
return nil, err return nil, err
+1 -2
View File
@@ -6,7 +6,6 @@ package system
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@@ -24,7 +23,7 @@ func GetInfo(ctx context.Context) *Info {
utsname := unix.Utsname{} utsname := unix.Utsname{}
err := unix.Uname(&utsname) err := unix.Uname(&utsname)
if err != nil { if err != nil {
fmt.Println("getInfo:", err) log.Warnf("getInfo: %s", err)
} }
sysName := string(bytes.Split(utsname.Sysname[:], []byte{0})[0]) sysName := string(bytes.Split(utsname.Sysname[:], []byte{0})[0])
machine := string(bytes.Split(utsname.Machine[:], []byte{0})[0]) machine := string(bytes.Split(utsname.Machine[:], []byte{0})[0])
+6 -5
View File
@@ -6,13 +6,14 @@ package system
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strings" "strings"
"time" "time"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
@@ -43,8 +44,8 @@ func GetInfo(ctx context.Context) *Info {
} }
} }
osStr := strings.Replace(info, "\n", "", -1) osStr := strings.ReplaceAll(info, "\n", "")
osStr = strings.Replace(osStr, "\r\n", "", -1) osStr = strings.ReplaceAll(osStr, "\r\n", "")
osInfo := strings.Split(osStr, " ") osInfo := strings.Split(osStr, " ")
if osName == "" { if osName == "" {
osName = osInfo[3] osName = osInfo[3]
@@ -67,7 +68,7 @@ func _getInfo() string {
cmd.Stderr = &stderr cmd.Stderr = &stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
fmt.Println("getInfo:", err) log.Warnf("getInfo: %s", err)
} }
return out.String() return out.String()
} }
@@ -81,7 +82,7 @@ func _getReleaseInfo() string {
cmd.Stderr = &stderr cmd.Stderr = &stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
fmt.Println("getReleaseInfo:", err) log.Warnf("geucwReleaseInfo: %s", err)
} }
return out.String() return out.String()
} }
+38 -3
View File
@@ -5,17 +5,24 @@ import (
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
"strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/yusufpapurcu/wmi"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
type Win32_OperatingSystem struct {
Caption string
}
// GetInfo retrieves and parses the system information // GetInfo retrieves and parses the system information
func GetInfo(ctx context.Context) *Info { func GetInfo(ctx context.Context) *Info {
ver := getOSVersion() osName, osVersion := getOSNameAndVersion()
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} buildVersion := getBuildVersion()
gio := &Info{Kernel: "windows", OSVersion: osVersion, Core: buildVersion, Platform: "unknown", OS: osName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
systemHostname, _ := os.Hostname() systemHostname, _ := os.Hostname()
gio.Hostname = extractDeviceName(ctx, systemHostname) gio.Hostname = extractDeviceName(ctx, systemHostname)
gio.WiretrusteeVersion = version.NetbirdVersion() gio.WiretrusteeVersion = version.NetbirdVersion()
@@ -24,7 +31,35 @@ func GetInfo(ctx context.Context) *Info {
return gio return gio
} }
func getOSVersion() string { func getOSNameAndVersion() (string, string) {
var dst []Win32_OperatingSystem
query := wmi.CreateQuery(&dst, "")
err := wmi.Query(query, &dst)
if err != nil {
log.Fatal(err)
}
if len(dst) == 0 {
return "Windows", getBuildVersion()
}
split := strings.Split(dst[0].Caption, " ")
if len(split) < 3 {
return "Windows", getBuildVersion()
}
name := split[1]
version := split[2]
if split[2] == "Server" {
name = fmt.Sprintf("%s %s", split[1], split[2])
version = split[3]
}
return name, version
}
func getBuildVersion() string {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
+2 -2
View File
@@ -69,7 +69,7 @@ func main() {
a.Run() a.Run()
} else { } else {
if err := checkPIDFile(); err != nil { if err := checkPIDFile(); err != nil {
fmt.Println(err) log.Errorf("check PID file: %v", err)
return return
} }
systray.Run(client.onTrayReady, client.onTrayExit) systray.Run(client.onTrayReady, client.onTrayExit)
@@ -634,5 +634,5 @@ func checkPIDFile() error {
} }
} }
return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) //nolint:gosec
} }
+6 -5
View File
@@ -4,11 +4,12 @@ package dns
import ( import (
"fmt" "fmt"
"github.com/miekg/dns"
"golang.org/x/net/idna"
"net" "net"
"regexp" "regexp"
"strings" "strings"
"github.com/miekg/dns"
"golang.org/x/net/idna"
) )
const ( const (
@@ -85,6 +86,8 @@ func (s SimpleRecord) Len() uint16 {
} }
} }
var invalidHostMatcher = regexp.MustCompile(invalidHostLabel)
// GetParsedDomainLabel returns a domain label with max 59 characters, // GetParsedDomainLabel returns a domain label with max 59 characters,
// parsed for old Hosts.txt requirements, and converted to ASCII and lowercase // parsed for old Hosts.txt requirements, and converted to ASCII and lowercase
func GetParsedDomainLabel(name string) (string, error) { func GetParsedDomainLabel(name string) (string, error) {
@@ -95,11 +98,9 @@ func GetParsedDomainLabel(name string) (string, error) {
rawLabel := labels[0] rawLabel := labels[0]
ascii, err := idna.Punycode.ToASCII(rawLabel) ascii, err := idna.Punycode.ToASCII(rawLabel)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to convert host lavel to ASCII, error: %v", err) return "", fmt.Errorf("unable to convert host label to ASCII, error: %v", err)
} }
invalidHostMatcher := regexp.MustCompile(invalidHostLabel)
validHost := strings.ToLower(invalidHostMatcher.ReplaceAllString(ascii, "-")) validHost := strings.ToLower(invalidHostMatcher.ReplaceAllString(ascii, "-"))
if len(validHost) > 58 { if len(validHost) > 58 {
validHost = validHost[:59] validHost = validHost[:59]
+2 -1
View File
@@ -51,7 +51,8 @@ require (
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/nadoo/ipset v0.5.0 github.com/nadoo/ipset v0.5.0
github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695
github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695
github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/okta/okta-sdk-golang/v2 v2.18.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2
+4 -2
View File
@@ -495,8 +495,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 h1:zhe8qseauBuYOS910jpl5sv8Tb+36zxQPXrwYXqll0g= github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695 h1:c/Rvyy/mqbFoKo6FS8ihQ3/3y+TAl0qDEH0pO2tXayM=
github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA=
github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695 h1:9HRnqSosRuKyOZgVN/hJW3DG2zVyt5AARmiQlSuDPIc=
github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=
+2
View File
@@ -132,6 +132,7 @@ func resetGlobals() {
} }
func createFiles(t *testing.T) (string, []module) { func createFiles(t *testing.T) (string, []module) {
t.Helper()
writeFile := func(path, text string) { writeFile := func(path, text string) {
if err := os.WriteFile(path, []byte(text), 0644); err != nil { if err := os.WriteFile(path, []byte(text), 0644); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -167,6 +168,7 @@ func createFiles(t *testing.T) (string, []module) {
} }
func getRandomLoadedModule(t *testing.T) (string, error) { func getRandomLoadedModule(t *testing.T) (string, error) {
t.Helper()
f, err := os.Open("/proc/modules") f, err := os.Open("/proc/modules")
if err != nil { if err != nil {
return "", err return "", err
+1 -1
View File
@@ -1,6 +1,6 @@
package iface package iface
// TunAdapter is an interface for create tun device from externel service // TunAdapter is an interface for create tun device from external service
type TunAdapter interface { type TunAdapter interface {
ConfigureInterface(address string, mtu int, dns string, searchDomains string, routes string) (int, error) ConfigureInterface(address string, mtu int, dns string, searchDomains string, routes string) (int, error)
UpdateAddr(address string) error UpdateAddr(address string) error
+2 -1
View File
@@ -99,7 +99,8 @@ func (c *tunDevice) assignAddr() error {
} }
if len(list) > 0 { if len(list) > 0 {
for _, a := range list { for _, a := range list {
err = netlink.AddrDel(link, &a) addr := a
err = netlink.AddrDel(link, &addr)
if err != nil { if err != nil {
return err return err
} }
+1 -1
View File
@@ -141,7 +141,7 @@ func (c *wGConfigurer) removeAllowedIP(peerKey string, allowedIP string) error {
for i, existingAllowedIP := range existingPeer.AllowedIPs { for i, existingAllowedIP := range existingPeer.AllowedIPs {
if existingAllowedIP.String() == ipNet.String() { if existingAllowedIP.String() == ipNet.String() {
newAllowedIPs = append(existingPeer.AllowedIPs[:i], existingPeer.AllowedIPs[i+1:]...) newAllowedIPs = append(existingPeer.AllowedIPs[:i], existingPeer.AllowedIPs[i+1:]...) //nolint:gocritic
break break
} }
} }
+1 -1
View File
@@ -36,7 +36,7 @@ fi
if [[ "x-$NETBIRD_DOMAIN" == "x-" ]]; then if [[ "x-$NETBIRD_DOMAIN" == "x-" ]]; then
echo NETBIRD_DOMAIN is not set, please update your setup.env file echo NETBIRD_DOMAIN is not set, please update your setup.env file
echo If you are migrating from old versions, you migh need to update your variables prefixes from echo If you are migrating from old versions, you might need to update your variables prefixes from
echo WIRETRUSTEE_.. TO NETBIRD_ echo WIRETRUSTEE_.. TO NETBIRD_
exit 1 exit 1
fi fi
@@ -387,7 +387,7 @@ check_nb_domain() {
if [ "$DOMAIN" == "netbird.example.com" ]; then if [ "$DOMAIN" == "netbird.example.com" ]; then
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
retrun 1 return 1
fi fi
return 0 return 0
} }
+1 -1
View File
@@ -677,7 +677,7 @@ no-cli
# #
#cli-password=$5$79a316b350311570$81df9cfb9af7f5e5a76eada31e7097b663a0670f99a3c07ded3f1c8e59c5658a #cli-password=$5$79a316b350311570$81df9cfb9af7f5e5a76eada31e7097b663a0670f99a3c07ded3f1c8e59c5658a
# #
# Or unsecure form for the same password: # Or insecure form for the same password:
# #
# cli-password=CHANGE_ME # cli-password=CHANGE_ME
+11 -9
View File
@@ -31,6 +31,7 @@ import (
const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB" const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
func startManagement(t *testing.T) (*grpc.Server, net.Listener) { func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
t.Helper()
level, _ := log.ParseLevel("debug") level, _ := log.ParseLevel("debug")
log.SetLevel(level) log.SetLevel(level)
@@ -57,7 +58,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
t.Fatal(err) t.Fatal(err)
} }
peersUpdateManager := mgmt.NewPeersUpdateManager() peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore, false) eventStore, false)
@@ -81,6 +82,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
} }
func startMockManagement(t *testing.T) (*grpc.Server, net.Listener, *mock_server.ManagementServiceServerMock, wgtypes.Key) { func startMockManagement(t *testing.T) (*grpc.Server, net.Listener, *mock_server.ManagementServiceServerMock, wgtypes.Key) {
t.Helper()
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -168,7 +170,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) {
t.Error("expecting err on unregistered login, got nil") t.Error("expecting err on unregistered login, got nil")
} }
if s, ok := status.FromError(err); !ok || s.Code() != codes.PermissionDenied { if s, ok := status.FromError(err); !ok || s.Code() != codes.PermissionDenied {
t.Errorf("expecting err code %d denied on on unregistered login got %d", codes.PermissionDenied, s.Code()) t.Errorf("expecting err code %d denied on unregistered login got %d", codes.PermissionDenied, s.Code())
} }
} }
@@ -283,7 +285,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
testKey, err := wgtypes.GenerateKey() testKey, err := wgtypes.GenerateKey()
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
serverAddr := lis.Addr().String() serverAddr := lis.Addr().String()
@@ -291,12 +293,12 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
testClient, err := NewClient(ctx, serverAddr, testKey, false) testClient, err := NewClient(ctx, serverAddr, testKey, false)
if err != nil { if err != nil {
log.Fatalf("error while creating testClient: %v", err) t.Fatalf("error while creating testClient: %v", err)
} }
key, err := testClient.GetServerPublicKey() key, err := testClient.GetServerPublicKey()
if err != nil { if err != nil {
log.Fatalf("error while getting server public key from testclient, %v", err) t.Fatalf("error while getting server public key from testclient, %v", err)
} }
var actualMeta *mgmtProto.PeerSystemMeta var actualMeta *mgmtProto.PeerSystemMeta
@@ -362,7 +364,7 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) {
testKey, err := wgtypes.GenerateKey() testKey, err := wgtypes.GenerateKey()
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
serverAddr := lis.Addr().String() serverAddr := lis.Addr().String()
@@ -370,7 +372,7 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) {
client, err := NewClient(ctx, serverAddr, testKey, false) client, err := NewClient(ctx, serverAddr, testKey, false)
if err != nil { if err != nil {
log.Fatalf("error while creating testClient: %v", err) t.Fatalf("error while creating testClient: %v", err)
} }
expectedFlowInfo := &mgmtProto.DeviceAuthorizationFlow{ expectedFlowInfo := &mgmtProto.DeviceAuthorizationFlow{
@@ -406,7 +408,7 @@ func Test_GetPKCEAuthorizationFlow(t *testing.T) {
testKey, err := wgtypes.GenerateKey() testKey, err := wgtypes.GenerateKey()
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
serverAddr := lis.Addr().String() serverAddr := lis.Addr().String()
@@ -414,7 +416,7 @@ func Test_GetPKCEAuthorizationFlow(t *testing.T) {
client, err := NewClient(ctx, serverAddr, testKey, false) client, err := NewClient(ctx, serverAddr, testKey, false)
if err != nil { if err != nil {
log.Fatalf("error while creating testClient: %v", err) t.Fatalf("error while creating testClient: %v", err)
} }
expectedFlowInfo := &mgmtProto.PKCEAuthorizationFlow{ expectedFlowInfo := &mgmtProto.PKCEAuthorizationFlow{
+1 -1
View File
@@ -130,7 +130,7 @@ var (
if err != nil { if err != nil {
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err) return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
} }
peersUpdateManager := server.NewPeersUpdateManager() peersUpdateManager := server.NewPeersUpdateManager(appMetrics)
var idpManager idp.Manager var idpManager idp.Manager
if config.IdpManagerConfig != nil { if config.IdpManagerConfig != nil {
+1 -1
View File
@@ -67,7 +67,7 @@ func init() {
rootCmd.MarkFlagRequired("config") //nolint rootCmd.MarkFlagRequired("config") //nolint
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "") rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout") rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
rootCmd.AddCommand(mgmtCmd) rootCmd.AddCommand(mgmtCmd)
migrationCmd.PersistentFlags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location") migrationCmd.PersistentFlags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
+166 -54
View File
@@ -17,15 +17,18 @@ import (
"github.com/eko/gocache/v3/cache" "github.com/eko/gocache/v3/cache"
cacheStore "github.com/eko/gocache/v3/store" cacheStore "github.com/eko/gocache/v3/store"
"github.com/netbirdio/management-integrations/additions"
gocache "github.com/patrickmn/go-cache" gocache "github.com/patrickmn/go-cache"
"github.com/rs/xid" "github.com/rs/xid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/base62" "github.com/netbirdio/netbird/base62"
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@@ -42,6 +45,8 @@ const (
DefaultPeerLoginExpiration = 24 * time.Hour DefaultPeerLoginExpiration = 24 * time.Hour
) )
type ExternalCacheManager cache.CacheInterface[*idp.UserData]
func cacheEntryExpiration() time.Duration { func cacheEntryExpiration() time.Duration {
r := rand.Intn(int(CacheExpirationMax.Milliseconds()-CacheExpirationMin.Milliseconds())) + int(CacheExpirationMin.Milliseconds()) r := rand.Intn(int(CacheExpirationMax.Milliseconds()-CacheExpirationMin.Milliseconds())) + int(CacheExpirationMin.Milliseconds())
return time.Duration(r) * time.Millisecond return time.Duration(r) * time.Millisecond
@@ -57,19 +62,22 @@ type AccountManager interface {
InviteUser(accountID string, initiatorUserID string, targetUserID string) error InviteUser(accountID string, initiatorUserID string, targetUserID string) error
ListSetupKeys(accountID, userID string) ([]*SetupKey, error) ListSetupKeys(accountID, userID string) ([]*SetupKey, error)
SaveUser(accountID, initiatorUserID string, update *User) (*UserInfo, error) SaveUser(accountID, initiatorUserID string, update *User) (*UserInfo, error)
SaveOrAddUser(accountID, initiatorUserID string, update *User, addIfNotExists bool) (*UserInfo, error)
GetSetupKey(accountID, userID, keyID string) (*SetupKey, error) GetSetupKey(accountID, userID, keyID string) (*SetupKey, error)
GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error)
GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, *User, error) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, *User, error)
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error) GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
DeleteAccount(accountID, userID string) error
MarkPATUsed(tokenID string) error MarkPATUsed(tokenID string) error
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
GetPeers(accountID, userID string) ([]*Peer, error) ListUsers(accountID string) ([]*User, error)
GetPeers(accountID, userID string) ([]*nbpeer.Peer, error)
MarkPeerConnected(peerKey string, connected bool) error MarkPeerConnected(peerKey string, connected bool) error
DeletePeer(accountID, peerID, userID string) error DeletePeer(accountID, peerID, userID string) error
UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error) UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error)
GetNetworkMap(peerID string) (*NetworkMap, error) GetNetworkMap(peerID string) (*NetworkMap, error)
GetPeerNetwork(peerID string) (*Network, error) GetPeerNetwork(peerID string) (*Network, error)
AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) AddPeer(setupKey, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, error)
CreatePAT(accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) CreatePAT(accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error)
DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error
GetPAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) GetPAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error)
@@ -97,14 +105,17 @@ type AccountManager interface {
DeleteNameServerGroup(accountID, nsGroupID, userID string) error DeleteNameServerGroup(accountID, nsGroupID, userID string) error
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error) ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
GetDNSDomain() string GetDNSDomain() string
StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any)
GetEvents(accountID, userID string) ([]*activity.Event, error) GetEvents(accountID, userID string) ([]*activity.Event, error)
GetDNSSettings(accountID string, userID string) (*DNSSettings, error) GetDNSSettings(accountID string, userID string) (*DNSSettings, error)
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
GetPeer(accountID, peerID, userID string) (*Peer, error) GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error)
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error) UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
GetAllConnectedPeers() (map[string]struct{}, error) GetAllConnectedPeers() (map[string]struct{}, error)
HasConnectedChannel(peerID string) bool
GetExternalCacheManager() ExternalCacheManager
} }
type DefaultAccountManager struct { type DefaultAccountManager struct {
@@ -112,12 +123,13 @@ type DefaultAccountManager struct {
// cacheMux and cacheLoading helps to make sure that only a single cache reload runs at a time per accountID // cacheMux and cacheLoading helps to make sure that only a single cache reload runs at a time per accountID
cacheMux sync.Mutex cacheMux sync.Mutex
// cacheLoading keeps the accountIDs that are currently reloading. The accountID has to be removed once cache has been reloaded // cacheLoading keeps the accountIDs that are currently reloading. The accountID has to be removed once cache has been reloaded
cacheLoading map[string]chan struct{} cacheLoading map[string]chan struct{}
peersUpdateManager *PeersUpdateManager peersUpdateManager *PeersUpdateManager
idpManager idp.Manager idpManager idp.Manager
cacheManager cache.CacheInterface[[]*idp.UserData] cacheManager cache.CacheInterface[[]*idp.UserData]
ctx context.Context externalCacheManager ExternalCacheManager
eventStore activity.Store ctx context.Context
eventStore activity.Store
// singleAccountMode indicates whether the instance has a single account. // singleAccountMode indicates whether the instance has a single account.
// If true, then every new user will end up under the same account. // If true, then every new user will end up under the same account.
@@ -151,17 +163,24 @@ type Settings struct {
// JWTGroupsClaimName from which we extract groups name to add it to account groups // JWTGroupsClaimName from which we extract groups name to add it to account groups
JWTGroupsClaimName string JWTGroupsClaimName string
// Extra is a dictionary of Account settings
Extra *account.ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
} }
// Copy copies the Settings struct // Copy copies the Settings struct
func (s *Settings) Copy() *Settings { func (s *Settings) Copy() *Settings {
return &Settings{ settings := &Settings{
PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled, PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled,
PeerLoginExpiration: s.PeerLoginExpiration, PeerLoginExpiration: s.PeerLoginExpiration,
JWTGroupsEnabled: s.JWTGroupsEnabled, JWTGroupsEnabled: s.JWTGroupsEnabled,
JWTGroupsClaimName: s.JWTGroupsClaimName, JWTGroupsClaimName: s.JWTGroupsClaimName,
GroupsPropagationEnabled: s.GroupsPropagationEnabled, GroupsPropagationEnabled: s.GroupsPropagationEnabled,
} }
if s.Extra != nil {
settings.Extra = s.Extra.Copy()
}
return settings
} }
// Account represents a unique account of the system // Account represents a unique account of the system
@@ -177,8 +196,8 @@ type Account struct {
SetupKeys map[string]*SetupKey `gorm:"-"` SetupKeys map[string]*SetupKey `gorm:"-"`
SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"` SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"`
Network *Network `gorm:"embedded;embeddedPrefix:network_"` Network *Network `gorm:"embedded;embeddedPrefix:network_"`
Peers map[string]*Peer `gorm:"-"` Peers map[string]*nbpeer.Peer `gorm:"-"`
PeersG []Peer `json:"-" gorm:"foreignKey:AccountID;references:id"` PeersG []nbpeer.Peer `json:"-" gorm:"foreignKey:AccountID;references:id"`
Users map[string]*User `gorm:"-"` Users map[string]*User `gorm:"-"`
UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"` UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"`
Groups map[string]*Group `gorm:"-"` Groups map[string]*Group `gorm:"-"`
@@ -204,6 +223,7 @@ type UserInfo struct {
Status string `json:"-"` Status string `json:"-"`
IsServiceUser bool `json:"is_service_user"` IsServiceUser bool `json:"is_service_user"`
IsBlocked bool `json:"is_blocked"` IsBlocked bool `json:"is_blocked"`
NonDeletable bool `json:"non_deletable"`
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
Issued string `json:"issued"` Issued string `json:"issued"`
IntegrationReference IntegrationReference `json:"-"` IntegrationReference IntegrationReference `json:"-"`
@@ -212,7 +232,7 @@ type UserInfo struct {
// getRoutesToSync returns the enabled routes for the peer ID and the routes // getRoutesToSync returns the enabled routes for the peer ID and the routes
// from the ACL peers that have distribution groups associated with the peer ID. // from the ACL peers that have distribution groups associated with the peer ID.
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID. // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Route { func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*route.Route {
routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID) routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID)
peerRoutesMembership := make(lookupMap) peerRoutesMembership := make(lookupMap)
for _, r := range append(routes, peerDisabledRoutes...) { for _, r := range append(routes, peerDisabledRoutes...) {
@@ -336,10 +356,22 @@ func (a *Account) GetGroup(groupID string) *Group {
// GetPeerNetworkMap returns a group by ID if exists, nil otherwise // GetPeerNetworkMap returns a group by ID if exists, nil otherwise
func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
peer := a.Peers[peerID]
if peer == nil {
return &NetworkMap{
Network: a.Network.Copy(),
}
}
validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer})
if len(validatedPeers) == 0 {
return &NetworkMap{
Network: a.Network.Copy(),
}
}
aclPeers, firewallRules := a.getPeerConnectionResources(peerID) aclPeers, firewallRules := a.getPeerConnectionResources(peerID)
// exclude expired peers // exclude expired peers
var peersToConnect []*Peer var peersToConnect []*nbpeer.Peer
var expiredPeers []*Peer var expiredPeers []*nbpeer.Peer
for _, p := range aclPeers { for _, p := range aclPeers {
expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration) expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration)
if a.Settings.PeerLoginExpirationEnabled && expired { if a.Settings.PeerLoginExpirationEnabled && expired {
@@ -377,8 +409,8 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
} }
// GetExpiredPeers returns peers that have been expired // GetExpiredPeers returns peers that have been expired
func (a *Account) GetExpiredPeers() []*Peer { func (a *Account) GetExpiredPeers() []*nbpeer.Peer {
var peers []*Peer var peers []*nbpeer.Peer
for _, peer := range a.GetPeersWithExpiration() { for _, peer := range a.GetPeersWithExpiration() {
expired, _ := peer.LoginExpired(a.Settings.PeerLoginExpiration) expired, _ := peer.LoginExpired(a.Settings.PeerLoginExpiration)
if expired { if expired {
@@ -417,8 +449,8 @@ func (a *Account) GetNextPeerExpiration() (time.Duration, bool) {
} }
// GetPeersWithExpiration returns a list of peers that have Peer.LoginExpirationEnabled set to true and that were added by a user // GetPeersWithExpiration returns a list of peers that have Peer.LoginExpirationEnabled set to true and that were added by a user
func (a *Account) GetPeersWithExpiration() []*Peer { func (a *Account) GetPeersWithExpiration() []*nbpeer.Peer {
peers := make([]*Peer, 0) peers := make([]*nbpeer.Peer, 0)
for _, peer := range a.Peers { for _, peer := range a.Peers {
if peer.LoginExpirationEnabled && peer.AddedWithSSOLogin() { if peer.LoginExpirationEnabled && peer.AddedWithSSOLogin() {
peers = append(peers, peer) peers = append(peers, peer)
@@ -428,8 +460,8 @@ func (a *Account) GetPeersWithExpiration() []*Peer {
} }
// GetPeers returns a list of all Account peers // GetPeers returns a list of all Account peers
func (a *Account) GetPeers() []*Peer { func (a *Account) GetPeers() []*nbpeer.Peer {
var peers []*Peer var peers []*nbpeer.Peer
for _, peer := range a.Peers { for _, peer := range a.Peers {
peers = append(peers, peer) peers = append(peers, peer)
} }
@@ -443,7 +475,7 @@ func (a *Account) UpdateSettings(update *Settings) *Account {
} }
// UpdatePeer saves new or replaces existing peer // UpdatePeer saves new or replaces existing peer
func (a *Account) UpdatePeer(update *Peer) { func (a *Account) UpdatePeer(update *nbpeer.Peer) {
a.Peers[update.ID] = update a.Peers[update.ID] = update
} }
@@ -472,7 +504,7 @@ func (a *Account) DeletePeer(peerID string) {
// FindPeerByPubKey looks for a Peer by provided WireGuard public key in the Account or returns error if it wasn't found. // FindPeerByPubKey looks for a Peer by provided WireGuard public key in the Account or returns error if it wasn't found.
// It will return an object copy of the peer. // It will return an object copy of the peer.
func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) { func (a *Account) FindPeerByPubKey(peerPubKey string) (*nbpeer.Peer, error) {
for _, peer := range a.Peers { for _, peer := range a.Peers {
if peer.Key == peerPubKey { if peer.Key == peerPubKey {
return peer.Copy(), nil return peer.Copy(), nil
@@ -483,8 +515,8 @@ func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) {
} }
// FindUserPeers returns a list of peers that user owns (created) // FindUserPeers returns a list of peers that user owns (created)
func (a *Account) FindUserPeers(userID string) ([]*Peer, error) { func (a *Account) FindUserPeers(userID string) ([]*nbpeer.Peer, error) {
peers := make([]*Peer, 0) peers := make([]*nbpeer.Peer, 0)
for _, peer := range a.Peers { for _, peer := range a.Peers {
if peer.UserID == userID { if peer.UserID == userID {
peers = append(peers, peer) peers = append(peers, peer)
@@ -576,7 +608,7 @@ func (a *Account) getPeerDNSLabels() lookupMap {
} }
func (a *Account) Copy() *Account { func (a *Account) Copy() *Account {
peers := map[string]*Peer{} peers := map[string]*nbpeer.Peer{}
for id, peer := range a.Peers { for id, peer := range a.Peers {
peers[id] = peer.Copy() peers[id] = peer.Copy()
} }
@@ -653,7 +685,7 @@ func (a *Account) GetGroupAll() (*Group, error) {
} }
// GetPeer looks up a Peer by ID // GetPeer looks up a Peer by ID
func (a *Account) GetPeer(peerID string) *Peer { func (a *Account) GetPeer(peerID string) *nbpeer.Peer {
return a.Peers[peerID] return a.Peers[peerID]
} }
@@ -816,9 +848,13 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
goCacheClient := gocache.New(CacheExpirationMax, 30*time.Minute) goCacheClient := gocache.New(CacheExpirationMax, 30*time.Minute)
goCacheStore := cacheStore.NewGoCache(goCacheClient) goCacheStore := cacheStore.NewGoCache(goCacheClient)
am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](goCacheStore)) am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](goCacheStore))
// TODO: what is max expiration time? Should be quite long
am.externalCacheManager = cache.New[*idp.UserData](
cacheStore.NewGoCache(goCacheClient),
)
if !isNil(am.idpManager) { if !isNil(am.idpManager) {
go func() { go func() {
err := am.warmupIDPCache() err := am.warmupIDPCache()
@@ -833,6 +869,10 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
return am, nil return am, nil
} }
func (am *DefaultAccountManager) GetExternalCacheManager() ExternalCacheManager {
return am.externalCacheManager
}
// UpdateAccountSettings updates Account settings. // UpdateAccountSettings updates Account settings.
// Only users with role UserRoleAdmin can update the account. // Only users with role UserRoleAdmin can update the account.
// User that performs the update has to belong to the account. // User that performs the update has to belong to the account.
@@ -855,12 +895,17 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
return nil, err return nil, err
} }
err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore)
if err != nil {
return nil, err
}
user, err := account.FindUser(userID) user, err := account.FindUser(userID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !user.IsAdmin() { if !user.HasAdminPower() {
return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account") return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account")
} }
@@ -873,11 +918,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
} else { } else {
am.checkAndSchedulePeerLoginExpiration(account) am.checkAndSchedulePeerLoginExpiration(account)
} }
am.storeEvent(userID, accountID, accountID, event, nil) am.StoreEvent(userID, accountID, accountID, event, nil)
} }
if oldSettings.PeerLoginExpiration != newSettings.PeerLoginExpiration { if oldSettings.PeerLoginExpiration != newSettings.PeerLoginExpiration {
am.storeEvent(userID, accountID, accountID, activity.AccountPeerLoginExpirationDurationUpdated, nil) am.StoreEvent(userID, accountID, accountID, activity.AccountPeerLoginExpirationDurationUpdated, nil)
am.checkAndSchedulePeerLoginExpiration(account) am.checkAndSchedulePeerLoginExpiration(account)
} }
@@ -934,14 +979,15 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er
_, err := am.Store.GetAccount(accountId) _, err := am.Store.GetAccount(accountId)
statusErr, _ := status.FromError(err) statusErr, _ := status.FromError(err)
if err == nil { switch {
case err == nil:
log.Warnf("an account with ID already exists, retrying...") log.Warnf("an account with ID already exists, retrying...")
continue continue
} else if statusErr.Type() == status.NotFound { case statusErr.Type() == status.NotFound:
newAccount := newAccountWithId(accountId, userID, domain) newAccount := newAccountWithId(accountId, userID, domain)
am.storeEvent(userID, newAccount.Id, accountId, activity.AccountCreated, nil) am.StoreEvent(userID, newAccount.Id, accountId, activity.AccountCreated, nil)
return newAccount, nil return newAccount, nil
} else { default:
return nil, err return nil, err
} }
} }
@@ -987,6 +1033,57 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
return nil return nil
} }
// DeleteAccount deletes an account and all its users from local store and from the remote IDP if the requester is an admin and account owner
func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
}
user, err := account.FindUser(userID)
if err != nil {
return err
}
if !user.HasAdminPower() {
return status.Errorf(status.PermissionDenied, "user is not allowed to delete account")
}
if user.Id != account.CreatedBy {
return status.Errorf(status.PermissionDenied, "user is not allowed to delete account. Only account owner can delete account")
}
for _, otherUser := range account.Users {
if otherUser.IsServiceUser {
continue
}
if otherUser.Id == userID {
continue
}
deleteUserErr := am.deleteRegularUser(account, userID, otherUser.Id)
if deleteUserErr != nil {
return deleteUserErr
}
}
err = am.deleteRegularUser(account, userID, userID)
if err != nil {
log.Errorf("failed deleting user %s. error: %s", userID, err)
return err
}
err = am.Store.DeleteAccount(account)
if err != nil {
log.Errorf("failed deleting account %s. error: %s", accountID, err)
return err
}
log.Debugf("account %s deleted", accountID)
return nil
}
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and // GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
// userID doesn't have an account associated with it, one account is created // userID doesn't have an account associated with it, one account is created
func (am *DefaultAccountManager) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) { func (am *DefaultAccountManager) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) {
@@ -1094,10 +1191,15 @@ func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountI
// lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil // lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil
func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Account) (*idp.UserData, error) { func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Account) (*idp.UserData, error) {
users := make(map[string]struct{}, len(account.Users)) users := make(map[string]struct{}, len(account.Users))
// ignore service users and users provisioned by integrations than are never logged in
for _, user := range account.Users { for _, user := range account.Users {
if !user.IsServiceUser { if user.IsServiceUser {
users[user.Id] = struct{}{} continue
} }
if user.Issued == UserIssuedIntegration {
continue
}
users[user.Id] = struct{}{}
} }
log.Debugf("looking up user %s of account %s in cache", userID, account.Id) log.Debugf("looking up user %s of account %s in cache", userID, account.Id)
userData, err := am.lookupCache(users, account.Id) userData, err := am.lookupCache(users, account.Id)
@@ -1167,16 +1269,20 @@ func (am *DefaultAccountManager) lookupCache(accountUsers map[string]struct{}, a
userDataMap[datum.ID] = struct{}{} userDataMap[datum.ID] = struct{}{}
} }
// check whether we need to reload the cache // the accountUsers ID list of non integration users from store, we check if cache has all of them
// the accountUsers ID list is the source of truth and all the users should be in the cache // as result of for loop knownUsersCount will have number of users are not presented in the cashed
reload := len(accountUsers) != len(data) knownUsersCount := len(accountUsers)
for user := range accountUsers { for user := range accountUsers {
if _, ok := userDataMap[user]; !ok { if _, ok := userDataMap[user]; ok {
reload = true knownUsersCount--
continue
} }
log.Debugf("cache doesn't know about %s user", user)
} }
if reload { // if we know users that are not yet in cache more likely cache is outdated
if knownUsersCount > 0 {
log.Debugf("cache doesn't know about %d users from store, reloading", knownUsersCount)
// reload cache once avoiding loops // reload cache once avoiding loops
data, err = am.refreshCache(accountID) data, err = am.refreshCache(accountID)
if err != nil { if err != nil {
@@ -1280,7 +1386,7 @@ func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims
return nil, err return nil, err
} }
am.storeEvent(claims.UserId, claims.UserId, account.Id, activity.UserJoined, nil) am.StoreEvent(claims.UserId, claims.UserId, account.Id, activity.UserJoined, nil)
return account, nil return account, nil
} }
@@ -1313,7 +1419,7 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e
return return
} }
log.Debugf("user %s of account %s redeemed invite", user.ID, account.Id) log.Debugf("user %s of account %s redeemed invite", user.ID, account.Id)
am.storeEvent(userID, userID, account.Id, activity.UserJoined, nil) am.StoreEvent(userID, userID, account.Id, activity.UserJoined, nil)
}() }()
} }
@@ -1463,7 +1569,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
am.updateAccountPeers(account) am.updateAccountPeers(account)
for _, g := range addNewGroups { for _, g := range addNewGroups {
if group := account.GetGroup(g); group != nil { if group := account.GetGroup(g); group != nil {
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser, am.StoreEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser,
map[string]any{ map[string]any{
"group": group.Name, "group": group.Name,
"group_id": group.ID, "group_id": group.ID,
@@ -1473,7 +1579,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
} }
for _, g := range removeOldGroups { for _, g := range removeOldGroups {
if group := account.GetGroup(g); group != nil { if group := account.GetGroup(g); group != nil {
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupRemovedFromUser, am.StoreEvent(user.Id, user.Id, account.Id, activity.GroupRemovedFromUser,
map[string]any{ map[string]any{
"group": group.Name, "group": group.Name,
"group_id": group.ID, "group_id": group.ID,
@@ -1571,9 +1677,15 @@ func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, er
return am.peersUpdateManager.GetAllConnectedPeers(), nil return am.peersUpdateManager.GetAllConnectedPeers(), nil
} }
// HasConnectedChannel returns true if peers has channel in update manager, otherwise false
func (am *DefaultAccountManager) HasConnectedChannel(peerID string) bool {
return am.peersUpdateManager.HasChannel(peerID)
}
var invalidDomainRegexp = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
func isDomainValid(domain string) bool { func isDomainValid(domain string) bool {
re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) return invalidDomainRegexp.MatchString(domain)
return re.Match([]byte(domain))
} }
// GetDNSDomain returns the configured dnsDomain // GetDNSDomain returns the configured dnsDomain
@@ -1619,12 +1731,12 @@ func newAccountWithId(accountID, userID, domain string) *Account {
log.Debugf("creating new account") log.Debugf("creating new account")
network := NewNetwork() network := NewNetwork()
peers := make(map[string]*Peer) peers := make(map[string]*nbpeer.Peer)
users := make(map[string]*User) users := make(map[string]*User)
routes := make(map[string]*route.Route) routes := make(map[string]*route.Route)
setupKeys := map[string]*SetupKey{} setupKeys := map[string]*SetupKey{}
nameServersGroups := make(map[string]*nbdns.NameServerGroup) nameServersGroups := make(map[string]*nbdns.NameServerGroup)
users[userID] = NewAdminUser(userID) users[userID] = NewOwnerUser(userID)
dnsSettings := DNSSettings{ dnsSettings := DNSSettings{
DisabledManagementGroups: make([]string, 0), DisabledManagementGroups: make([]string, 0),
} }
+13
View File
@@ -0,0 +1,13 @@
package account
type ExtraSettings struct {
// PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator
PeerApprovalEnabled bool
}
// Copy copies the ExtraSettings struct
func (e *ExtraSettings) Copy() *ExtraSettings {
return &ExtraSettings{
PeerApprovalEnabled: e.PeerApprovalEnabled,
}
}
+257 -71
View File
@@ -15,6 +15,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -25,10 +26,11 @@ import (
) )
func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) {
peer := &Peer{ t.Helper()
peer := &nbpeer.Peer{
Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=", Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=",
Name: "test-host@netbird.io", Name: "test-host@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host@netbird.io", Hostname: "test-host@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@@ -52,6 +54,7 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac
} }
func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy string, domain string, expectedUsers []string) { func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy string, domain string, expectedUsers []string) {
t.Helper()
if len(account.Peers) != 0 { if len(account.Peers) != 0 {
t.Errorf("expected account to have len(Peers) = %v, got %v", 0, len(account.Peers)) t.Errorf("expected account to have len(Peers) = %v, got %v", 0, len(account.Peers))
} }
@@ -108,13 +111,14 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy
func TestAccount_GetPeerNetworkMap(t *testing.T) { func TestAccount_GetPeerNetworkMap(t *testing.T) {
peerID1 := "peer-1" peerID1 := "peer-1"
peerID2 := "peer-2" peerID2 := "peer-2"
// peerID3 := "peer-3"
tt := []struct { tt := []struct {
name string name string
accountSettings Settings accountSettings Settings
peerID string peerID string
expectedPeers []string expectedPeers []string
expectedOfflinePeers []string expectedOfflinePeers []string
peers map[string]*Peer peers map[string]*nbpeer.Peer
}{ }{
{ {
name: "Should return ALL peers when global peer login expiration disabled", name: "Should return ALL peers when global peer login expiration disabled",
@@ -122,14 +126,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
peerID: peerID1, peerID: peerID1,
expectedPeers: []string{peerID2}, expectedPeers: []string{peerID2},
expectedOfflinePeers: []string{}, expectedOfflinePeers: []string{},
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: peerID1, ID: peerID1,
Key: "peer-1-key", Key: "peer-1-key",
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID1, Name: peerID1,
DNSLabel: peerID1, DNSLabel: peerID1,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: true, LoginExpired: true,
@@ -143,7 +147,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID2, Name: peerID2,
DNSLabel: peerID2, DNSLabel: peerID2,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: false, LoginExpired: false,
@@ -160,14 +164,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
peerID: peerID1, peerID: peerID1,
expectedPeers: []string{}, expectedPeers: []string{},
expectedOfflinePeers: []string{peerID2}, expectedOfflinePeers: []string{peerID2},
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: peerID1, ID: peerID1,
Key: "peer-1-key", Key: "peer-1-key",
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID1, Name: peerID1,
DNSLabel: peerID1, DNSLabel: peerID1,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: true, LoginExpired: true,
@@ -182,7 +186,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID2, Name: peerID2,
DNSLabel: peerID2, DNSLabel: peerID2,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: true, LoginExpired: true,
@@ -193,6 +197,159 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
}, },
}, },
}, },
// {
// name: "Should return only peers that are approved when peer approval is enabled",
// accountSettings: Settings{PeerApprovalEnabled: true},
// peerID: peerID1,
// expectedPeers: []string{peerID3},
// expectedOfflinePeers: []string{},
// peers: map[string]*Peer{
// "peer-1": {
// ID: peerID1,
// Key: "peer-1-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID1,
// DNSLabel: peerID1,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: true,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// "peer-2": {
// ID: peerID2,
// Key: "peer-2-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID2,
// DNSLabel: peerID2,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: false,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// "peer-3": {
// ID: peerID3,
// Key: "peer-3-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID3,
// DNSLabel: peerID3,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: true,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// },
// },
// {
// name: "Should return all peers when peer approval is disabled",
// accountSettings: Settings{PeerApprovalEnabled: false},
// peerID: peerID1,
// expectedPeers: []string{peerID2, peerID3},
// expectedOfflinePeers: []string{},
// peers: map[string]*Peer{
// "peer-1": {
// ID: peerID1,
// Key: "peer-1-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID1,
// DNSLabel: peerID1,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: true,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// "peer-2": {
// ID: peerID2,
// Key: "peer-2-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID2,
// DNSLabel: peerID2,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: false,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// "peer-3": {
// ID: peerID3,
// Key: "peer-3-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID3,
// DNSLabel: peerID3,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: true,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// },
// },
// {
// name: "Should return no peers when peer approval is enabled and the requesting peer is not approved",
// accountSettings: Settings{PeerApprovalEnabled: true},
// peerID: peerID1,
// expectedPeers: []string{},
// expectedOfflinePeers: []string{},
// peers: map[string]*Peer{
// "peer-1": {
// ID: peerID1,
// Key: "peer-1-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID1,
// DNSLabel: peerID1,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: false,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// "peer-2": {
// ID: peerID2,
// Key: "peer-2-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID2,
// DNSLabel: peerID2,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: true,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// "peer-3": {
// ID: peerID3,
// Key: "peer-3-key",
// IP: net.IP{100, 64, 0, 1},
// Name: peerID3,
// DNSLabel: peerID3,
// Status: &PeerStatus{
// LastSeen: time.Now().UTC(),
// Connected: false,
// Approved: true,
// },
// UserID: userID,
// LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30),
// },
// },
// },
} }
netIP := net.IP{100, 64, 0, 0} netIP := net.IP{100, 64, 0, 0}
@@ -207,6 +364,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
for _, testCase := range tt { for _, testCase := range tt {
account := newAccountWithId("account-1", userID, "netbird.io") account := newAccountWithId("account-1", userID, "netbird.io")
account.UpdateSettings(&testCase.accountSettings)
account.Network = network account.Network = network
account.Peers = testCase.peers account.Peers = testCase.peers
for _, peer := range account.Peers { for _, peer := range account.Peers {
@@ -251,7 +409,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
} }
if account != nil && account.Users[userID] == nil { if account != nil && account.Users[userID] == nil {
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userID, account.Id) t.Fatalf("expected to create an account for a user %s but no user was found after creation under the account %s", userID, account.Id)
return return
} }
@@ -304,7 +462,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
inputInitUserParams: defaultInitAccount, inputInitUserParams: defaultInitAccount,
testingFunc: require.NotEqual, testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match", expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin, expectedUserRole: UserRoleOwner,
expectedDomainCategory: "", expectedDomainCategory: "",
expectedDomain: publicDomain, expectedDomain: publicDomain,
expectedPrimaryDomainStatus: false, expectedPrimaryDomainStatus: false,
@@ -326,7 +484,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
inputInitUserParams: initUnknown, inputInitUserParams: initUnknown,
testingFunc: require.NotEqual, testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match", expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin, expectedUserRole: UserRoleOwner,
expectedDomain: unknownDomain, expectedDomain: unknownDomain,
expectedDomainCategory: "", expectedDomainCategory: "",
expectedPrimaryDomainStatus: false, expectedPrimaryDomainStatus: false,
@@ -344,7 +502,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
inputInitUserParams: defaultInitAccount, inputInitUserParams: defaultInitAccount,
testingFunc: require.NotEqual, testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match", expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin, expectedUserRole: UserRoleOwner,
expectedDomain: privateDomain, expectedDomain: privateDomain,
expectedDomainCategory: PrivateCategory, expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true, expectedPrimaryDomainStatus: true,
@@ -385,7 +543,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
inputInitUserParams: defaultInitAccount, inputInitUserParams: defaultInitAccount,
testingFunc: require.Equal, testingFunc: require.Equal,
expectedMSG: "account IDs should match", expectedMSG: "account IDs should match",
expectedUserRole: UserRoleAdmin, expectedUserRole: UserRoleOwner,
expectedDomain: defaultInitAccount.Domain, expectedDomain: defaultInitAccount.Domain,
expectedDomainCategory: PrivateCategory, expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true, expectedPrimaryDomainStatus: true,
@@ -404,7 +562,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
inputInitUserParams: defaultInitAccount, inputInitUserParams: defaultInitAccount,
testingFunc: require.Equal, testingFunc: require.Equal,
expectedMSG: "account IDs should match", expectedMSG: "account IDs should match",
expectedUserRole: UserRoleAdmin, expectedUserRole: UserRoleOwner,
expectedDomain: defaultInitAccount.Domain, expectedDomain: defaultInitAccount.Domain,
expectedDomainCategory: PrivateCategory, expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true, expectedPrimaryDomainStatus: true,
@@ -422,7 +580,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
inputInitUserParams: defaultInitAccount, inputInitUserParams: defaultInitAccount,
testingFunc: require.NotEqual, testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match", expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin, expectedUserRole: UserRoleOwner,
expectedDomain: "", expectedDomain: "",
expectedDomainCategory: "", expectedDomainCategory: "",
expectedPrimaryDomainStatus: false, expectedPrimaryDomainStatus: false,
@@ -626,7 +784,7 @@ func TestAccountManager_PrivateAccount(t *testing.T) {
} }
if account != nil && account.Users[userId] == nil { if account != nil && account.Users[userId] == nil {
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userId, account.Id) t.Fatalf("expected to create an account for a user %s but no user was found after creation under the account %s", userId, account.Id)
} }
} }
@@ -744,6 +902,31 @@ func TestAccountManager_GetAccount(t *testing.T) {
} }
} }
func TestAccountManager_DeleteAccount(t *testing.T) {
manager, err := createManager(t)
if err != nil {
t.Fatal(err)
return
}
expectedId := "test_account"
userId := "account_creator"
account, err := createAccount(manager, expectedId, userId, "")
if err != nil {
t.Fatal(err)
}
err = manager.DeleteAccount(account.Id, userId)
if err != nil {
t.Fatal(err)
}
getAccount, err := manager.Store.GetAccount(account.Id)
if err == nil {
t.Fatal(fmt.Errorf("expected to get an error when trying to get deleted account, got %v", getAccount))
}
}
func TestAccountManager_AddPeer(t *testing.T) { func TestAccountManager_AddPeer(t *testing.T) {
manager, err := createManager(t) manager, err := createManager(t)
if err != nil { if err != nil {
@@ -778,9 +961,9 @@ func TestAccountManager_AddPeer(t *testing.T) {
expectedPeerKey := key.PublicKey().String() expectedPeerKey := key.PublicKey().String()
expectedSetupKey := setupKey.Key expectedSetupKey := setupKey.Key
peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: expectedPeerKey, Key: expectedPeerKey,
Meta: PeerSystemMeta{Hostname: expectedPeerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@@ -846,9 +1029,9 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
expectedPeerKey := key.PublicKey().String() expectedPeerKey := key.PublicKey().String()
expectedUserID := userID expectedUserID := userID
peer, _, err := manager.AddPeer("", userID, &Peer{ peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{
Key: expectedPeerKey, Key: expectedPeerKey,
Meta: PeerSystemMeta{Hostname: expectedPeerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v, account users: %v", err, account.CreatedBy) t.Errorf("expecting peer to be added, got failure %v, account users: %v", err, account.CreatedBy)
@@ -913,7 +1096,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
return return
} }
getPeer := func() *Peer { getPeer := func() *nbpeer.Peer {
key, err := wgtypes.GeneratePrivateKey() key, err := wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -921,9 +1104,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
} }
expectedPeerKey := key.PublicKey().String() expectedPeerKey := key.PublicKey().String()
peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: expectedPeerKey, Key: expectedPeerKey,
Meta: PeerSystemMeta{Hostname: expectedPeerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}) })
if err != nil { if err != nil {
t.Fatalf("expecting peer1 to be added, got failure %v", err) t.Fatalf("expecting peer1 to be added, got failure %v", err)
@@ -1095,9 +1278,9 @@ func TestAccountManager_DeletePeer(t *testing.T) {
peerKey := key.PublicKey().String() peerKey := key.PublicKey().String()
peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey, Key: peerKey,
Meta: PeerSystemMeta{Hostname: peerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: peerKey},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@@ -1131,6 +1314,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
} }
func getEvent(t *testing.T, accountID string, manager AccountManager, eventType activity.Activity) *activity.Event { func getEvent(t *testing.T, accountID string, manager AccountManager, eventType activity.Activity) *activity.Event {
t.Helper()
for { for {
select { select {
case <-time.After(time.Second): case <-time.After(time.Second):
@@ -1155,7 +1339,7 @@ func TestGetUsersFromAccount(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
users := map[string]*User{"1": {Id: "1", Role: "admin"}, "2": {Id: "2", Role: "user"}, "3": {Id: "3", Role: "user"}} users := map[string]*User{"1": {Id: "1", Role: UserRoleOwner}, "2": {Id: "2", Role: "user"}, "3": {Id: "3", Role: "user"}}
accountId := "test_account_id" accountId := "test_account_id"
account, err := createAccount(manager, accountId, users["1"].Id, "") account, err := createAccount(manager, accountId, users["1"].Id, "")
@@ -1235,8 +1419,8 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer-1": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
}, },
Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}}, Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
Routes: map[string]*route.Route{ Routes: map[string]*route.Route{
@@ -1279,7 +1463,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
}, },
} }
routes := account.getRoutesToSync("peer-2", []*Peer{{Key: "peer-1"}, {Key: "peer-3"}}) routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
assert.Len(t, routes, 2) assert.Len(t, routes, 2)
routeIDs := make(map[string]struct{}, 2) routeIDs := make(map[string]struct{}, 2)
@@ -1289,7 +1473,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
assert.Contains(t, routeIDs, "route-2") assert.Contains(t, routeIDs, "route-2")
assert.Contains(t, routeIDs, "route-3") assert.Contains(t, routeIDs, "route-3")
emptyRoutes := account.getRoutesToSync("peer-3", []*Peer{{Key: "peer-1"}, {Key: "peer-2"}}) emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
assert.Len(t, emptyRoutes, 0) assert.Len(t, emptyRoutes, 0)
} }
@@ -1310,10 +1494,10 @@ func TestAccount_Copy(t *testing.T) {
Network: &Network{ Network: &Network{
Identifier: "net1", Identifier: "net1",
}, },
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": { "peer1": {
Key: "key1", Key: "key1",
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now(), LastSeen: time.Now(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@@ -1385,7 +1569,7 @@ func TestAccount_Copy(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
account.Peers["peer1"].Status.Connected = false // we change original object to confirm that copy wont change account.Peers["peer1"].Status.Connected = false // we change original object to confirm that copy won't change
accCopyBytes, err := json.Marshal(accountCopy) accCopyBytes, err := json.Marshal(accountCopy)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -1440,9 +1624,9 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
key, err := wgtypes.GenerateKey() key, err := wgtypes.GenerateKey()
require.NoError(t, err, "unable to generate WireGuard key") require.NoError(t, err, "unable to generate WireGuard key")
peer, _, err := manager.AddPeer("", userID, &Peer{ peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{
Key: key.PublicKey().String(), Key: key.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
}) })
require.NoError(t, err, "unable to add peer") require.NoError(t, err, "unable to add peer")
@@ -1489,9 +1673,9 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
key, err := wgtypes.GenerateKey() key, err := wgtypes.GenerateKey()
require.NoError(t, err, "unable to generate WireGuard key") require.NoError(t, err, "unable to generate WireGuard key")
_, _, err = manager.AddPeer("", userID, &Peer{ _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{
Key: key.PublicKey().String(), Key: key.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
}) })
require.NoError(t, err, "unable to add peer") require.NoError(t, err, "unable to add peer")
@@ -1530,9 +1714,9 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
key, err := wgtypes.GenerateKey() key, err := wgtypes.GenerateKey()
require.NoError(t, err, "unable to generate WireGuard key") require.NoError(t, err, "unable to generate WireGuard key")
_, _, err = manager.AddPeer("", userID, &Peer{ _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{
Key: key.PublicKey().String(), Key: key.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
}) })
require.NoError(t, err, "unable to add peer") require.NoError(t, err, "unable to add peer")
@@ -1611,13 +1795,13 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
func TestAccount_GetExpiredPeers(t *testing.T) { func TestAccount_GetExpiredPeers(t *testing.T) {
type test struct { type test struct {
name string name string
peers map[string]*Peer peers map[string]*nbpeer.Peer
expectedPeers map[string]struct{} expectedPeers map[string]struct{}
} }
testCases := []test{ testCases := []test{
{ {
name: "Peers with login expiration disabled, no expired peers", name: "Peers with login expiration disabled, no expired peers",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
}, },
@@ -1629,11 +1813,11 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
}, },
{ {
name: "Two peers expired", name: "Two peers expired",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: "peer-1", ID: "peer-1",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@@ -1644,7 +1828,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
"peer-2": { "peer-2": {
ID: "peer-2", ID: "peer-2",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@@ -1656,7 +1840,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
"peer-3": { "peer-3": {
ID: "peer-3", ID: "peer-3",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@@ -1696,19 +1880,19 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
func TestAccount_GetPeersWithExpiration(t *testing.T) { func TestAccount_GetPeersWithExpiration(t *testing.T) {
type test struct { type test struct {
name string name string
peers map[string]*Peer peers map[string]*nbpeer.Peer
expectedPeers map[string]struct{} expectedPeers map[string]struct{}
} }
testCases := []test{ testCases := []test{
{ {
name: "No account peers, no peers with expiration", name: "No account peers, no peers with expiration",
peers: map[string]*Peer{}, peers: map[string]*nbpeer.Peer{},
expectedPeers: map[string]struct{}{}, expectedPeers: map[string]struct{}{},
}, },
{ {
name: "Peers with login expiration disabled, no peers with expiration", name: "Peers with login expiration disabled, no peers with expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
UserID: userID, UserID: userID,
@@ -1722,7 +1906,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) {
}, },
{ {
name: "Peers with login expiration enabled, return peers with expiration", name: "Peers with login expiration enabled, return peers with expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: "peer-1", ID: "peer-1",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
@@ -1765,7 +1949,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) {
func TestAccount_GetNextPeerExpiration(t *testing.T) { func TestAccount_GetNextPeerExpiration(t *testing.T) {
type test struct { type test struct {
name string name string
peers map[string]*Peer peers map[string]*nbpeer.Peer
expiration time.Duration expiration time.Duration
expirationEnabled bool expirationEnabled bool
expectedNextRun bool expectedNextRun bool
@@ -1776,7 +1960,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
testCases := []test{ testCases := []test{
{ {
name: "No peers, no expiration", name: "No peers, no expiration",
peers: map[string]*Peer{}, peers: map[string]*nbpeer.Peer{},
expiration: time.Second, expiration: time.Second,
expirationEnabled: false, expirationEnabled: false,
expectedNextRun: false, expectedNextRun: false,
@@ -1784,16 +1968,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "No connected peers, no expiration", name: "No connected peers, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: false, Connected: false,
}, },
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
}, },
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
@@ -1807,16 +1991,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "Connected peers with disabled expiration, no expiration", name: "Connected peers with disabled expiration, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
}, },
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
}, },
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
@@ -1830,9 +2014,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "Expired peers, no expiration", name: "Expired peers, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: true, LoginExpired: true,
}, },
@@ -1840,7 +2024,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: true, LoginExpired: true,
}, },
@@ -1855,9 +2039,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "To be expired peer, return expiration", name: "To be expired peer, return expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
}, },
@@ -1866,7 +2050,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: true, LoginExpired: true,
}, },
@@ -1881,9 +2065,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "Peers added with setup keys, no expiration", name: "Peers added with setup keys, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
}, },
@@ -1891,7 +2075,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
SetupKey: "key", SetupKey: "key",
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
}, },
@@ -1926,7 +2110,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
func TestAccount_SetJWTGroups(t *testing.T) { func TestAccount_SetJWTGroups(t *testing.T) {
// create a new account // create a new account
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
@@ -1974,7 +2158,7 @@ func TestAccount_SetJWTGroups(t *testing.T) {
func TestAccount_UserGroupsAddToPeers(t *testing.T) { func TestAccount_UserGroupsAddToPeers(t *testing.T) {
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
@@ -2010,7 +2194,7 @@ func TestAccount_UserGroupsAddToPeers(t *testing.T) {
func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) { func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
@@ -2038,15 +2222,17 @@ func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
} }
func createManager(t *testing.T) (*DefaultAccountManager, error) { func createManager(t *testing.T) (*DefaultAccountManager, error) {
t.Helper()
store, err := createStore(t) store, err := createStore(t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.cloud", eventStore, false) return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, false)
} }
func createStore(t *testing.T) (Store, error) { func createStore(t *testing.T) (Store, error) {
t.Helper()
dataDir := t.TempDir() dataDir := t.TempDir()
store, err := NewStoreFromJson(dataDir, nil) store, err := NewStoreFromJson(dataDir, nil)
if err != nil { if err != nil {
+24
View File
@@ -114,6 +114,22 @@ const (
PeerLoginExpired PeerLoginExpired
// DashboardLogin indicates that the user logged in to the dashboard // DashboardLogin indicates that the user logged in to the dashboard
DashboardLogin DashboardLogin
// IntegrationCreated indicates that the user created an integration
IntegrationCreated
// IntegrationUpdated indicates that the user updated an integration
IntegrationUpdated
// IntegrationDeleted indicates that the user deleted an integration
IntegrationDeleted
// AccountPeerApprovalEnabled indicates that the user enabled peer approval for the account
AccountPeerApprovalEnabled
// AccountPeerApprovalDisabled indicates that the user disabled peer approval for the account
AccountPeerApprovalDisabled
// PeerApproved indicates that the peer has been approved
PeerApproved
// PeerApprovalRevoked indicates that the peer approval has been revoked
PeerApprovalRevoked
// TransferredOwnerRole indicates that the user transferred the owner role of the account
TransferredOwnerRole
) )
var activityMap = map[Activity]Code{ var activityMap = map[Activity]Code{
@@ -169,6 +185,14 @@ var activityMap = map[Activity]Code{
UserLoggedInPeer: {"User logged in peer", "user.peer.login"}, UserLoggedInPeer: {"User logged in peer", "user.peer.login"},
PeerLoginExpired: {"Peer login expired", "peer.login.expire"}, PeerLoginExpired: {"Peer login expired", "peer.login.expire"},
DashboardLogin: {"Dashboard login", "dashboard.login"}, DashboardLogin: {"Dashboard login", "dashboard.login"},
IntegrationCreated: {"Integration created", "integration.create"},
IntegrationUpdated: {"Integration updated", "integration.update"},
IntegrationDeleted: {"Integration deleted", "integration.delete"},
AccountPeerApprovalEnabled: {"Account peer approval enabled", "account.setting.peer.approval.enable"},
AccountPeerApprovalDisabled: {"Account peer approval disabled", "account.setting.peer.approval.disable"},
PeerApproved: {"Peer approved", "peer.approve"},
PeerApprovalRevoked: {"Peer approval revoked", "peer.approval.revoke"},
TransferredOwnerRole: {"Transferred owner role", "transferred.owner.role"},
} }
// StringCode returns a string code of the activity // StringCode returns a string code of the activity
+31 -7
View File
@@ -14,7 +14,7 @@ import (
) )
const ( const (
//eventSinkDB is the default name of the events database // eventSinkDB is the default name of the events database
eventSinkDB = "events.db" eventSinkDB = "events.db"
createTableQuery = "CREATE TABLE IF NOT EXISTS events " + createTableQuery = "CREATE TABLE IF NOT EXISTS events " +
"(id INTEGER PRIMARY KEY AUTOINCREMENT, " + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " +
@@ -28,22 +28,46 @@ const (
creatTableDeletedUsersQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL, name TEXT);` creatTableDeletedUsersQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL, name TEXT);`
selectDescQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta selectDescQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta
FROM events FROM events
LEFT JOIN deleted_users i ON events.initiator_id = i.id LEFT JOIN (
LEFT JOIN deleted_users t ON events.target_id = t.id SELECT id, MAX(name) as name, MAX(email) as email
FROM deleted_users
GROUP BY id
) i ON events.initiator_id = i.id
LEFT JOIN (
SELECT id, MAX(name) as name, MAX(email) as email
FROM deleted_users
GROUP BY id
) t ON events.target_id = t.id
WHERE account_id = ? WHERE account_id = ?
ORDER BY timestamp DESC LIMIT ? OFFSET ?;` ORDER BY timestamp DESC LIMIT ? OFFSET ?;`
selectAscQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta selectAscQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta
FROM events FROM events
LEFT JOIN deleted_users i ON events.initiator_id = i.id LEFT JOIN (
LEFT JOIN deleted_users t ON events.target_id = t.id SELECT id, MAX(name) as name, MAX(email) as email
FROM deleted_users
GROUP BY id
) i ON events.initiator_id = i.id
LEFT JOIN (
SELECT id, MAX(name) as name, MAX(email) as email
FROM deleted_users
GROUP BY id
) t ON events.target_id = t.id
WHERE account_id = ? WHERE account_id = ?
ORDER BY timestamp ASC LIMIT ? OFFSET ?;` ORDER BY timestamp ASC LIMIT ? OFFSET ?;`
insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " + insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " +
"VALUES(?, ?, ?, ?, ?, ?)" "VALUES(?, ?, ?, ?, ?, ?)"
/*
TODO:
The insert should avoid duplicated IDs in the table. So the query should be changes to something like:
`INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?) ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, name = EXCLUDED.name;`
For this to work we have to set the id column as primary key. But this is not possible because the id column is not unique
and some selfhosted deployments might have duplicates already so we need to clean the table first.
*/
insertDeleteUserQuery = `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?)` insertDeleteUserQuery = `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?)`
fallbackName = "unknown" fallbackName = "unknown"
+8 -7
View File
@@ -10,6 +10,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
) )
@@ -47,8 +48,8 @@ func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string)
return nil, err return nil, err
} }
if !user.IsAdmin() { if !user.HasAdminPower() {
return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view DNS settings") return nil, status.Errorf(status.PermissionDenied, "only users with admin power are allowed to view DNS settings")
} }
dnsSettings := account.DNSSettings.Copy() dnsSettings := account.DNSSettings.Copy()
return &dnsSettings, nil return &dnsSettings, nil
@@ -69,8 +70,8 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
return err return err
} }
if !user.IsAdmin() { if !user.HasAdminPower() {
return status.Errorf(status.PermissionDenied, "only admins are allowed to update DNS settings") return status.Errorf(status.PermissionDenied, "only users with admin power are allowed to update DNS settings")
} }
if dnsSettingsToSave == nil { if dnsSettingsToSave == nil {
@@ -96,14 +97,14 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
for _, id := range addedGroups { for _, id := range addedGroups {
group := account.GetGroup(id) group := account.GetGroup(id)
meta := map[string]any{"group": group.Name, "group_id": group.ID} meta := map[string]any{"group": group.Name, "group_id": group.ID}
am.storeEvent(userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta) am.StoreEvent(userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta)
} }
removedGroups := difference(oldSettings.DisabledManagementGroups, dnsSettingsToSave.DisabledManagementGroups) removedGroups := difference(oldSettings.DisabledManagementGroups, dnsSettingsToSave.DisabledManagementGroups)
for _, id := range removedGroups { for _, id := range removedGroups {
group := account.GetGroup(id) group := account.GetGroup(id)
meta := map[string]any{"group": group.Name, "group_id": group.ID} meta := map[string]any{"group": group.Name, "group_id": group.ID}
am.storeEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta) am.StoreEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
} }
am.updateAccountPeers(account) am.updateAccountPeers(account)
@@ -200,7 +201,7 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
} }
// peerIsNameserver returns true if the peer is a nameserver for a nsGroup // peerIsNameserver returns true if the peer is a nameserver for a nsGroup
func peerIsNameserver(peer *Peer, nsGroup *nbdns.NameServerGroup) bool { func peerIsNameserver(peer *nbpeer.Peer, nsGroup *nbdns.NameServerGroup) bool {
for _, ns := range nsGroup.NameServers { for _, ns := range nsGroup.NameServers {
if peer.IP.Equal(ns.IP.AsSlice()) { if peer.IP.Equal(ns.IP.AsSlice()) {
return true return true
+9 -5
View File
@@ -8,6 +8,7 @@ import (
"github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
) )
@@ -186,15 +187,17 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
} }
func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
t.Helper()
store, err := createDNSStore(t) store, err := createDNSStore(t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.test", eventStore, false) return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, false)
} }
func createDNSStore(t *testing.T) (Store, error) { func createDNSStore(t *testing.T) (Store, error) {
t.Helper()
dataDir := t.TempDir() dataDir := t.TempDir()
store, err := NewStoreFromJson(dataDir, nil) store, err := NewStoreFromJson(dataDir, nil)
if err != nil { if err != nil {
@@ -205,10 +208,11 @@ func createDNSStore(t *testing.T) (Store, error) {
} }
func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) {
peer1 := &Peer{ t.Helper()
peer1 := &nbpeer.Peer{
Key: dnsPeer1Key, Key: dnsPeer1Key,
Name: "test-host1@netbird.io", Name: "test-host1@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host1@netbird.io", Hostname: "test-host1@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@@ -220,10 +224,10 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
}, },
DNSLabel: dnsPeer1Key, DNSLabel: dnsPeer1Key,
} }
peer2 := &Peer{ peer2 := &nbpeer.Peer{
Key: dnsPeer2Key, Key: dnsPeer2Key,
Name: "test-host2@netbird.io", Name: "test-host2@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host2@netbird.io", Hostname: "test-host2@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
+3 -2
View File
@@ -7,6 +7,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
const ( const (
@@ -72,7 +73,7 @@ func (e *EphemeralManager) Stop() {
// OnPeerConnected remove the peer from the linked list of ephemeral peers. Because it has been called when the peer // OnPeerConnected remove the peer from the linked list of ephemeral peers. Because it has been called when the peer
// is active the manager will not delete it while it is active. // is active the manager will not delete it while it is active.
func (e *EphemeralManager) OnPeerConnected(peer *Peer) { func (e *EphemeralManager) OnPeerConnected(peer *nbpeer.Peer) {
if !peer.Ephemeral { if !peer.Ephemeral {
return return
} }
@@ -93,7 +94,7 @@ func (e *EphemeralManager) OnPeerConnected(peer *Peer) {
// OnPeerDisconnected add the peer to the linked list of ephemeral peers. Because of the peer // OnPeerDisconnected add the peer to the linked list of ephemeral peers. Because of the peer
// is inactive it will be deleted after the ephemeralLifeTime period. // is inactive it will be deleted after the ephemeralLifeTime period.
func (e *EphemeralManager) OnPeerDisconnected(peer *Peer) { func (e *EphemeralManager) OnPeerDisconnected(peer *nbpeer.Peer) {
if !peer.Ephemeral { if !peer.Ephemeral {
return return
} }
+4 -2
View File
@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"testing" "testing"
"time" "time"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
type MockStore struct { type MockStore struct {
@@ -124,7 +126,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int)
for i := 0; i < numberOfPeers; i++ { for i := 0; i < numberOfPeers; i++ {
peerId := fmt.Sprintf("peer_%d", i) peerId := fmt.Sprintf("peer_%d", i)
p := &Peer{ p := &nbpeer.Peer{
ID: peerId, ID: peerId,
Ephemeral: false, Ephemeral: false,
} }
@@ -133,7 +135,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int)
for i := 0; i < numberOfEphemeralPeers; i++ { for i := 0; i < numberOfEphemeralPeers; i++ {
peerId := fmt.Sprintf("ephemeral_peer_%d", i) peerId := fmt.Sprintf("ephemeral_peer_%d", i)
p := &Peer{ p := &nbpeer.Peer{
ID: peerId, ID: peerId,
Ephemeral: true, Ephemeral: true,
} }
+1 -1
View File
@@ -36,7 +36,7 @@ func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activit
return filtered, nil return filtered, nil
} }
func (am *DefaultAccountManager) storeEvent(initiatorID, targetID, accountID string, activityID activity.Activity, func (am *DefaultAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity,
meta map[string]any) { meta map[string]any) {
go func() { go func() {
+1
View File
@@ -11,6 +11,7 @@ import (
func generateAndStoreEvents(t *testing.T, manager *DefaultAccountManager, typ activity.Activity, initiatorID, targetID, func generateAndStoreEvents(t *testing.T, manager *DefaultAccountManager, typ activity.Activity, initiatorID, targetID,
accountID string, count int) { accountID string, count int) {
t.Helper()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
_, err := manager.eventStore.Save(&activity.Event{ _, err := manager.eventStore.Save(&activity.Event{
Timestamp: time.Now().UTC(), Timestamp: time.Now().UTC(),
+38 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
@@ -204,7 +205,7 @@ func restore(file string) (*FileStore, error) {
// Set the Peer.ID to the newly generated value. // Set the Peer.ID to the newly generated value.
// Replace all the mentions of Peer.Key as ID (groups and routes). // Replace all the mentions of Peer.Key as ID (groups and routes).
// Swap Peer.Key with Peer.ID in the Account.Peers map. // Swap Peer.Key with Peer.ID in the Account.Peers map.
migrationPeers := make(map[string]*Peer) // key to Peer migrationPeers := make(map[string]*nbpeer.Peer) // key to Peer
for key, peer := range account.Peers { for key, peer := range account.Peers {
// set LastLogin for the peers that were onboarded before the peer login expiration feature // set LastLogin for the peers that were onboarded before the peer login expiration feature
if peer.LastLogin.IsZero() { if peer.LastLogin.IsZero() {
@@ -351,6 +352,41 @@ func (s *FileStore) SaveAccount(account *Account) error {
return s.persist(s.storeFile) return s.persist(s.storeFile)
} }
func (s *FileStore) DeleteAccount(account *Account) error {
s.mux.Lock()
defer s.mux.Unlock()
if account.Id == "" {
return status.Errorf(status.InvalidArgument, "account id should not be empty")
}
for keyID := range account.SetupKeys {
delete(s.SetupKeyID2AccountID, strings.ToUpper(keyID))
}
// enforce peer to account index and delete peer to route indexes for rebuild
for _, peer := range account.Peers {
delete(s.PeerKeyID2AccountID, peer.Key)
delete(s.PeerID2AccountID, peer.ID)
}
for _, user := range account.Users {
for _, pat := range user.PATs {
delete(s.TokenID2UserID, pat.ID)
delete(s.HashedPAT2TokenID, pat.HashedToken)
}
delete(s.UserID2AccountID, user.Id)
}
if account.DomainCategory == PrivateCategory && account.IsDomainPrimaryAccount {
delete(s.PrivateDomain2AccountID, account.Domain)
}
delete(s.Accounts, account.Id)
return s.persist(s.storeFile)
}
// DeleteHashedPAT2TokenIDIndex removes an entry from the indexing map HashedPAT2TokenID // DeleteHashedPAT2TokenIDIndex removes an entry from the indexing map HashedPAT2TokenID
func (s *FileStore) DeleteHashedPAT2TokenIDIndex(hashedToken string) error { func (s *FileStore) DeleteHashedPAT2TokenIDIndex(hashedToken string) error {
s.mux.Lock() s.mux.Lock()
@@ -571,7 +607,7 @@ func (s *FileStore) SaveInstallationID(ID string) error {
// SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things. // SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
// PeerStatus will be saved eventually when some other changes occur. // PeerStatus will be saved eventually when some other changes occur.
func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStatus) error { func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error {
s.mux.Lock() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()
+67 -11
View File
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/util" "github.com/netbirdio/netbird/util"
) )
@@ -35,7 +36,7 @@ func TestStalePeerIndices(t *testing.T) {
peerID := "some_peer" peerID := "some_peer"
peerKey := "some_peer_key" peerKey := "some_peer_key"
account.Peers[peerID] = &Peer{ account.Peers[peerID] = &nbpeer.Peer{
ID: peerID, ID: peerID,
Key: peerKey, Key: peerKey,
} }
@@ -89,13 +90,13 @@ func TestSaveAccount(t *testing.T) {
account := newAccountWithId("account_id", "testuser", "") account := newAccountWithId("account_id", "testuser", "")
setupKey := GenerateDefaultSetupKey() setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey account.SetupKeys[setupKey.Key] = setupKey
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
// SaveAccount should trigger persist // SaveAccount should trigger persist
@@ -121,17 +122,71 @@ func TestSaveAccount(t *testing.T) {
} }
} }
func TestDeleteAccount(t *testing.T) {
storeDir := t.TempDir()
storeFile := filepath.Join(storeDir, "store.json")
err := util.CopyFileContents("testdata/store.json", storeFile)
if err != nil {
t.Fatal(err)
}
store, err := NewFileStore(storeDir, nil)
if err != nil {
t.Fatal(err)
}
var account *Account
for _, a := range store.Accounts {
account = a
break
}
require.NotNil(t, account, "failed to restore a FileStore file and get at least one account")
err = store.DeleteAccount(account)
require.NoError(t, err, "failed to delete account, error: %v", err)
_, ok := store.Accounts[account.Id]
require.False(t, ok, "failed to delete account")
for id := range account.Users {
_, ok := store.UserID2AccountID[id]
assert.False(t, ok, "failed to delete UserID2AccountID index")
for _, pat := range account.Users[id].PATs {
_, ok := store.HashedPAT2TokenID[pat.HashedToken]
assert.False(t, ok, "failed to delete HashedPAT2TokenID index")
_, ok = store.TokenID2UserID[pat.ID]
assert.False(t, ok, "failed to delete TokenID2UserID index")
}
}
for _, p := range account.Peers {
_, ok := store.PeerKeyID2AccountID[p.Key]
assert.False(t, ok, "failed to delete PeerKeyID2AccountID index")
_, ok = store.PeerID2AccountID[p.ID]
assert.False(t, ok, "failed to delete PeerID2AccountID index")
}
for id := range account.SetupKeys {
_, ok := store.SetupKeyID2AccountID[id]
assert.False(t, ok, "failed to delete SetupKeyID2AccountID index")
}
_, ok = store.PrivateDomain2AccountID[account.Domain]
assert.False(t, ok, "failed to delete PrivateDomain2AccountID index")
}
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
store := newStore(t) store := newStore(t)
account := newAccountWithId("account_id", "testuser", "") account := newAccountWithId("account_id", "testuser", "")
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
account.Groups["all"] = &Group{ account.Groups["all"] = &Group{
ID: "all", ID: "all",
@@ -546,19 +601,19 @@ func TestFileStore_SavePeerStatus(t *testing.T) {
} }
// save status of non-existing peer // save status of non-existing peer
newStatus := PeerStatus{Connected: true, LastSeen: time.Now().UTC()} newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}
err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus) err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
assert.Error(t, err) assert.Error(t, err)
// save new status of existing peer // save new status of existing peer
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
ID: "testpeer", ID: "testpeer",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()},
} }
err = store.SaveAccount(account) err = store.SaveAccount(account)
@@ -580,6 +635,7 @@ func TestFileStore_SavePeerStatus(t *testing.T) {
} }
func newStore(t *testing.T) *FileStore { func newStore(t *testing.T) *FileStore {
t.Helper()
store, err := NewFileStore(t.TempDir(), nil) store, err := NewFileStore(t.TempDir(), nil)
if err != nil { if err != nil {
t.Errorf("failed creating a new store") t.Errorf("failed creating a new store")
+12 -6
View File
@@ -101,7 +101,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
removedPeers = difference(oldGroup.Peers, newGroup.Peers) removedPeers = difference(oldGroup.Peers, newGroup.Peers)
} else { } else {
addedPeers = append(addedPeers, newGroup.Peers...) addedPeers = append(addedPeers, newGroup.Peers...)
am.storeEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta()) am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
} }
for _, p := range addedPeers { for _, p := range addedPeers {
@@ -110,7 +110,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
log.Errorf("peer %s not found under account %s while saving group", p, accountID) log.Errorf("peer %s not found under account %s while saving group", p, accountID)
continue continue
} }
am.storeEvent(userID, peer.ID, accountID, activity.GroupAddedToPeer, am.StoreEvent(userID, peer.ID, accountID, activity.GroupAddedToPeer,
map[string]any{ map[string]any{
"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(), "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
"peer_fqdn": peer.FQDN(am.GetDNSDomain()), "peer_fqdn": peer.FQDN(am.GetDNSDomain()),
@@ -123,7 +123,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
log.Errorf("peer %s not found under account %s while saving group", p, accountID) log.Errorf("peer %s not found under account %s while saving group", p, accountID)
continue continue
} }
am.storeEvent(userID, peer.ID, accountID, activity.GroupRemovedFromPeer, am.StoreEvent(userID, peer.ID, accountID, activity.GroupRemovedFromPeer,
map[string]any{ map[string]any{
"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(), "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
"peer_fqdn": peer.FQDN(am.GetDNSDomain()), "peer_fqdn": peer.FQDN(am.GetDNSDomain()),
@@ -163,9 +163,15 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
return nil return nil
} }
// check integration link // disable a deleting integration group if the initiator is not an admin service user
if g.Issued == GroupIssuedIntegration { if g.Issued == GroupIssuedIntegration {
return &GroupLinkError{GroupIssuedIntegration, g.IntegrationReference.String()} executingUser := account.Users[userId]
if executingUser == nil {
return status.Errorf(status.NotFound, "user not found")
}
if executingUser.Role != UserRoleAdmin || !executingUser.IsServiceUser {
return status.Errorf(status.PermissionDenied, "only service users with admin power can delete integration group")
}
} }
// check route links // check route links
@@ -235,7 +241,7 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
return err return err
} }
am.storeEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta()) am.StoreEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
am.updateAccountPeers(account) am.updateAccountPeers(account)
+14 -3
View File
@@ -1,9 +1,11 @@
package server package server
import ( import (
"errors"
"testing" "testing"
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@@ -55,19 +57,28 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
{ {
"integration", "integration",
"grp-for-integration", "grp-for-integration",
"integration", "only service users with admin power can delete integration group",
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
err = am.DeleteGroup(account.Id, "", testCase.groupID) err = am.DeleteGroup(account.Id, groupAdminUserID, testCase.groupID)
if err == nil { if err == nil {
t.Errorf("delete %s group successfully", testCase.groupID) t.Errorf("delete %s group successfully", testCase.groupID)
return return
} }
gErr, ok := err.(*GroupLinkError) var sErr *status.Error
if errors.As(err, &sErr) {
if sErr.Message != testCase.expectedReason {
t.Errorf("invalid error case: %s, expected: %s", sErr.Message, testCase.expectedReason)
}
return
}
var gErr *GroupLinkError
ok := errors.As(err, &gErr)
if !ok { if !ok {
t.Error("invalid error type") t.Error("invalid error type")
return return
+8 -7
View File
@@ -17,6 +17,7 @@ import (
"github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/encryption"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
internalStatus "github.com/netbirdio/netbird/management/server/status" internalStatus "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
) )
@@ -196,7 +197,7 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
} }
} }
func (s *GRPCServer) cancelPeerRoutines(peer *Peer) { func (s *GRPCServer) cancelPeerRoutines(peer *nbpeer.Peer) {
s.peersUpdateManager.CloseChannel(peer.ID) s.peersUpdateManager.CloseChannel(peer.ID)
s.turnCredentialsManager.CancelRefresh(peer.ID) s.turnCredentialsManager.CancelRefresh(peer.ID)
_ = s.accountManager.MarkPeerConnected(peer.Key, false) _ = s.accountManager.MarkPeerConnected(peer.Key, false)
@@ -243,8 +244,8 @@ func mapError(err error) error {
return status.Errorf(codes.Internal, "failed handling request") return status.Errorf(codes.Internal, "failed handling request")
} }
func extractPeerMeta(loginReq *proto.LoginRequest) PeerSystemMeta { func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
return PeerSystemMeta{ return nbpeer.PeerSystemMeta{
Hostname: loginReq.GetMeta().GetHostname(), Hostname: loginReq.GetMeta().GetHostname(),
GoOS: loginReq.GetMeta().GetGoOS(), GoOS: loginReq.GetMeta().GetGoOS(),
Kernel: loginReq.GetMeta().GetKernel(), Kernel: loginReq.GetMeta().GetKernel(),
@@ -413,7 +414,7 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
} }
} }
func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig { func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig {
netmask, _ := network.Net.Mask.Size() netmask, _ := network.Net.Mask.Size()
fqdn := peer.FQDN(dnsName) fqdn := peer.FQDN(dnsName)
return &proto.PeerConfig{ return &proto.PeerConfig{
@@ -423,7 +424,7 @@ func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfi
} }
} }
func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig { func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
remotePeers := []*proto.RemotePeerConfig{} remotePeers := []*proto.RemotePeerConfig{}
for _, rPeer := range peers { for _, rPeer := range peers {
fqdn := rPeer.FQDN(dnsName) fqdn := rPeer.FQDN(dnsName)
@@ -437,7 +438,7 @@ func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig
return remotePeers return remotePeers
} }
func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse { func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse {
wtConfig := toWiretrusteeConfig(config, turnCredentials) wtConfig := toWiretrusteeConfig(config, turnCredentials)
pConfig := toPeerConfig(peer, networkMap.Network, dnsName) pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
@@ -477,7 +478,7 @@ func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Em
} }
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization // sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error { func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *nbpeer.Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error {
// make secret time based TURN credentials optional // make secret time based TURN credentials optional
var turnCredentials *TURNCredentials var turnCredentials *TURNCredentials
if s.config.TURNConfig.TimeBasedCredentials { if s.config.TURNConfig.TimeBasedCredentials {
+44 -9
View File
@@ -8,6 +8,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
@@ -40,7 +41,7 @@ func (h *AccountsHandler) GetAllAccounts(w http.ResponseWriter, r *http.Request)
return return
} }
if !user.IsAdmin() { if !user.HasAdminPower() {
util.WriteError(status.Errorf(status.PermissionDenied, "the user has no permission to access account data"), w) util.WriteError(status.Errorf(status.PermissionDenied, "the user has no permission to access account data"), w)
return return
} }
@@ -77,6 +78,10 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)), PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
} }
if req.Settings.Extra != nil {
settings.Extra = &account.ExtraSettings{PeerApprovalEnabled: *req.Settings.Extra.PeerApprovalEnabled}
}
if req.Settings.JwtGroupsEnabled != nil { if req.Settings.JwtGroupsEnabled != nil {
settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled
} }
@@ -98,15 +103,45 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
util.WriteJSONObject(w, &resp) util.WriteJSONObject(w, &resp)
} }
// DeleteAccount is a HTTP DELETE handler to delete an account
func (h *AccountsHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
return
}
claims := h.claimsExtractor.FromRequestContext(r)
vars := mux.Vars(r)
targetAccountID := vars["accountId"]
if len(targetAccountID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid account ID"), w)
return
}
err := h.accountManager.DeleteAccount(targetAccountID, claims.UserId)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, emptyObject{})
}
func toAccountResponse(account *server.Account) *api.Account { func toAccountResponse(account *server.Account) *api.Account {
settings := api.AccountSettings{
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled,
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
}
if account.Settings.Extra != nil {
settings.Extra = &api.AccountExtraSettings{PeerApprovalEnabled: &account.Settings.Extra.PeerApprovalEnabled}
}
return &api.Account{ return &api.Account{
Id: account.Id, Id: account.Id,
Settings: api.AccountSettings{ Settings: settings,
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled,
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
},
} }
} }
+89 -2
View File
@@ -66,9 +66,18 @@ components:
description: Name of the claim from which we extract groups names to add it to account groups. description: Name of the claim from which we extract groups names to add it to account groups.
type: string type: string
example: "roles" example: "roles"
extra:
$ref: '#/components/schemas/AccountExtraSettings'
required: required:
- peer_login_expiration_enabled - peer_login_expiration_enabled
- peer_login_expiration - peer_login_expiration
AccountExtraSettings:
type: object
properties:
peer_approval_enabled:
description: (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin.
type: boolean
example: true
AccountRequest: AccountRequest:
type: object type: object
properties: properties:
@@ -213,11 +222,15 @@ components:
login_expiration_enabled: login_expiration_enabled:
type: boolean type: boolean
example: false example: false
approval_required:
description: (Cloud only) Indicates whether peer needs approval
type: boolean
example: true
required: required:
- name - name
- ssh_enabled - ssh_enabled
- login_expiration_enabled - login_expiration_enabled
Peer: PeerBase:
allOf: allOf:
- $ref: '#/components/schemas/PeerMinimum' - $ref: '#/components/schemas/PeerMinimum'
- type: object - type: object
@@ -281,6 +294,10 @@ components:
type: string type: string
format: date-time format: date-time
example: 2023-05-05T09:00:35.477782Z example: 2023-05-05T09:00:35.477782Z
approval_required:
description: (Cloud only) Indicates whether peer needs approval
type: boolean
example: true
required: required:
- ip - ip
- connected - connected
@@ -294,6 +311,50 @@ components:
- login_expiration_enabled - login_expiration_enabled
- login_expired - login_expired
- last_login - last_login
AccessiblePeer:
allOf:
- $ref: '#/components/schemas/PeerMinimum'
- type: object
properties:
ip:
description: Peer's IP address
type: string
example: 10.64.0.1
dns_label:
description: 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
type: string
example: stage-host-1.netbird.cloud
user_id:
description: User ID of the user that enrolled this peer
type: string
example: google-oauth2|277474792786460067937
required:
- ip
- dns_label
- user_id
Peer:
allOf:
- $ref: '#/components/schemas/PeerBase'
- type: object
properties:
accessible_peers:
description: List of accessible peers
type: array
items:
$ref: '#/components/schemas/AccessiblePeer'
required:
- accessible_peers
PeerBatch:
allOf:
- $ref: '#/components/schemas/PeerBase'
- type: object
properties:
accessible_peers_count:
description: Number of accessible peers
type: integer
example: 5
required:
- accessible_peers_count
SetupKey: SetupKey:
type: object type: object
properties: properties:
@@ -1030,6 +1091,32 @@ paths:
'500': '500':
"$ref": "#/components/responses/internal_error" "$ref": "#/components/responses/internal_error"
/api/accounts/{accountId}: /api/accounts/{accountId}:
delete:
summary: Delete an Account
description: Deletes an account and all its resources. Only administrators and account owners can delete accounts.
tags: [ Accounts ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
parameters:
- in: path
name: accountId
required: true
schema:
type: string
description: The unique identifier of an account
responses:
'200':
description: Delete account status code
content: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
put: put:
summary: Update an Account summary: Update an Account
description: Update information about an account description: Update information about an account
@@ -1364,7 +1451,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: '#/components/schemas/Peer' $ref: '#/components/schemas/PeerBatch'
'400': '400':
"$ref": "#/components/responses/bad_request" "$ref": "#/components/responses/bad_request"
'401': '401':
+145
View File
@@ -117,6 +117,24 @@ const (
UserStatusInvited UserStatus = "invited" UserStatusInvited UserStatus = "invited"
) )
// AccessiblePeer defines model for AccessiblePeer.
type AccessiblePeer struct {
// 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"`
// Id Peer ID
Id string `json:"id"`
// Ip Peer's IP address
Ip string `json:"ip"`
// Name Peer's hostname
Name string `json:"name"`
// UserId User ID of the user that enrolled this peer
UserId string `json:"user_id"`
}
// Account defines model for Account. // Account defines model for Account.
type Account struct { type Account struct {
// Id Account ID // Id Account ID
@@ -124,6 +142,12 @@ type Account struct {
Settings AccountSettings `json:"settings"` Settings AccountSettings `json:"settings"`
} }
// AccountExtraSettings defines model for AccountExtraSettings.
type AccountExtraSettings struct {
// PeerApprovalEnabled (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin.
PeerApprovalEnabled *bool `json:"peer_approval_enabled,omitempty"`
}
// AccountRequest defines model for AccountRequest. // AccountRequest defines model for AccountRequest.
type AccountRequest struct { type AccountRequest struct {
Settings AccountSettings `json:"settings"` Settings AccountSettings `json:"settings"`
@@ -131,6 +155,8 @@ type AccountRequest struct {
// AccountSettings defines model for AccountSettings. // AccountSettings defines model for AccountSettings.
type AccountSettings struct { type AccountSettings struct {
Extra *AccountExtraSettings `json:"extra,omitempty"`
// GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user
GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"`
@@ -302,6 +328,123 @@ type NameserverGroupRequest struct {
// Peer defines model for Peer. // Peer defines model for Peer.
type Peer struct { type Peer struct {
// AccessiblePeers List of accessible peers
AccessiblePeers []AccessiblePeer `json:"accessible_peers"`
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
ApprovalRequired *bool `json:"approval_required,omitempty"`
// Connected Peer to Management connection status
Connected bool `json:"connected"`
// 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"`
// Groups Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"`
// Hostname Hostname of the machine
Hostname string `json:"hostname"`
// Id Peer ID
Id string `json:"id"`
// Ip Peer's IP address
Ip string `json:"ip"`
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
LastLogin time.Time `json:"last_login"`
// LastSeen Last time peer connected to Netbird's management service
LastSeen time.Time `json:"last_seen"`
// LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
// LoginExpired Indicates whether peer's login expired or not
LoginExpired bool `json:"login_expired"`
// Name Peer's hostname
Name string `json:"name"`
// Os Peer's operating system and version
Os string `json:"os"`
// SshEnabled Indicates whether SSH server is enabled on this peer
SshEnabled bool `json:"ssh_enabled"`
// UiVersion Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"`
// UserId User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"`
// Version Peer's daemon or cli version
Version string `json:"version"`
}
// PeerBase defines model for PeerBase.
type PeerBase struct {
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
ApprovalRequired *bool `json:"approval_required,omitempty"`
// Connected Peer to Management connection status
Connected bool `json:"connected"`
// 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"`
// Groups Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"`
// Hostname Hostname of the machine
Hostname string `json:"hostname"`
// Id Peer ID
Id string `json:"id"`
// Ip Peer's IP address
Ip string `json:"ip"`
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
LastLogin time.Time `json:"last_login"`
// LastSeen Last time peer connected to Netbird's management service
LastSeen time.Time `json:"last_seen"`
// LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
// LoginExpired Indicates whether peer's login expired or not
LoginExpired bool `json:"login_expired"`
// Name Peer's hostname
Name string `json:"name"`
// Os Peer's operating system and version
Os string `json:"os"`
// SshEnabled Indicates whether SSH server is enabled on this peer
SshEnabled bool `json:"ssh_enabled"`
// UiVersion Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"`
// UserId User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"`
// Version Peer's daemon or cli version
Version string `json:"version"`
}
// PeerBatch defines model for PeerBatch.
type PeerBatch struct {
// AccessiblePeersCount Number of accessible peers
AccessiblePeersCount int `json:"accessible_peers_count"`
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
ApprovalRequired *bool `json:"approval_required,omitempty"`
// Connected Peer to Management connection status // Connected Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
@@ -362,6 +505,8 @@ type PeerMinimum struct {
// PeerRequest defines model for PeerRequest. // PeerRequest defines model for PeerRequest.
type PeerRequest struct { type PeerRequest struct {
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
ApprovalRequired *bool `json:"approval_required,omitempty"`
LoginExpirationEnabled bool `json:"login_expiration_enabled"` LoginExpirationEnabled bool `json:"login_expiration_enabled"`
Name string `json:"name"` Name string `json:"name"`
SshEnabled bool `json:"ssh_enabled"` SshEnabled bool `json:"ssh_enabled"`
@@ -19,10 +19,11 @@ import (
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/mock_server" "github.com/netbirdio/netbird/management/server/mock_server"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
) )
var TestPeers = map[string]*server.Peer{ var TestPeers = map[string]*nbpeer.Peer{
"A": {Key: "A", ID: "peer-A-ID", IP: net.ParseIP("100.100.100.100")}, "A": {Key: "A", ID: "peer-A-ID", IP: net.ParseIP("100.100.100.100")},
"B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")}, "B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")},
} }
@@ -230,7 +231,7 @@ func TestWriteGroup(t *testing.T) {
expectedBody: false, expectedBody: false,
}, },
{ {
name: "Write Group PUT not not change Issue", name: "Write Group PUT not change Issue",
requestType: http.MethodPut, requestType: http.MethodPut,
requestPath: "/api/groups/id-jwt-group", requestPath: "/api/groups/id-jwt-group",
requestBody: bytes.NewBuffer( requestBody: bytes.NewBuffer(
+2
View File
@@ -7,6 +7,7 @@ import (
"github.com/rs/cors" "github.com/rs/cors"
"github.com/netbirdio/management-integrations/integrations" "github.com/netbirdio/management-integrations/integrations"
s "github.com/netbirdio/netbird/management/server" s "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/middleware" "github.com/netbirdio/netbird/management/server/http/middleware"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
@@ -105,6 +106,7 @@ func APIHandler(accountManager s.AccountManager, jwtValidator jwtclaims.JWTValid
func (apiHandler *apiHandler) addAccountsEndpoint() { func (apiHandler *apiHandler) addAccountsEndpoint() {
accountsHandler := NewAccountsHandler(apiHandler.AccountManager, apiHandler.AuthCfg) accountsHandler := NewAccountsHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
apiHandler.Router.HandleFunc("/accounts/{accountId}", accountsHandler.UpdateAccount).Methods("PUT", "OPTIONS") apiHandler.Router.HandleFunc("/accounts/{accountId}", accountsHandler.UpdateAccount).Methods("PUT", "OPTIONS")
apiHandler.Router.HandleFunc("/accounts/{accountId}", accountsHandler.DeleteAccount).Methods("DELETE", "OPTIONS")
apiHandler.Router.HandleFunc("/accounts", accountsHandler.GetAllAccounts).Methods("GET", "OPTIONS") apiHandler.Router.HandleFunc("/accounts", accountsHandler.GetAllAccounts).Methods("GET", "OPTIONS")
} }
@@ -33,6 +33,8 @@ func NewAccessControl(audience, userIDClaim string, getUser GetUser) *AccessCont
} }
} }
var tokenPathRegexp = regexp.MustCompile(`^.*/api/users/.*/tokens.*$`)
// Handler method of the middleware which forbids all modify requests for non admin users // Handler method of the middleware which forbids all modify requests for non admin users
// It also adds // It also adds
func (a *AccessControl) Handler(h http.Handler) http.Handler { func (a *AccessControl) Handler(h http.Handler) http.Handler {
@@ -41,6 +43,7 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler {
user, err := a.getUser(claims) user, err := a.getUser(claims)
if err != nil { if err != nil {
log.Errorf("failed to get user from claims: %s", err)
util.WriteError(status.Errorf(status.Unauthorized, "invalid JWT"), w) util.WriteError(status.Errorf(status.Unauthorized, "invalid JWT"), w)
return return
} }
@@ -50,23 +53,17 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler {
return return
} }
if !user.IsAdmin() { if !user.HasAdminPower() {
switch r.Method { switch r.Method {
case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut: case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut:
ok, err := regexp.MatchString(`^.*/api/users/.*/tokens.*$`, r.URL.Path) if tokenPathRegexp.MatchString(r.URL.Path) {
if err != nil {
log.Debugf("regex failed")
util.WriteError(status.Errorf(status.Internal, ""), w)
return
}
if ok {
log.Debugf("valid Path") log.Debugf("valid Path")
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
return return
} }
util.WriteError(status.Errorf(status.PermissionDenied, "only admin can perform this operation"), w) util.WriteError(status.Errorf(status.PermissionDenied, "only users with admin power can perform this operation"), w)
return return
} }
} }
+101 -21
View File
@@ -11,6 +11,7 @@ import (
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
) )
@@ -31,17 +32,12 @@ func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg) *Pee
} }
} }
func (h *PeersHandler) checkPeerStatus(peer *server.Peer) (*server.Peer, error) { func (h *PeersHandler) checkPeerStatus(peer *nbpeer.Peer) (*nbpeer.Peer, error) {
peerToReturn := peer.Copy() peerToReturn := peer.Copy()
if peer.Status.Connected { if peer.Status.Connected {
statuses, err := h.accountManager.GetAllConnectedPeers()
if err != nil {
return peerToReturn, err
}
// Although we have online status in store we do not yet have an updated channel so have to show it as disconnected // Although we have online status in store we do not yet have an updated channel so have to show it as disconnected
// This may happen after server restart when not all peers are yet connected // This may happen after server restart when not all peers are yet connected
if _, connected := statuses[peerToReturn.ID]; !connected { if !h.accountManager.HasConnectedChannel(peer.ID) {
peerToReturn.Status.Connected = false peerToReturn.Status.Connected = false
} }
} }
@@ -61,8 +57,14 @@ func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w
util.WriteError(err, w) util.WriteError(err, w)
return return
} }
dnsDomain := h.accountManager.GetDNSDomain()
util.WriteJSONObject(w, toPeerResponse(peerToReturn, account, h.accountManager.GetDNSDomain())) groupsInfo := toGroupsInfo(account.Groups, peer.ID)
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers))
} }
func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) { func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
@@ -73,15 +75,26 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
return return
} }
update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name,
LoginExpirationEnabled: req.LoginExpirationEnabled} LoginExpirationEnabled: req.LoginExpirationEnabled}
if req.ApprovalRequired != nil {
update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired}
}
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
if err != nil { if err != nil {
util.WriteError(err, w) util.WriteError(err, w)
return return
} }
dnsDomain := h.accountManager.GetDNSDomain() dnsDomain := h.accountManager.GetDNSDomain()
util.WriteJSONObject(w, toPeerResponse(peer, account, dnsDomain))
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers))
} }
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) { func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
@@ -142,14 +155,18 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
dnsDomain := h.accountManager.GetDNSDomain() dnsDomain := h.accountManager.GetDNSDomain()
respBody := []*api.Peer{} respBody := make([]*api.PeerBatch, 0, len(peers))
for _, peer := range peers { for _, peer := range peers {
peerToReturn, err := h.checkPeerStatus(peer) peerToReturn, err := h.checkPeerStatus(peer)
if err != nil { if err != nil {
util.WriteError(err, w) util.WriteError(err, w)
return return
} }
respBody = append(respBody, toPeerResponse(peerToReturn, account, dnsDomain)) groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
accessiblePeerNumbers := h.accessiblePeersNumber(account, peer.ID)
respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
} }
util.WriteJSONObject(w, respBody) util.WriteJSONObject(w, respBody)
return return
@@ -158,17 +175,48 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
} }
} }
func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string) *api.Peer { func (h *PeersHandler) accessiblePeersNumber(account *server.Account, peerID string) int {
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
return len(netMap.Peers) + len(netMap.OfflinePeers)
}
func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.AccessiblePeer {
accessiblePeers := make([]api.AccessiblePeer, 0, len(netMap.Peers)+len(netMap.OfflinePeers))
for _, p := range netMap.Peers {
ap := api.AccessiblePeer{
Id: p.ID,
Name: p.Name,
Ip: p.IP.String(),
DnsLabel: fqdn(p, dnsDomain),
UserId: p.UserID,
}
accessiblePeers = append(accessiblePeers, ap)
}
for _, p := range netMap.OfflinePeers {
ap := api.AccessiblePeer{
Id: p.ID,
Name: p.Name,
Ip: p.IP.String(),
DnsLabel: fqdn(p, dnsDomain),
UserId: p.UserID,
}
accessiblePeers = append(accessiblePeers, ap)
}
return accessiblePeers
}
func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMinimum {
var groupsInfo []api.GroupMinimum var groupsInfo []api.GroupMinimum
groupsChecked := make(map[string]struct{}) groupsChecked := make(map[string]struct{})
for _, group := range account.Groups { for _, group := range groups {
_, ok := groupsChecked[group.ID] _, ok := groupsChecked[group.ID]
if ok { if ok {
continue continue
} }
groupsChecked[group.ID] = struct{}{} groupsChecked[group.ID] = struct{}{}
for _, pk := range group.Peers { for _, pk := range group.Peers {
if pk == peer.ID { if pk == peerID {
info := api.GroupMinimum{ info := api.GroupMinimum{
Id: group.ID, Id: group.ID,
Name: group.Name, Name: group.Name,
@@ -179,12 +227,10 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string
} }
} }
} }
return groupsInfo
}
fqdn := peer.FQDN(dnsDomain) func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
if fqdn == "" {
fqdn = peer.DNSLabel
}
return &api.Peer{ return &api.Peer{
Id: peer.ID, Id: peer.ID,
Name: peer.Name, Name: peer.Name,
@@ -198,9 +244,43 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string
Hostname: peer.Meta.Hostname, Hostname: peer.Meta.Hostname,
UserId: &peer.UserID, UserId: &peer.UserID,
UiVersion: &peer.Meta.UIVersion, UiVersion: &peer.Meta.UIVersion,
DnsLabel: fqdn, 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,
ApprovalRequired: &peer.Status.RequiresApproval,
}
}
func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch {
return &api.PeerBatch{
Id: peer.ID,
Name: peer.Name,
Ip: peer.IP.String(),
Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
Version: peer.Meta.WtVersion,
Groups: groupsInfo,
SshEnabled: peer.SSHEnabled,
Hostname: peer.Meta.Hostname,
UserId: &peer.UserID,
UiVersion: &peer.Meta.UIVersion,
DnsLabel: fqdn(peer, dnsDomain),
LoginExpirationEnabled: peer.LoginExpirationEnabled,
LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired,
AccessiblePeersCount: accessiblePeersCount,
ApprovalRequired: &peer.Status.RequiresApproval,
}
}
func fqdn(peer *nbpeer.Peer, dnsDomain string) string {
fqdn := peer.FQDN(dnsDomain)
if fqdn == "" {
return peer.DNSLabel
} else {
return fqdn
} }
} }
+25 -15
View File
@@ -3,7 +3,6 @@ package http
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
@@ -14,6 +13,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
@@ -26,11 +26,11 @@ import (
const testPeerID = "test_peer" const testPeerID = "test_peer"
const noUpdateChannelTestPeerID = "no-update-channel" const noUpdateChannelTestPeerID = "no-update-channel"
func initTestMetaData(peers ...*server.Peer) *PeersHandler { func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler {
return &PeersHandler{ return &PeersHandler{
accountManager: &mock_server.MockAccountManager{ accountManager: &mock_server.MockAccountManager{
UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) { UpdatePeerFunc: func(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
var p *server.Peer var p *nbpeer.Peer
for _, peer := range peers { for _, peer := range peers {
if update.ID == peer.ID { if update.ID == peer.ID {
p = peer.Copy() p = peer.Copy()
@@ -42,8 +42,8 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
p.Name = update.Name p.Name = update.Name
return p, nil return p, nil
}, },
GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) { GetPeerFunc: func(accountID, peerID, userID string) (*nbpeer.Peer, error) {
var p *server.Peer var p *nbpeer.Peer
for _, peer := range peers { for _, peer := range peers {
if peerID == peer.ID { if peerID == peer.ID {
p = peer.Copy() p = peer.Copy()
@@ -52,7 +52,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
} }
return p, nil return p, nil
}, },
GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) { GetPeersFunc: func(accountID, userID string) ([]*nbpeer.Peer, error) {
return peers, nil return peers, nil
}, },
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) { GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
@@ -60,8 +60,9 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
return &server.Account{ return &server.Account{
Id: claims.AccountId, Id: claims.AccountId,
Domain: "hotmail.com", Domain: "hotmail.com",
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
peers[0].ID: peers[0], peers[0].ID: peers[0],
peers[1].ID: peers[1],
}, },
Users: map[string]*server.User{ Users: map[string]*server.User{
"test_user": user, "test_user": user,
@@ -70,9 +71,17 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
PeerLoginExpirationEnabled: true, PeerLoginExpirationEnabled: true,
PeerLoginExpiration: time.Hour, PeerLoginExpiration: time.Hour,
}, },
Network: &server.Network{
Identifier: "ciclqisab2ss43jdn8q0",
Net: net.IPNet{
IP: net.ParseIP("100.67.0.0"),
Mask: net.IPv4Mask(255, 255, 0, 0),
},
Serial: 51,
},
}, user, nil }, user, nil
}, },
GetAllConnectedPeersFunc: func() (map[string]struct{}, error) { HasConnectedChannelFunc: func(peerID string) bool {
statuses := make(map[string]struct{}) statuses := make(map[string]struct{})
for _, peer := range peers { for _, peer := range peers {
if peer.ID == noUpdateChannelTestPeerID { if peer.ID == noUpdateChannelTestPeerID {
@@ -80,7 +89,8 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
} }
statuses[peer.ID] = struct{}{} statuses[peer.ID] = struct{}{}
} }
return statuses, nil _, ok := statuses[peerID]
return ok
}, },
}, },
claimsExtractor: jwtclaims.NewClaimsExtractor( claimsExtractor: jwtclaims.NewClaimsExtractor(
@@ -99,15 +109,15 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
// Use the metadata generated by initTestMetaData() to check for values // Use the metadata generated by initTestMetaData() to check for values
func TestGetPeers(t *testing.T) { func TestGetPeers(t *testing.T) {
peer := &server.Peer{ peer := &nbpeer.Peer{
ID: testPeerID, ID: testPeerID,
Key: "key", Key: "key",
SetupKey: "setupkey", SetupKey: "setupkey",
IP: net.ParseIP("100.64.0.1"), IP: net.ParseIP("100.64.0.1"),
Status: &server.PeerStatus{Connected: true}, Status: &nbpeer.PeerStatus{Connected: true},
Name: "PeerName", Name: "PeerName",
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
Meta: server.PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "hostname", Hostname: "hostname",
GoOS: "GoOS", GoOS: "GoOS",
Kernel: "kernel", Kernel: "kernel",
@@ -136,7 +146,7 @@ func TestGetPeers(t *testing.T) {
requestPath string requestPath string
requestBody io.Reader requestBody io.Reader
expectedArray bool expectedArray bool
expectedPeer *server.Peer expectedPeer *nbpeer.Peer
}{ }{
{ {
name: "GetPeersMetaData", name: "GetPeersMetaData",
@@ -223,7 +233,7 @@ func TestGetPeers(t *testing.T) {
} }
} }
fmt.Println(got) t.Log(got)
assert.Equal(t, got.Name, tc.expectedPeer.Name) assert.Equal(t, got.Name, tc.expectedPeer.Name)
assert.Equal(t, got.Version, tc.expectedPeer.Meta.WtVersion) assert.Equal(t, got.Version, tc.expectedPeer.Meta.WtVersion)
+5 -3
View File
@@ -290,17 +290,19 @@ func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Polic
Enabled: policy.Enabled, Enabled: policy.Enabled,
} }
for _, r := range policy.Rules { for _, r := range policy.Rules {
rID := r.ID
rDescription := r.Description
rule := api.PolicyRule{ rule := api.PolicyRule{
Id: &r.ID, Id: &rID,
Name: r.Name, Name: r.Name,
Enabled: r.Enabled, Enabled: r.Enabled,
Description: &r.Description, Description: &rDescription,
Bidirectional: r.Bidirectional, Bidirectional: r.Bidirectional,
Protocol: api.PolicyRuleProtocol(r.Protocol), Protocol: api.PolicyRuleProtocol(r.Protocol),
Action: api.PolicyRuleAction(r.Action), Action: api.PolicyRuleAction(r.Action),
} }
if len(r.Ports) != 0 { if len(r.Ports) != 0 {
portsCopy := r.Ports[:] portsCopy := r.Ports
rule.Ports = &portsCopy rule.Ports = &portsCopy
} }
for _, gid := range r.Sources { for _, gid := range r.Sources {
@@ -11,6 +11,7 @@ import (
"testing" "testing"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
@@ -55,12 +56,12 @@ var baseExistingRoute = &route.Route{
var testingAccount = &server.Account{ var testingAccount = &server.Account{
Id: testAccountID, Id: testAccountID,
Domain: "hotmail.com", Domain: "hotmail.com",
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
existingPeerID: { existingPeerID: {
Key: existingPeerKey, Key: existingPeerKey,
IP: netip.MustParseAddr(existingPeerIP1).AsSlice(), IP: netip.MustParseAddr(existingPeerIP1).AsSlice(),
ID: existingPeerID, ID: existingPeerID,
Meta: server.PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
GoOS: "linux", GoOS: "linux",
}, },
}, },
@@ -68,7 +69,7 @@ var testingAccount = &server.Account{
Key: nonLinuxExistingPeerID, Key: nonLinuxExistingPeerID,
IP: netip.MustParseAddr(existingPeerIP2).AsSlice(), IP: netip.MustParseAddr(existingPeerIP2).AsSlice(),
ID: nonLinuxExistingPeerID, ID: nonLinuxExistingPeerID,
Meta: server.PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
GoOS: "darwin", GoOS: "darwin",
}, },
}, },
+5 -4
View File
@@ -192,13 +192,14 @@ func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
func toResponseBody(key *server.SetupKey) *api.SetupKey { func toResponseBody(key *server.SetupKey) *api.SetupKey {
var state string var state string
if key.IsExpired() { switch {
case key.IsExpired():
state = "expired" state = "expired"
} else if key.IsRevoked() { case key.IsRevoked():
state = "revoked" state = "revoked"
} else if key.IsOverUsed() { case key.IsOverUsed():
state = "overused" state = "overused"
} else { default:
state = "valid" state = "valid"
} }
@@ -220,6 +220,7 @@ func TestSetupKeysHandlers(t *testing.T) {
} }
func assertKeys(t *testing.T, got *api.SetupKey, expected *api.SetupKey) { func assertKeys(t *testing.T, got *api.SetupKey, expected *api.SetupKey) {
t.Helper()
// this comparison is done manually because when converting to JSON dates formatted differently // this comparison is done manually because when converting to JSON dates formatted differently
// assert.Equal(t, got.UpdatedAt, tc.expectedSetupKey.UpdatedAt) //doesn't work // assert.Equal(t, got.UpdatedAt, tc.expectedSetupKey.UpdatedAt) //doesn't work
assert.WithinDurationf(t, got.UpdatedAt, expected.UpdatedAt, 0, "") assert.WithinDurationf(t, got.UpdatedAt, expected.UpdatedAt, 0, "")
+11 -2
View File
@@ -94,7 +94,7 @@ func (h *UsersHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
util.WriteJSONObject(w, toUserResponse(newUser, claims.UserId)) util.WriteJSONObject(w, toUserResponse(newUser, claims.UserId))
} }
// DeleteUser is a DELETE request to delete a user (only works for service users right now) // DeleteUser is a DELETE request to delete a user
func (h *UsersHandler) DeleteUser(w http.ResponseWriter, r *http.Request) { func (h *UsersHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete { if r.Method != http.MethodDelete {
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w) util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
@@ -155,9 +155,14 @@ func (h *UsersHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
email = *req.Email email = *req.Email
} }
name := ""
if req.Name != nil {
name = *req.Name
}
newUser, err := h.accountManager.CreateUser(account.Id, user.Id, &server.UserInfo{ newUser, err := h.accountManager.CreateUser(account.Id, user.Id, &server.UserInfo{
Email: email, Email: email,
Name: *req.Name, Name: name,
Role: req.Role, Role: req.Role,
AutoGroups: req.AutoGroups, AutoGroups: req.AutoGroups,
IsServiceUser: req.IsServiceUser, IsServiceUser: req.IsServiceUser,
@@ -197,10 +202,14 @@ func (h *UsersHandler) GetAllUsers(w http.ResponseWriter, r *http.Request) {
users := make([]*api.User, 0) users := make([]*api.User, 0)
for _, r := range data { for _, r := range data {
if r.NonDeletable {
continue
}
if serviceUser == "" { if serviceUser == "" {
users = append(users, toUserResponse(r, claims.UserId)) users = append(users, toUserResponse(r, claims.UserId))
continue continue
} }
includeServiceUser, err := strconv.ParseBool(serviceUser) includeServiceUser, err := strconv.ParseBool(serviceUser)
log.Debugf("Should include service user: %v", includeServiceUser) log.Debugf("Should include service user: %v", includeServiceUser)
if err != nil { if err != nil {
+11 -2
View File
@@ -20,8 +20,9 @@ import (
) )
const ( const (
serviceUserID = "serviceUserID" serviceUserID = "serviceUserID"
regularUserID = "regularUserID" nonDeletableServiceUserID = "nonDeletableServiceUserID"
regularUserID = "regularUserID"
) )
var usersTestAccount = &server.Account{ var usersTestAccount = &server.Account{
@@ -49,6 +50,13 @@ var usersTestAccount = &server.Account{
AutoGroups: []string{"group_1"}, AutoGroups: []string{"group_1"},
Issued: server.UserIssuedAPI, Issued: server.UserIssuedAPI,
}, },
nonDeletableServiceUserID: {
Id: serviceUserID,
Role: "admin",
IsServiceUser: true,
NonDeletable: true,
Issued: server.UserIssuedIntegration,
},
}, },
} }
@@ -67,6 +75,7 @@ func initUsersTestData() *UsersHandler {
Name: "", Name: "",
Email: "", Email: "",
IsServiceUser: v.IsServiceUser, IsServiceUser: v.IsServiceUser,
NonDeletable: v.NonDeletable,
Issued: v.Issued, Issued: v.Issued,
}) })
} }
+1
View File
@@ -65,6 +65,7 @@ func (mc *mockAuth0Credentials) Authenticate() (JWTToken, error) {
} }
func newTestJWT(t *testing.T, expInt int) string { func newTestJWT(t *testing.T, expInt int) string {
t.Helper()
now := time.Now() now := time.Now()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iat": now.Unix(), "iat": now.Unix(),
+77
View File
@@ -0,0 +1,77 @@
package idp
// MockIDP is a mock implementation of the IDP interface
type MockIDP struct {
UpdateUserAppMetadataFunc func(userId string, appMetadata AppMetadata) error
GetUserDataByIDFunc func(userId string, appMetadata AppMetadata) (*UserData, error)
GetAccountFunc func(accountId string) ([]*UserData, error)
GetAllAccountsFunc func() (map[string][]*UserData, error)
CreateUserFunc func(email, name, accountID, invitedByEmail string) (*UserData, error)
GetUserByEmailFunc func(email string) ([]*UserData, error)
InviteUserByIDFunc func(userID string) error
DeleteUserFunc func(userID string) error
}
// UpdateUserAppMetadata is a mock implementation of the IDP interface UpdateUserAppMetadata method
func (m *MockIDP) UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error {
if m.UpdateUserAppMetadataFunc != nil {
return m.UpdateUserAppMetadataFunc(userId, appMetadata)
}
return nil
}
// GetUserDataByID is a mock implementation of the IDP interface GetUserDataByID method
func (m *MockIDP) GetUserDataByID(userId string, appMetadata AppMetadata) (*UserData, error) {
if m.GetUserDataByIDFunc != nil {
return m.GetUserDataByIDFunc(userId, appMetadata)
}
return nil, nil
}
// GetAccount is a mock implementation of the IDP interface GetAccount method
func (m *MockIDP) GetAccount(accountId string) ([]*UserData, error) {
if m.GetAccountFunc != nil {
return m.GetAccountFunc(accountId)
}
return nil, nil
}
// GetAllAccounts is a mock implementation of the IDP interface GetAllAccounts method
func (m *MockIDP) GetAllAccounts() (map[string][]*UserData, error) {
if m.GetAllAccountsFunc != nil {
return m.GetAllAccountsFunc()
}
return nil, nil
}
// CreateUser is a mock implementation of the IDP interface CreateUser method
func (m *MockIDP) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
if m.CreateUserFunc != nil {
return m.CreateUserFunc(email, name, accountID, invitedByEmail)
}
return nil, nil
}
// GetUserByEmail is a mock implementation of the IDP interface GetUserByEmail method
func (m *MockIDP) GetUserByEmail(email string) ([]*UserData, error) {
if m.GetUserByEmailFunc != nil {
return m.GetUserByEmailFunc(email)
}
return nil, nil
}
// InviteUserByID is a mock implementation of the IDP interface InviteUserByID method
func (m *MockIDP) InviteUserByID(userID string) error {
if m.InviteUserByIDFunc != nil {
return m.InviteUserByIDFunc(userID)
}
return nil
}
// DeleteUser is a mock implementation of the IDP interface DeleteUser method
func (m *MockIDP) DeleteUser(userID string) error {
if m.DeleteUserFunc != nil {
return m.DeleteUserFunc(userID)
}
return nil
}
-1
View File
@@ -256,7 +256,6 @@ func (om *OktaManager) InviteUserByID(_ string) error {
func (om *OktaManager) DeleteUser(userID string) error { func (om *OktaManager) DeleteUser(userID string) error {
resp, err := om.client.User.DeactivateOrDeleteUser(context.Background(), userID, nil) resp, err := om.client.User.DeactivateOrDeleteUser(context.Background(), userID, nil)
if err != nil { if err != nil {
fmt.Println(err.Error())
return err return err
} }
+3 -5
View File
@@ -463,11 +463,9 @@ func (zp zitadelProfile) userData() *UserData {
if zp.Human != nil { if zp.Human != nil {
email = zp.Human.Email.Email email = zp.Human.Email.Email
name = zp.Human.Profile.DisplayName name = zp.Human.Profile.DisplayName
} else { } else if len(zp.LoginNames) > 0 {
if len(zp.LoginNames) > 0 { email = zp.LoginNames[0]
email = zp.LoginNames[0] name = zp.LoginNames[0]
name = zp.LoginNames[0]
}
} }
return &UserData{ return &UserData{
+11 -5
View File
@@ -10,7 +10,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance string) *http.Request { func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audience string) *http.Request {
t.Helper()
const layout = "2006-01-02T15:04:05.999Z" const layout = "2006-01-02T15:04:05.999Z"
claimMaps := jwt.MapClaims{} claimMaps := jwt.MapClaims{}
@@ -18,16 +19,16 @@ func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance st
claimMaps[UserIDClaim] = claims.UserId claimMaps[UserIDClaim] = claims.UserId
} }
if claims.AccountId != "" { if claims.AccountId != "" {
claimMaps[audiance+AccountIDSuffix] = claims.AccountId claimMaps[audience+AccountIDSuffix] = claims.AccountId
} }
if claims.Domain != "" { if claims.Domain != "" {
claimMaps[audiance+DomainIDSuffix] = claims.Domain claimMaps[audience+DomainIDSuffix] = claims.Domain
} }
if claims.DomainCategory != "" { if claims.DomainCategory != "" {
claimMaps[audiance+DomainCategorySuffix] = claims.DomainCategory claimMaps[audience+DomainCategorySuffix] = claims.DomainCategory
} }
if claims.LastLogin != (time.Time{}) { if claims.LastLogin != (time.Time{}) {
claimMaps[audiance+LastLoginSuffix] = claims.LastLogin.Format(layout) claimMaps[audience+LastLoginSuffix] = claims.LastLogin.Format(layout)
} }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimMaps) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimMaps)
r, err := http.NewRequest(http.MethodGet, "http://localhost", nil) r, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
@@ -143,6 +144,7 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
} }
func TestExtractClaimsSetOptions(t *testing.T) { func TestExtractClaimsSetOptions(t *testing.T) {
t.Helper()
type test struct { type test struct {
name string name string
extractor *ClaimsExtractor extractor *ClaimsExtractor
@@ -153,6 +155,7 @@ func TestExtractClaimsSetOptions(t *testing.T) {
name: "No custom options", name: "No custom options",
extractor: NewClaimsExtractor(), extractor: NewClaimsExtractor(),
check: func(t *testing.T, c test) { check: func(t *testing.T, c test) {
t.Helper()
if c.extractor.authAudience != "" { if c.extractor.authAudience != "" {
t.Error("audience should be empty") t.Error("audience should be empty")
return return
@@ -172,6 +175,7 @@ func TestExtractClaimsSetOptions(t *testing.T) {
name: "Custom audience", name: "Custom audience",
extractor: NewClaimsExtractor(WithAudience("https://login/")), extractor: NewClaimsExtractor(WithAudience("https://login/")),
check: func(t *testing.T, c test) { check: func(t *testing.T, c test) {
t.Helper()
if c.extractor.authAudience != "https://login/" { if c.extractor.authAudience != "https://login/" {
t.Errorf("audience expected %s, got %s", "https://login/", c.extractor.authAudience) t.Errorf("audience expected %s, got %s", "https://login/", c.extractor.authAudience)
return return
@@ -183,6 +187,7 @@ func TestExtractClaimsSetOptions(t *testing.T) {
name: "Custom user id claim", name: "Custom user id claim",
extractor: NewClaimsExtractor(WithUserIDClaim("customUserId")), extractor: NewClaimsExtractor(WithUserIDClaim("customUserId")),
check: func(t *testing.T, c test) { check: func(t *testing.T, c test) {
t.Helper()
if c.extractor.userIDClaim != "customUserId" { if c.extractor.userIDClaim != "customUserId" {
t.Errorf("user id claim expected %s, got %s", "customUserId", c.extractor.userIDClaim) t.Errorf("user id claim expected %s, got %s", "customUserId", c.extractor.userIDClaim)
return return
@@ -199,6 +204,7 @@ func TestExtractClaimsSetOptions(t *testing.T) {
} }
})), })),
check: func(t *testing.T, c test) { check: func(t *testing.T, c test) {
t.Helper()
claims := c.extractor.FromRequestContext(&http.Request{}) claims := c.extractor.FromRequestContext(&http.Request{})
if claims.UserId != "testCustomRequest" { if claims.UserId != "testCustomRequest" {
t.Errorf("user id claim expected %s, got %s", "testCustomRequest", claims.UserId) t.Errorf("user id claim expected %s, got %s", "testCustomRequest", claims.UserId)
+2 -1
View File
@@ -400,6 +400,7 @@ func TestServer_GetDeviceAuthorizationFlow(t *testing.T) {
} }
func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error) { func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error) {
t.Helper()
lis, err := net.Listen("tcp", "localhost:0") lis, err := net.Listen("tcp", "localhost:0")
if err != nil { if err != nil {
return nil, "", err return nil, "", err
@@ -409,7 +410,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
peersUpdateManager := NewPeersUpdateManager() peersUpdateManager := NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "", accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "",
eventStore, false) eventStore, false)
+1 -1
View File
@@ -501,7 +501,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
if err != nil { if err != nil {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
} }
peersUpdateManager := server.NewPeersUpdateManager() peersUpdateManager := server.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore, false) eventStore, false)
+14 -16
View File
@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"regexp"
"sort" "sort"
"strings" "strings"
"time" "time"
@@ -201,14 +200,14 @@ func (w *Worker) generateProperties() properties {
expirationEnabled++ expirationEnabled++
} }
groups = groups + len(account.Groups) groups += len(account.Groups)
routes = routes + len(account.Routes) routes += len(account.Routes)
for _, route := range account.Routes { for _, route := range account.Routes {
if len(route.PeerGroups) > 0 { if len(route.PeerGroups) > 0 {
routesWithRGGroups++ routesWithRGGroups++
} }
} }
nameservers = nameservers + len(account.NameServerGroups) nameservers += len(account.NameServerGroups)
for _, policy := range account.Policies { for _, policy := range account.Policies {
for _, rule := range policy.Rules { for _, rule := range policy.Rules {
@@ -232,10 +231,10 @@ func (w *Worker) generateProperties() properties {
} }
for _, key := range account.SetupKeys { for _, key := range account.SetupKeys {
setupKeysUsage = setupKeysUsage + key.UsedTimes setupKeysUsage += key.UsedTimes
if key.Ephemeral { if key.Ephemeral {
ephemeralPeersSKs++ ephemeralPeersSKs++
ephemeralPeersSKUsage = ephemeralPeersSKUsage + key.UsedTimes ephemeralPeersSKUsage += key.UsedTimes
} }
} }
@@ -381,29 +380,28 @@ func createPostRequest(ctx context.Context, endpoint string, payloadStr string)
} }
func getMinMaxVersion(inputList []string) (string, string) { func getMinMaxVersion(inputList []string) (string, string) {
reg, err := regexp.Compile(version.SemverRegexpRaw)
if err != nil {
return "", ""
}
versions := make([]*version.Version, 0) versions := make([]*version.Version, 0)
for _, raw := range inputList { for _, raw := range inputList {
if raw != "" && reg.MatchString(raw) { if raw != "" && nbversion.SemverRegexp.MatchString(raw) {
v, err := version.NewVersion(raw) v, err := version.NewVersion(raw)
if err == nil { if err == nil {
versions = append(versions, v) versions = append(versions, v)
} }
} }
} }
switch len(versions) {
targetIndex := 1
l := len(versions)
switch l {
case 0: case 0:
return "", "" return "", ""
case 1: case targetIndex:
v := versions[0].String() v := versions[targetIndex-1].String()
return v, v return v, v
default: default:
sort.Sort(version.Collection(versions)) sort.Sort(version.Collection(versions))
return versions[0].String(), versions[len(versions)-1].String() return versions[targetIndex-1].String(), versions[l-1].String()
} }
} }
+5 -4
View File
@@ -5,6 +5,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@@ -37,12 +38,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account {
NameServerGroups: map[string]*nbdns.NameServerGroup{ NameServerGroups: map[string]*nbdns.NameServerGroup{
"1": {}, "1": {},
}, },
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
"1": { "1": {
ID: "1", ID: "1",
UserID: "test", UserID: "test",
SSHEnabled: true, SSHEnabled: true,
Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
}, },
}, },
Policies: []*server.Policy{ Policies: []*server.Policy{
@@ -101,12 +102,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account {
NameServerGroups: map[string]*nbdns.NameServerGroup{ NameServerGroups: map[string]*nbdns.NameServerGroup{
"1": {}, "1": {},
}, },
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
"1": { "1": {
ID: "1", ID: "1",
UserID: "test", UserID: "test",
SSHEnabled: true, SSHEnabled: true,
Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
}, },
}, },
Policies: []*server.Policy{ Policies: []*server.Policy{

Some files were not shown because too many files have changed in this diff Show More