Merge branch 'main' into feature/peer-approval

This commit is contained in:
pascal-fischer 2023-11-29 16:27:01 +01:00 committed by GitHub
commit 141065f14e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1095 additions and 346 deletions

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:

View File

@ -12,6 +12,12 @@ linters-settings:
# Default: false # Default: false
check-type-assertions: false check-type-assertions: false
gocritic:
disabled-checks:
- commentFormatting
- captLocal
- deprecatedComment
govet: govet:
# Enable all analyzers. # Enable all analyzers.
# Default: false # Default: false
@ -19,6 +25,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,6 +40,7 @@ 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
@ -35,6 +48,7 @@ linters:
- dupword # dupword checks for duplicate words in the source code - dupword # dupword checks for duplicate words in the source code
- durationcheck # durationcheck checks for two durations multiplied together - durationcheck # durationcheck checks for two durations multiplied together
- forbidigo # forbidigo forbids identifiers - forbidigo # forbidigo forbids identifiers
- gocritic # provides diagnostics that check for bugs, performance and style issues
- mirror # mirror reports wrong mirror patterns of bytes/strings usage - mirror # mirror reports wrong mirror patterns of bytes/strings usage
- misspell # misspess finds commonly misspelled English words in comments - 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

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:

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

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

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

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

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

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

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

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

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

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

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")
} }

View File

@ -252,7 +252,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)

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)
@ -773,9 +773,9 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
t.Helper() 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)

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 {

View File

@ -204,16 +204,14 @@ 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 // todo fix custom address
if e.dnsServer == nil {
e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress)
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.routeManager.SetRouteChangeListener(e.mobileDep.NetworkChangeListener) e.routeManager.SetRouteChangeListener(e.mobileDep.NetworkChangeListener)
@ -490,16 +488,14 @@ 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
} }

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)

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

View File

@ -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

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

View File

@ -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)

View File

@ -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{

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 toNetIPAddr(a route.Addr) (netip.Addr, bool) {
}
func toIPAddr(a route.Addr) (net.IP, error) {
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
} }
} }

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 {
@ -97,7 +109,7 @@ loop:
rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0]))
attrs, err := syscall.ParseNetlinkRouteAttr(&m) attrs, err := syscall.ParseNetlinkRouteAttr(&m)
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 +117,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 {

View File

@ -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) {

View File

@ -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,21 +51,23 @@ 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()) err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String())
require.NoError(t, err, "should not return err") require.NoError(t, err, "removeFromRouteTableIfNonSystem 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")
internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
require.NoError(t, err) require.NoError(t, err)
@ -75,6 +77,7 @@ func TestAddRemoveRoutes(t *testing.T) {
} else { } else {
require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway") require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway")
} }
}
}) })
} }
} }
@ -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)
}
}
}

View File

@ -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

View File

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

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 (

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

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()

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

View File

@ -44,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]

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)

View File

@ -86,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) {
@ -99,8 +101,6 @@ func GetParsedDomainLabel(name string) (string, error) {
return "", fmt.Errorf("unable to convert host label 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]

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

View File

@ -285,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()
@ -293,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
@ -364,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()
@ -372,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{
@ -408,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()
@ -416,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{

View File

@ -67,6 +67,7 @@ type AccountManager interface {
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)
ListUsers(accountID string) ([]*User, error) ListUsers(accountID string) ([]*User, error)
@ -978,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
} }
} }
@ -1031,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.IsAdmin() {
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) {
@ -1624,9 +1677,10 @@ func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, er
return am.peersUpdateManager.GetAllConnectedPeers(), nil return am.peersUpdateManager.GetAllConnectedPeers(), nil
} }
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.MatchString(domain)
} }
// GetDNSDomain returns the configured dnsDomain // GetDNSDomain returns the configured dnsDomain

View File

@ -902,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 {

View File

@ -352,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()

View File

@ -122,6 +122,60 @@ 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)

View File

@ -103,6 +103,30 @@ 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{ settings := api.AccountSettings{
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),

View File

@ -1091,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

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")
} }

View File

@ -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 {
@ -55,13 +57,7 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler {
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

View File

@ -221,7 +221,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin
} }
groupsChecked[group.ID] = struct{}{} groupsChecked[group.ID] = struct{}{}
for _, pk := range group.Peers { for _, pk := range group.Peers {
if pk != peerID { if pk == peerID {
info := api.GroupMinimum{ info := api.GroupMinimum{
Id: group.ID, Id: group.ID,
Name: group.Name, Name: group.Name,

View File

@ -300,7 +300,7 @@ func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Polic
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 {

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

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,

View File

@ -463,12 +463,10 @@ 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{
Email: email, Email: email,

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,15 +380,10 @@ 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)

View File

@ -68,6 +68,7 @@ type MockAccountManager struct {
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error) ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error) CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error)
GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error)
DeleteAccountFunc func(accountID, userID string) error
GetDNSDomainFunc func() string GetDNSDomainFunc func() string
StoreEventFunc func(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) StoreEventFunc func(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any)
GetEventsFunc func(accountID, userID string) ([]*activity.Event, error) GetEventsFunc func(accountID, userID string) ([]*activity.Event, error)
@ -157,6 +158,14 @@ func (am *MockAccountManager) GetAccountFromPAT(pat string) (*server.Account, *s
return nil, nil, nil, status.Errorf(codes.Unimplemented, "method GetAccountFromPAT is not implemented") return nil, nil, nil, status.Errorf(codes.Unimplemented, "method GetAccountFromPAT is not implemented")
} }
// DeleteAccount mock implementation of DeleteAccount from server.AccountManager interface
func (am *MockAccountManager) DeleteAccount(accountID, userID string) error {
if am.DeleteAccountFunc != nil {
return am.DeleteAccountFunc(accountID, userID)
}
return status.Errorf(codes.Unimplemented, "method DeleteAccount is not implemented")
}
// MarkPATUsed mock implementation of MarkPATUsed from server.AccountManager interface // MarkPATUsed mock implementation of MarkPATUsed from server.AccountManager interface
func (am *MockAccountManager) MarkPATUsed(pat string) error { func (am *MockAccountManager) MarkPATUsed(pat string) error {
if am.MarkPATUsedFunc != nil { if am.MarkPATUsedFunc != nil {

View File

@ -267,8 +267,9 @@ func validateGroups(list []string, groups map[string]*Group) error {
return nil return nil
} }
var domainMatcher = regexp.MustCompile(domainPattern)
func validateDomain(domain string) error { func validateDomain(domain string) error {
domainMatcher := regexp.MustCompile(domainPattern)
if !domainMatcher.MatchString(domain) { if !domainMatcher.MatchString(domain) {
return errors.New("domain should consists of only letters, numbers, and hyphens with no leading, trailing hyphens, or spaces") return errors.New("domain should consists of only letters, numbers, and hyphens with no leading, trailing hyphens, or spaces")
} }

View File

@ -67,7 +67,7 @@ func NewNetwork() *Network {
func (n *Network) IncSerial() { func (n *Network) IncSerial() {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
n.Serial = n.Serial + 1 n.Serial++
} }
// CurrentSerial returns the Network.Serial of the network (latest state id) // CurrentSerial returns the Network.Serial of the network (latest state id)

View File

@ -407,7 +407,7 @@ func (am *DefaultAccountManager) ListPolicies(accountID, userID string) ([]*Poli
return nil, status.Errorf(status.PermissionDenied, "Only Administrators can view policies") return nil, status.Errorf(status.PermissionDenied, "Only Administrators can view policies")
} }
return account.Policies[:], nil return account.Policies, nil
} }
func (am *DefaultAccountManager) deletePolicy(account *Account, policyID string) (*Policy, error) { func (am *DefaultAccountManager) deletePolicy(account *Account, policyID string) (*Policy, error) {

View File

@ -137,7 +137,7 @@ func (key *SetupKey) HiddenCopy(length int) *SetupKey {
// IncrementUsage makes a copy of a key, increments the UsedTimes by 1 and sets LastUsed to now // IncrementUsage makes a copy of a key, increments the UsedTimes by 1 and sets LastUsed to now
func (key *SetupKey) IncrementUsage() *SetupKey { func (key *SetupKey) IncrementUsage() *SetupKey {
c := key.Copy() c := key.Copy()
c.UsedTimes = c.UsedTimes + 1 c.UsedTimes++
c.LastUsed = time.Now().UTC() c.LastUsed = time.Now().UTC()
return c return c
} }

View File

@ -204,6 +204,37 @@ func (s *SqliteStore) SaveAccount(account *Account) error {
return err return err
} }
func (s *SqliteStore) DeleteAccount(account *Account) error {
start := time.Now()
err := s.db.Transaction(func(tx *gorm.DB) error {
result := tx.Select(clause.Associations).Delete(account.Policies, "account_id = ?", account.Id)
if result.Error != nil {
return result.Error
}
result = tx.Select(clause.Associations).Delete(account.UsersG, "account_id = ?", account.Id)
if result.Error != nil {
return result.Error
}
result = tx.Select(clause.Associations).Delete(account)
if result.Error != nil {
return result.Error
}
return nil
})
took := time.Since(start)
if s.metrics != nil {
s.metrics.StoreMetrics().CountPersistenceDuration(took)
}
log.Debugf("took %d ms to delete an account to the SQLite", took.Milliseconds())
return err
}
func (s *SqliteStore) SaveInstallationID(ID string) error { func (s *SqliteStore) SaveInstallationID(ID string) error {
installation := installation{InstallationIDValue: ID} installation := installation{InstallationIDValue: ID}
installation.ID = uint(s.installationPK) installation.ID = uint(s.installationPK)
@ -338,7 +369,7 @@ func (s *SqliteStore) GetAccount(accountID string) (*Account, error) {
var rules []*PolicyRule var rules []*PolicyRule
err := s.db.Model(&PolicyRule{}).Find(&rules, "policy_id = ?", policy.ID).Error err := s.db.Model(&PolicyRule{}).Find(&rules, "policy_id = ?", policy.ID).Error
if err != nil { if err != nil {
return nil, status.Errorf(status.NotFound, "account not found") return nil, status.Errorf(status.NotFound, "rule not found")
} }
account.Policies[i].Rules = rules account.Policies[i].Rules = rules
} }

View File

@ -100,6 +100,80 @@ func TestSqlite_SaveAccount(t *testing.T) {
} }
} }
func TestSqlite_DeleteAccount(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("The SQLite store is not properly supported by Windows yet")
}
store := newSqliteStore(t)
testUserID := "testuser"
user := NewAdminUser(testUserID)
user.PATs = map[string]*PersonalAccessToken{"testtoken": {
ID: "testtoken",
Name: "test token",
}}
account := newAccountWithId("account_id", testUserID, "")
setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey
account.Peers["testpeer"] = &Peer{
Key: "peerkey",
SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{},
Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
}
account.Users[testUserID] = user
err := store.SaveAccount(account)
require.NoError(t, err)
if len(store.GetAllAccounts()) != 1 {
t.Errorf("expecting 1 Accounts to be stored after SaveAccount()")
}
err = store.DeleteAccount(account)
require.NoError(t, err)
if len(store.GetAllAccounts()) != 0 {
t.Errorf("expecting 0 Accounts to be stored after DeleteAccount()")
}
_, err = store.GetAccountByPeerPubKey("peerkey")
require.Error(t, err, "expecting error after removing DeleteAccount when getting account by peer public key")
_, err = store.GetAccountByUser("testuser")
require.Error(t, err, "expecting error after removing DeleteAccount when getting account by user")
_, err = store.GetAccountByPeerID("testpeer")
require.Error(t, err, "expecting error after removing DeleteAccount when getting account by peer id")
_, err = store.GetAccountBySetupKey(setupKey.Key)
require.Error(t, err, "expecting error after removing DeleteAccount when getting account by setup key")
_, err = store.GetAccount(account.Id)
require.Error(t, err, "expecting error after removing DeleteAccount when getting account by id")
for _, policy := range account.Policies {
var rules []*PolicyRule
err = store.db.Model(&PolicyRule{}).Find(&rules, "policy_id = ?", policy.ID).Error
require.NoError(t, err, "expecting no error after removing DeleteAccount when searching for policy rules")
require.Len(t, rules, 0, "expecting no policy rules to be found after removing DeleteAccount")
}
for _, accountUser := range account.Users {
var pats []*PersonalAccessToken
err = store.db.Model(&PersonalAccessToken{}).Find(&pats, "user_id = ?", accountUser.Id).Error
require.NoError(t, err, "expecting no error after removing DeleteAccount when searching for personal access token")
require.Len(t, pats, 0, "expecting no personal access token to be found after removing DeleteAccount")
}
}
func TestSqlite_SavePeerStatus(t *testing.T) { func TestSqlite_SavePeerStatus(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("The SQLite store is not properly supported by Windows yet") t.Skip("The SQLite store is not properly supported by Windows yet")

View File

@ -15,6 +15,7 @@ import (
type Store interface { type Store interface {
GetAllAccounts() []*Account GetAllAccounts() []*Account
GetAccount(accountID string) (*Account, error) GetAccount(accountID string) (*Account, error)
DeleteAccount(account *Account) error
GetAccountByUser(userID string) (*Account, error) GetAccountByUser(userID string) (*Account, error)
GetAccountByPeerPubKey(peerKey string) (*Account, error) GetAccountByPeerPubKey(peerKey string) (*Account, error)
GetAccountByPeerID(peerID string) (*Account, error) GetAccountByPeerID(peerID string) (*Account, error)

View File

@ -259,6 +259,14 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite
return nil, fmt.Errorf("provided user update is nil") return nil, fmt.Errorf("provided user update is nil")
} }
switch {
case invite.Name == "":
return nil, status.Errorf(status.InvalidArgument, "name can't be empty")
case invite.Email == "":
return nil, status.Errorf(status.InvalidArgument, "email can't be empty")
default:
}
account, err := am.Store.GetAccount(accountID) account, err := am.Store.GetAccount(accountID)
if err != nil { if err != nil {
return nil, status.Errorf(status.NotFound, "account %s doesn't exist", accountID) return nil, status.Errorf(status.NotFound, "account %s doesn't exist", accountID)

View File

@ -0,0 +1,3 @@
# Extra flags you might want to pass to the daemon
FLAGS=""

View File

@ -0,0 +1,41 @@
[Unit]
Description=Netbird Management
Documentation=https://netbird.io/docs
After=network-online.target syslog.target
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=-/etc/default/netbird-management
ExecStart=/usr/bin/netbird-mgmt management $FLAGS
Restart=on-failure
RestartSec=5
TimeoutStopSec=10
CacheDirectory=netbird
ConfigurationDirectory=netbird
LogDirectory=netbird
RuntimeDirectory=netbird
StateDirectory=netbird
# sandboxing
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateMounts=yes
PrivateTmp=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=yes
RemoveIPC=yes
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,41 @@
[Unit]
Description=Netbird Signal
Documentation=https://netbird.io/docs
After=network-online.target syslog.target
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=-/etc/default/netbird-signal
ExecStart=/usr/bin/netbird-signal run $FLAGS
Restart=on-failure
RestartSec=5
TimeoutStopSec=10
CacheDirectory=netbird
ConfigurationDirectory=netbird
LogDirectory=netbird
RuntimeDirectory=netbird
StateDirectory=netbird
# sandboxing
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateMounts=yes
PrivateTmp=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=yes
RemoveIPC=yes
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,41 @@
[Unit]
Description=Netbird Client (%i)
Documentation=https://netbird.io/docs
After=network-online.target syslog.target NetworkManager.service
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=-/etc/default/netbird
ExecStart=/usr/bin/netbird service run --log-file /var/log/netbird/client-%i.log --config /etc/netbird/%i.json --daemon-addr unix:///var/run/netbird/%i.sock $FLAGS
Restart=on-failure
RestartSec=5
TimeoutStopSec=10
CacheDirectory=netbird
ConfigurationDirectory=netbird
LogDirectory=netbird
RuntimeDirectory=netbird
StateDirectory=netbird
# sandboxing
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateMounts=yes
PrivateTmp=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=no # needed to load wg module for kernel-mode WireGuard
ProtectKernelTunables=no
ProtectSystem=yes
RemoveIPC=yes
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
[Install]
WantedBy=multi-user.target

View File

@ -248,7 +248,7 @@ func (s *SharedSocket) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
decodedLayers := make([]gopacket.LayerType, 0, 3) decodedLayers := make([]gopacket.LayerType, 0, 3)
err = parser.DecodeLayers(pkt.buf[:], &decodedLayers) err = parser.DecodeLayers(pkt.buf, &decodedLayers)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }

View File

@ -354,16 +354,17 @@ func (c *GrpcClient) receive(stream proto.SignalExchange_ConnectStreamClient,
for { for {
msg, err := stream.Recv() msg, err := stream.Recv()
if s, ok := status.FromError(err); ok && s.Code() == codes.Canceled { switch s, ok := status.FromError(err); {
case ok && s.Code() == codes.Canceled:
log.Debugf("stream canceled (usually indicates shutdown)") log.Debugf("stream canceled (usually indicates shutdown)")
return err return err
} else if s.Code() == codes.Unavailable { case s.Code() == codes.Unavailable:
log.Debugf("Signal Service is unavailable") log.Debugf("Signal Service is unavailable")
return err return err
} else if err == io.EOF { case err == io.EOF:
log.Debugf("Signal Service stream closed by server") log.Debugf("Signal Service stream closed by server")
return err return err
} else if err != nil { case err != nil:
return err return err
} }
log.Tracef("received a new message from Peer [fingerprint: %s]", msg.Key) log.Tracef("received a new message from Peer [fingerprint: %s]", msg.Key)

View File

@ -15,7 +15,7 @@ func Retry(attempts int, sleep time.Duration, toExec func() error, onError func(
if attempts--; attempts > 0 { if attempts--; attempts > 0 {
jitter := time.Duration(rand.Int63n(int64(sleep))) jitter := time.Duration(rand.Int63n(int64(sleep)))
sleep = sleep + jitter/2 sleep += jitter / 2
onError(err) onError(err)
time.Sleep(sleep) time.Sleep(sleep)

View File

@ -1,8 +1,19 @@
package version package version
import (
"regexp"
v "github.com/hashicorp/go-version"
)
// will be replaced with the release version when using goreleaser // will be replaced with the release version when using goreleaser
var version = "development" var version = "development"
var (
VersionRegexp = regexp.MustCompile("^" + v.VersionRegexpRaw + "$")
SemverRegexp = regexp.MustCompile("^" + v.SemverRegexpRaw + "$")
)
// NetbirdVersion returns the Netbird version // NetbirdVersion returns the Netbird version
func NetbirdVersion() string { func NetbirdVersion() string {
return version return version