From 125a7a9daf6f40470f27bbe7cdc3d0a5aea4764c Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Tue, 7 Nov 2023 14:05:58 +0300 Subject: [PATCH 01/59] Add integration activity types to codes.go (#1293) New activity types for integration creation, update, and deletion have been added to the activity codes. This ensures the tracking of these user activities relating to integrations, which were not previously being logged. --- management/server/activity/codes.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index ce36f520f..7bad9f627 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -114,6 +114,12 @@ const ( PeerLoginExpired // DashboardLogin indicates that the user logged in to the dashboard DashboardLogin + // IntegrationCreated indicates that the user created an integration + IntegrationCreated + // IntegrationUpdated indicates that the user updated an integration + IntegrationUpdated + // IntegrationDeleted indicates that the user deleted an integration + IntegrationDeleted ) var activityMap = map[Activity]Code{ @@ -169,6 +175,9 @@ var activityMap = map[Activity]Code{ UserLoggedInPeer: {"User logged in peer", "user.peer.login"}, PeerLoginExpired: {"Peer login expired", "peer.login.expire"}, DashboardLogin: {"Dashboard login", "dashboard.login"}, + IntegrationCreated: {"Integration created", "integration.create"}, + IntegrationUpdated: {"Integration updated", "integration.update"}, + IntegrationDeleted: {"Integration deleted", "integration.delete"}, } // StringCode returns a string code of the activity From b726b3262d94b52ae4439070fd8d82bd92cf83f9 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Tue, 7 Nov 2023 13:37:57 +0100 Subject: [PATCH 02/59] Add codespell job (#1281) add codespell workflow and fixed spelling issues --- .github/workflows/golangci-lint.yml | 12 ++++++++++++ client/internal/acl/manager_test.go | 2 +- client/internal/dns/server_test.go | 4 ++-- client/internal/ebpf/ebpf/manager_linux_test.go | 8 ++++---- client/internal/peer/conn.go | 4 ++-- client/ssh/util.go | 5 +++-- dns/dns.go | 7 ++++--- iface/tun_adapter.go | 2 +- infrastructure_files/configure.sh | 2 +- infrastructure_files/getting-started-with-zitadel.sh | 2 +- infrastructure_files/turnserver.conf.tmpl | 2 +- management/server/account_test.go | 6 +++--- management/server/jwtclaims/extractor_test.go | 10 +++++----- sharedsock/sock_linux.go | 2 +- signal/client/grpc.go | 2 +- 15 files changed, 42 insertions(+), 28 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 4e584ecc2..b23baf031 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -10,6 +10,18 @@ concurrency: cancel-in-progress: true jobs: + codespell: + name: codespell + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: codespell + uses: codespell-project/actions-codespell@v2 + with: + ignore_words_list: erro,clienta + skip: go.mod,go.sum + only_warn: 1 golangci: strategy: fail-fast: false diff --git a/client/internal/acl/manager_test.go b/client/internal/acl/manager_test.go index 518e895cf..25de2a57f 100644 --- a/client/internal/acl/manager_test.go +++ b/client/internal/acl/manager_test.go @@ -281,7 +281,7 @@ func TestDefaultManagerSquashRulesNoAffect(t *testing.T) { manager := &DefaultManager{} if rules, _ := manager.squashAcceptRules(networkMap); len(rules) != len(networkMap.FirewallRules) { - t.Errorf("we should got same amount of rules as intput, got %v", len(rules)) + t.Errorf("we should get the same amount of rules as output, got %v", len(rules)) } } diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index cd7932d07..51cc9c8c4 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -339,7 +339,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) { err = wgIface.Create() if err != nil { - t.Errorf("crate and init wireguard interface: %v", err) + t.Errorf("create and init wireguard interface: %v", err) return } defer func() { @@ -789,7 +789,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { err = wgIface.Create() if err != nil { - t.Fatalf("crate and init wireguard interface: %v", err) + t.Fatalf("create and init wireguard interface: %v", err) return nil, err } diff --git a/client/internal/ebpf/ebpf/manager_linux_test.go b/client/internal/ebpf/ebpf/manager_linux_test.go index 956499e5b..5664a4565 100644 --- a/client/internal/ebpf/ebpf/manager_linux_test.go +++ b/client/internal/ebpf/ebpf/manager_linux_test.go @@ -8,12 +8,12 @@ func TestManager_setFeatureFlag(t *testing.T) { mgr := GeneralManager{} mgr.setFeatureFlag(featureFlagWGProxy) if mgr.featureFlags != 1 { - t.Errorf("invalid faeture state") + t.Errorf("invalid feature state") } mgr.setFeatureFlag(featureFlagDnsForwarder) if mgr.featureFlags != 3 { - t.Errorf("invalid faeture state") + t.Errorf("invalid feature state") } } @@ -27,7 +27,7 @@ func TestManager_unsetFeatureFlag(t *testing.T) { t.Errorf("unexpected error: %s", err) } if mgr.featureFlags != 2 { - t.Errorf("invalid faeture state, expected: %d, got: %d", 2, mgr.featureFlags) + t.Errorf("invalid feature state, expected: %d, got: %d", 2, mgr.featureFlags) } err = mgr.unsetFeatureFlag(featureFlagDnsForwarder) @@ -35,6 +35,6 @@ func TestManager_unsetFeatureFlag(t *testing.T) { t.Errorf("unexpected error: %s", err) } if mgr.featureFlags != 0 { - t.Errorf("invalid faeture state, expected: %d, got: %d", 0, mgr.featureFlags) + t.Errorf("invalid feature state, expected: %d, got: %d", 0, mgr.featureFlags) } } diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 9247ed3c5..442f5fe27 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -242,7 +242,7 @@ func (conn *Conn) Open() error { } err := conn.statusRecorder.UpdatePeerState(peerState) if err != nil { - log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err) + log.Warnf("error while updating the state of peer %s,err: %v", conn.config.Key, err) } defer func() { @@ -301,7 +301,7 @@ func (conn *Conn) Open() error { } err = conn.statusRecorder.UpdatePeerState(peerState) if err != nil { - log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err) + log.Warnf("error while updating the state of peer %s,err: %v", conn.config.Key, err) } err = conn.agent.GatherCandidates() diff --git a/client/ssh/util.go b/client/ssh/util.go index 8ecb77c93..cf5f1396e 100644 --- a/client/ssh/util.go +++ b/client/ssh/util.go @@ -9,9 +9,10 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "strings" + "golang.org/x/crypto/ed25519" gossh "golang.org/x/crypto/ssh" - "strings" ) // KeyType is a type of SSH key @@ -42,7 +43,7 @@ func GeneratePrivateKey(keyType KeyType) ([]byte, error) { case RSA: key, err = rsa.GenerateKey(rand.Reader, RSAKeySize) default: - return nil, fmt.Errorf("unsupported ket type %s", keyType) + return nil, fmt.Errorf("unsupported key type %s", keyType) } if err != nil { return nil, err diff --git a/dns/dns.go b/dns/dns.go index 16ebd1d96..b58b79b53 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -4,11 +4,12 @@ package dns import ( "fmt" - "github.com/miekg/dns" - "golang.org/x/net/idna" "net" "regexp" "strings" + + "github.com/miekg/dns" + "golang.org/x/net/idna" ) const ( @@ -95,7 +96,7 @@ func GetParsedDomainLabel(name string) (string, error) { rawLabel := labels[0] ascii, err := idna.Punycode.ToASCII(rawLabel) if err != nil { - return "", fmt.Errorf("unable to convert host lavel to ASCII, error: %v", err) + return "", fmt.Errorf("unable to convert host label to ASCII, error: %v", err) } invalidHostMatcher := regexp.MustCompile(invalidHostLabel) diff --git a/iface/tun_adapter.go b/iface/tun_adapter.go index da0b1695b..c10eb3d19 100644 --- a/iface/tun_adapter.go +++ b/iface/tun_adapter.go @@ -1,6 +1,6 @@ package iface -// TunAdapter is an interface for create tun device from externel service +// TunAdapter is an interface for create tun device from external service type TunAdapter interface { ConfigureInterface(address string, mtu int, dns string, searchDomains string, routes string) (int, error) UpdateAddr(address string) error diff --git a/infrastructure_files/configure.sh b/infrastructure_files/configure.sh index 6d2902816..1f70584f3 100755 --- a/infrastructure_files/configure.sh +++ b/infrastructure_files/configure.sh @@ -36,7 +36,7 @@ fi if [[ "x-$NETBIRD_DOMAIN" == "x-" ]]; then echo NETBIRD_DOMAIN is not set, please update your setup.env file - echo If you are migrating from old versions, you migh need to update your variables prefixes from + echo If you are migrating from old versions, you might need to update your variables prefixes from echo WIRETRUSTEE_.. TO NETBIRD_ exit 1 fi diff --git a/infrastructure_files/getting-started-with-zitadel.sh b/infrastructure_files/getting-started-with-zitadel.sh index d00c2719c..67d16e1e2 100644 --- a/infrastructure_files/getting-started-with-zitadel.sh +++ b/infrastructure_files/getting-started-with-zitadel.sh @@ -387,7 +387,7 @@ check_nb_domain() { if [ "$DOMAIN" == "netbird.example.com" ]; then echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr - retrun 1 + return 1 fi return 0 } diff --git a/infrastructure_files/turnserver.conf.tmpl b/infrastructure_files/turnserver.conf.tmpl index 9b31cb511..1dc38e62d 100644 --- a/infrastructure_files/turnserver.conf.tmpl +++ b/infrastructure_files/turnserver.conf.tmpl @@ -677,7 +677,7 @@ no-cli # #cli-password=$5$79a316b350311570$81df9cfb9af7f5e5a76eada31e7097b663a0670f99a3c07ded3f1c8e59c5658a # -# Or unsecure form for the same password: +# Or insecure form for the same password: # # cli-password=CHANGE_ME diff --git a/management/server/account_test.go b/management/server/account_test.go index c8a8a5dc9..0d707bde9 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -251,7 +251,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) { } if account != nil && account.Users[userID] == nil { - t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userID, account.Id) + t.Fatalf("expected to create an account for a user %s but no user was found after creation under the account %s", userID, account.Id) return } @@ -626,7 +626,7 @@ func TestAccountManager_PrivateAccount(t *testing.T) { } if account != nil && account.Users[userId] == nil { - t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userId, account.Id) + t.Fatalf("expected to create an account for a user %s but no user was found after creation under the account %s", userId, account.Id) } } @@ -1385,7 +1385,7 @@ func TestAccount_Copy(t *testing.T) { if err != nil { t.Fatal(err) } - account.Peers["peer1"].Status.Connected = false // we change original object to confirm that copy wont change + account.Peers["peer1"].Status.Connected = false // we change original object to confirm that copy won't change accCopyBytes, err := json.Marshal(accountCopy) if err != nil { t.Fatal(err) diff --git a/management/server/jwtclaims/extractor_test.go b/management/server/jwtclaims/extractor_test.go index f7eeb82e5..f2cbb1ebe 100644 --- a/management/server/jwtclaims/extractor_test.go +++ b/management/server/jwtclaims/extractor_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance string) *http.Request { +func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audience string) *http.Request { const layout = "2006-01-02T15:04:05.999Z" claimMaps := jwt.MapClaims{} @@ -18,16 +18,16 @@ func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance st claimMaps[UserIDClaim] = claims.UserId } if claims.AccountId != "" { - claimMaps[audiance+AccountIDSuffix] = claims.AccountId + claimMaps[audience+AccountIDSuffix] = claims.AccountId } if claims.Domain != "" { - claimMaps[audiance+DomainIDSuffix] = claims.Domain + claimMaps[audience+DomainIDSuffix] = claims.Domain } if claims.DomainCategory != "" { - claimMaps[audiance+DomainCategorySuffix] = claims.DomainCategory + claimMaps[audience+DomainCategorySuffix] = claims.DomainCategory } if claims.LastLogin != (time.Time{}) { - claimMaps[audiance+LastLoginSuffix] = claims.LastLogin.Format(layout) + claimMaps[audience+LastLoginSuffix] = claims.LastLogin.Format(layout) } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimMaps) r, err := http.NewRequest(http.MethodGet, "http://localhost", nil) diff --git a/sharedsock/sock_linux.go b/sharedsock/sock_linux.go index c9e35dfa2..b823bb508 100644 --- a/sharedsock/sock_linux.go +++ b/sharedsock/sock_linux.go @@ -262,7 +262,7 @@ func (s *SharedSocket) ReadFrom(b []byte) (n int, addr net.Addr, err error) { return int(udp.Length), remoteAddr, nil } -// WriteTo builds a UDP packet and writes it using the specific IP version writter +// WriteTo builds a UDP packet and writes it using the specific IP version writer func (s *SharedSocket) WriteTo(buf []byte, rAddr net.Addr) (n int, err error) { rUDPAddr, ok := rAddr.(*net.UDPAddr) if !ok { diff --git a/signal/client/grpc.go b/signal/client/grpc.go index fef443173..9f70234e9 100644 --- a/signal/client/grpc.go +++ b/signal/client/grpc.go @@ -260,7 +260,7 @@ func (c *GrpcClient) SendToStream(msg *proto.EncryptedMessage) error { return fmt.Errorf("no connection to signal") } if c.stream == nil { - return fmt.Errorf("connection to the Signal Exchnage has not been established yet. Please call GrpcClient.Receive before sending messages") + return fmt.Errorf("connection to the Signal Exchange has not been established yet. Please call GrpcClient.Receive before sending messages") } err := c.stream.Send(msg) From 8be6e92563334489c11afe48d3faa32fee18ebc6 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Tue, 7 Nov 2023 14:38:36 +0100 Subject: [PATCH 03/59] Extend API with accessible peers (#1284) Extend the peer and peers API endpoints with accessible peers. --- management/server/http/api/openapi.yml | 48 ++++++- management/server/http/api/types.gen.go | 126 +++++++++++++++++++ management/server/http/peers_handler.go | 103 +++++++++++++-- management/server/http/peers_handler_test.go | 9 ++ 4 files changed, 271 insertions(+), 15 deletions(-) diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index a0a64fd98..64e97426a 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -217,7 +217,7 @@ components: - name - ssh_enabled - login_expiration_enabled - Peer: + PeerBase: allOf: - $ref: '#/components/schemas/PeerMinimum' - type: object @@ -294,6 +294,50 @@ components: - login_expiration_enabled - login_expired - last_login + AccessiblePeer: + allOf: + - $ref: '#/components/schemas/PeerMinimum' + - type: object + properties: + ip: + description: Peer's IP address + type: string + example: 10.64.0.1 + dns_label: + description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + type: string + example: stage-host-1.netbird.cloud + user_id: + description: User ID of the user that enrolled this peer + type: string + example: google-oauth2|277474792786460067937 + required: + - ip + - dns_label + - user_id + Peer: + allOf: + - $ref: '#/components/schemas/PeerBase' + - type: object + properties: + accessible_peers: + description: List of accessible peers + type: array + items: + $ref: '#/components/schemas/AccessiblePeer' + required: + - accessible_peers + PeerBatch: + allOf: + - $ref: '#/components/schemas/PeerBase' + - type: object + properties: + accessible_peers_count: + description: Number of accessible peers + type: integer + example: 5 + required: + - accessible_peers_count SetupKey: type: object properties: @@ -1364,7 +1408,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/Peer' + $ref: '#/components/schemas/PeerBatch' '400': "$ref": "#/components/responses/bad_request" '401': diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index ddf8ce65f..ea70b7f3a 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -117,6 +117,24 @@ const ( UserStatusInvited UserStatus = "invited" ) +// AccessiblePeer defines model for AccessiblePeer. +type AccessiblePeer struct { + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + + // Id Peer ID + Id string `json:"id"` + + // Ip Peer's IP address + Ip string `json:"ip"` + + // Name Peer's hostname + Name string `json:"name"` + + // UserId User ID of the user that enrolled this peer + UserId string `json:"user_id"` +} + // Account defines model for Account. type Account struct { // Id Account ID @@ -302,6 +320,114 @@ type NameserverGroupRequest struct { // Peer defines model for Peer. type Peer struct { + // AccessiblePeers List of accessible peers + AccessiblePeers []AccessiblePeer `json:"accessible_peers"` + + // Connected Peer to Management connection status + Connected bool `json:"connected"` + + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + + // Groups Groups that the peer belongs to + Groups []GroupMinimum `json:"groups"` + + // Hostname Hostname of the machine + Hostname string `json:"hostname"` + + // Id Peer ID + Id string `json:"id"` + + // Ip Peer's IP address + Ip string `json:"ip"` + + // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. + LastLogin time.Time `json:"last_login"` + + // LastSeen Last time peer connected to Netbird's management service + LastSeen time.Time `json:"last_seen"` + + // LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not + LoginExpirationEnabled bool `json:"login_expiration_enabled"` + + // LoginExpired Indicates whether peer's login expired or not + LoginExpired bool `json:"login_expired"` + + // Name Peer's hostname + Name string `json:"name"` + + // Os Peer's operating system and version + Os string `json:"os"` + + // SshEnabled Indicates whether SSH server is enabled on this peer + SshEnabled bool `json:"ssh_enabled"` + + // UiVersion Peer's desktop UI version + UiVersion *string `json:"ui_version,omitempty"` + + // UserId User ID of the user that enrolled this peer + UserId *string `json:"user_id,omitempty"` + + // Version Peer's daemon or cli version + Version string `json:"version"` +} + +// PeerBase defines model for PeerBase. +type PeerBase struct { + // Connected Peer to Management connection status + Connected bool `json:"connected"` + + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + + // Groups Groups that the peer belongs to + Groups []GroupMinimum `json:"groups"` + + // Hostname Hostname of the machine + Hostname string `json:"hostname"` + + // Id Peer ID + Id string `json:"id"` + + // Ip Peer's IP address + Ip string `json:"ip"` + + // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. + LastLogin time.Time `json:"last_login"` + + // LastSeen Last time peer connected to Netbird's management service + LastSeen time.Time `json:"last_seen"` + + // LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not + LoginExpirationEnabled bool `json:"login_expiration_enabled"` + + // LoginExpired Indicates whether peer's login expired or not + LoginExpired bool `json:"login_expired"` + + // Name Peer's hostname + Name string `json:"name"` + + // Os Peer's operating system and version + Os string `json:"os"` + + // SshEnabled Indicates whether SSH server is enabled on this peer + SshEnabled bool `json:"ssh_enabled"` + + // UiVersion Peer's desktop UI version + UiVersion *string `json:"ui_version,omitempty"` + + // UserId User ID of the user that enrolled this peer + UserId *string `json:"user_id,omitempty"` + + // Version Peer's daemon or cli version + Version string `json:"version"` +} + +// PeerBatch defines model for PeerBatch. +type PeerBatch struct { + // AccessiblePeersCount Number of accessible peers + AccessiblePeersCount int `json:"accessible_peers_count"` + // Connected Peer to Management connection status Connected bool `json:"connected"` diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index a485d6ccf..3d0d735ec 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -61,8 +61,14 @@ func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w util.WriteError(err, w) return } + dnsDomain := h.accountManager.GetDNSDomain() - util.WriteJSONObject(w, toPeerResponse(peerToReturn, account, h.accountManager.GetDNSDomain())) + groupsInfo := toGroupsInfo(account.Groups, peer.ID) + + netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain()) + accessiblePeers := toAccessiblePeers(netMap, dnsDomain) + + util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers)) } func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) { @@ -81,7 +87,13 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe return } dnsDomain := h.accountManager.GetDNSDomain() - util.WriteJSONObject(w, toPeerResponse(peer, account, dnsDomain)) + + groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID) + + netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain()) + accessiblePeers := toAccessiblePeers(netMap, dnsDomain) + + util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers)) } func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) { @@ -142,14 +154,18 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) { dnsDomain := h.accountManager.GetDNSDomain() - respBody := []*api.Peer{} + respBody := make([]*api.PeerBatch, 0, len(peers)) for _, peer := range peers { peerToReturn, err := h.checkPeerStatus(peer) if err != nil { util.WriteError(err, w) return } - respBody = append(respBody, toPeerResponse(peerToReturn, account, dnsDomain)) + groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID) + + accessiblePeerNumbers := h.accessiblePeersNumber(account, peer.ID) + + respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers)) } util.WriteJSONObject(w, respBody) return @@ -158,17 +174,48 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) { } } -func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string) *api.Peer { +func (h *PeersHandler) accessiblePeersNumber(account *server.Account, peerID string) int { + netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain()) + return len(netMap.Peers) + len(netMap.OfflinePeers) +} + +func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.AccessiblePeer { + accessiblePeers := make([]api.AccessiblePeer, 0, len(netMap.Peers)+len(netMap.OfflinePeers)) + for _, p := range netMap.Peers { + ap := api.AccessiblePeer{ + Id: p.ID, + Name: p.Name, + Ip: p.IP.String(), + DnsLabel: fqdn(p, dnsDomain), + UserId: p.UserID, + } + accessiblePeers = append(accessiblePeers, ap) + } + + for _, p := range netMap.OfflinePeers { + ap := api.AccessiblePeer{ + Id: p.ID, + Name: p.Name, + Ip: p.IP.String(), + DnsLabel: fqdn(p, dnsDomain), + UserId: p.UserID, + } + accessiblePeers = append(accessiblePeers, ap) + } + return accessiblePeers +} + +func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMinimum { var groupsInfo []api.GroupMinimum groupsChecked := make(map[string]struct{}) - for _, group := range account.Groups { + for _, group := range groups { _, ok := groupsChecked[group.ID] if ok { continue } groupsChecked[group.ID] = struct{}{} for _, pk := range group.Peers { - if pk == peer.ID { + if pk != peerID { info := api.GroupMinimum{ Id: group.ID, Name: group.Name, @@ -179,12 +226,10 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string } } } + return groupsInfo +} - fqdn := peer.FQDN(dnsDomain) - if fqdn == "" { - fqdn = peer.DNSLabel - } - +func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer { return &api.Peer{ Id: peer.ID, Name: peer.Name, @@ -198,9 +243,41 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string Hostname: peer.Meta.Hostname, UserId: &peer.UserID, UiVersion: &peer.Meta.UIVersion, - DnsLabel: fqdn, + DnsLabel: fqdn(peer, dnsDomain), LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, + AccessiblePeers: accessiblePeer, + } +} + +func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch { + return &api.PeerBatch{ + Id: peer.ID, + Name: peer.Name, + Ip: peer.IP.String(), + Connected: peer.Status.Connected, + LastSeen: peer.Status.LastSeen, + Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core), + Version: peer.Meta.WtVersion, + Groups: groupsInfo, + SshEnabled: peer.SSHEnabled, + Hostname: peer.Meta.Hostname, + UserId: &peer.UserID, + UiVersion: &peer.Meta.UIVersion, + DnsLabel: fqdn(peer, dnsDomain), + LoginExpirationEnabled: peer.LoginExpirationEnabled, + LastLogin: peer.LastLogin, + LoginExpired: peer.Status.LoginExpired, + AccessiblePeersCount: accessiblePeersCount, + } +} + +func fqdn(peer *server.Peer, dnsDomain string) string { + fqdn := peer.FQDN(dnsDomain) + if fqdn == "" { + return peer.DNSLabel + } else { + return fqdn } } diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 1856861d5..e61c6905c 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -70,8 +70,17 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { PeerLoginExpirationEnabled: true, PeerLoginExpiration: time.Hour, }, + Network: &server.Network{ + Identifier: "ciclqisab2ss43jdn8q0", + Net: net.IPNet{ + IP: net.ParseIP("100.67.0.0"), + Mask: net.IPv4Mask(255, 255, 0, 0), + }, + Serial: 51, + }, }, user, nil }, + GetAllConnectedPeersFunc: func() (map[string]struct{}, error) { statuses := make(map[string]struct{}) for _, peer := range peers { From 9f7e13fc87b037c4d3071351bb58d010f1aca67e Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Tue, 7 Nov 2023 17:02:51 +0300 Subject: [PATCH 04/59] Enable deletion of integration resources (#1294) * Enforce admin service user role for integration group deletion Added a check to prevent non-admin service users from deleting integration groups. * Restrict deletion of integration user to admin service user only * Refactor user and group deletion tests --- management/server/group.go | 10 ++++++++-- management/server/group_test.go | 17 ++++++++++++++--- management/server/user.go | 5 +++-- management/server/user_test.go | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/management/server/group.go b/management/server/group.go index d626c3538..16f19147f 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -163,9 +163,15 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) return nil } - // check integration link + // disable a deleting integration group if the initiator is not an admin service user if g.Issued == GroupIssuedIntegration { - return &GroupLinkError{GroupIssuedIntegration, g.IntegrationReference.String()} + executingUser := account.Users[userId] + if executingUser == nil { + return status.Errorf(status.NotFound, "user not found") + } + if executingUser.Role != UserRoleAdmin || !executingUser.IsServiceUser { + return status.Errorf(status.PermissionDenied, "only admins service user can delete integration group") + } } // check route links diff --git a/management/server/group_test.go b/management/server/group_test.go index 5db0ca900..97a1b75f5 100644 --- a/management/server/group_test.go +++ b/management/server/group_test.go @@ -1,9 +1,11 @@ package server import ( + "errors" "testing" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" ) @@ -55,19 +57,28 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) { { "integration", "grp-for-integration", - "integration", + "only admins service user can delete integration group", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - err = am.DeleteGroup(account.Id, "", testCase.groupID) + err = am.DeleteGroup(account.Id, groupAdminUserID, testCase.groupID) if err == nil { t.Errorf("delete %s group successfully", testCase.groupID) return } - gErr, ok := err.(*GroupLinkError) + var sErr *status.Error + if errors.As(err, &sErr) { + if sErr.Message != testCase.expectedReason { + t.Errorf("invalid error case: %s, expected: %s", sErr.Message, testCase.expectedReason) + } + return + } + + var gErr *GroupLinkError + ok := errors.As(err, &gErr) if !ok { t.Error("invalid error type") return diff --git a/management/server/user.go b/management/server/user.go index 22edd2c2c..71d3cd7f4 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -387,8 +387,9 @@ func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, t return status.Errorf(status.NotFound, "target user not found") } - if targetUser.Issued == UserIssuedIntegration { - return status.Errorf(status.PermissionDenied, "only integration can delete this user") + // disable deleting integration user if the initiator is not admin service user + if targetUser.Issued == UserIssuedIntegration && !executingUser.IsServiceUser { + return status.Errorf(status.PermissionDenied, "only admin service user can delete this user") } // handle service user first and exit, no need to fetch extra data from IDP, etc diff --git a/management/server/user_test.go b/management/server/user_test.go index f1b997186..40e7f9a2d 100644 --- a/management/server/user_test.go +++ b/management/server/user_test.go @@ -508,7 +508,7 @@ func TestUser_DeleteUser_regularUser(t *testing.T) { name: "Delete integration regular user permission denied ", userID: "user4", assertErrFunc: assert.Error, - assertErrMessage: "only integration can delete this user", + assertErrMessage: "only admin service user can delete this user", }, } From 89e8540531fbc17dbd9b78df15edf445f67a6006 Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Wed, 8 Nov 2023 13:35:37 +0300 Subject: [PATCH 05/59] Export account manager events store (#1295) * Expose account manager StoreEvent to integrations * Add account manager StoreEvent mock --- management/server/account.go | 15 +++++----- management/server/dns.go | 4 +-- management/server/event.go | 2 +- management/server/group.go | 8 +++--- management/server/mock_server/account_mock.go | 8 ++++++ management/server/nameserver.go | 6 ++-- management/server/peer.go | 12 ++++---- management/server/policy.go | 4 +-- management/server/route.go | 9 +++--- management/server/setupkey.go | 10 +++---- management/server/user.go | 28 +++++++++---------- 11 files changed, 58 insertions(+), 48 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 30a9bd200..729660650 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -97,6 +97,7 @@ type AccountManager interface { DeleteNameServerGroup(accountID, nsGroupID, userID string) error ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error) GetDNSDomain() string + StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) GetEvents(accountID, userID string) ([]*activity.Event, error) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error @@ -873,11 +874,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, } else { am.checkAndSchedulePeerLoginExpiration(account) } - am.storeEvent(userID, accountID, accountID, event, nil) + am.StoreEvent(userID, accountID, accountID, event, nil) } if oldSettings.PeerLoginExpiration != newSettings.PeerLoginExpiration { - am.storeEvent(userID, accountID, accountID, activity.AccountPeerLoginExpirationDurationUpdated, nil) + am.StoreEvent(userID, accountID, accountID, activity.AccountPeerLoginExpirationDurationUpdated, nil) am.checkAndSchedulePeerLoginExpiration(account) } @@ -939,7 +940,7 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er continue } else if statusErr.Type() == status.NotFound { 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 } else { return nil, err @@ -1280,7 +1281,7 @@ func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims return nil, err } - am.storeEvent(claims.UserId, claims.UserId, account.Id, activity.UserJoined, nil) + am.StoreEvent(claims.UserId, claims.UserId, account.Id, activity.UserJoined, nil) return account, nil } @@ -1313,7 +1314,7 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e return } log.Debugf("user %s of account %s redeemed invite", user.ID, account.Id) - am.storeEvent(userID, userID, account.Id, activity.UserJoined, nil) + am.StoreEvent(userID, userID, account.Id, activity.UserJoined, nil) }() } @@ -1463,7 +1464,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat am.updateAccountPeers(account) for _, g := range addNewGroups { if group := account.GetGroup(g); group != nil { - am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser, + am.StoreEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser, map[string]any{ "group": group.Name, "group_id": group.ID, @@ -1473,7 +1474,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat } for _, g := range removeOldGroups { if group := account.GetGroup(g); group != nil { - am.storeEvent(user.Id, user.Id, account.Id, activity.GroupRemovedFromUser, + am.StoreEvent(user.Id, user.Id, account.Id, activity.GroupRemovedFromUser, map[string]any{ "group": group.Name, "group_id": group.ID, diff --git a/management/server/dns.go b/management/server/dns.go index f90a5e9f2..4f7f95aaf 100644 --- a/management/server/dns.go +++ b/management/server/dns.go @@ -96,14 +96,14 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string for _, id := range addedGroups { group := account.GetGroup(id) meta := map[string]any{"group": group.Name, "group_id": group.ID} - am.storeEvent(userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta) + am.StoreEvent(userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta) } removedGroups := difference(oldSettings.DisabledManagementGroups, dnsSettingsToSave.DisabledManagementGroups) for _, id := range removedGroups { group := account.GetGroup(id) meta := map[string]any{"group": group.Name, "group_id": group.ID} - am.storeEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta) + am.StoreEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta) } am.updateAccountPeers(account) diff --git a/management/server/event.go b/management/server/event.go index 6ada88d6f..450d1c00d 100644 --- a/management/server/event.go +++ b/management/server/event.go @@ -36,7 +36,7 @@ func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activit return filtered, nil } -func (am *DefaultAccountManager) storeEvent(initiatorID, targetID, accountID string, activityID activity.Activity, +func (am *DefaultAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) { go func() { diff --git a/management/server/group.go b/management/server/group.go index 16f19147f..07f61425e 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -101,7 +101,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G removedPeers = difference(oldGroup.Peers, newGroup.Peers) } else { addedPeers = append(addedPeers, newGroup.Peers...) - am.storeEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta()) + am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta()) } for _, p := range addedPeers { @@ -110,7 +110,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G log.Errorf("peer %s not found under account %s while saving group", p, accountID) continue } - am.storeEvent(userID, peer.ID, accountID, activity.GroupAddedToPeer, + am.StoreEvent(userID, peer.ID, accountID, activity.GroupAddedToPeer, map[string]any{ "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(), "peer_fqdn": peer.FQDN(am.GetDNSDomain()), @@ -123,7 +123,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G log.Errorf("peer %s not found under account %s while saving group", p, accountID) continue } - am.storeEvent(userID, peer.ID, accountID, activity.GroupRemovedFromPeer, + am.StoreEvent(userID, peer.ID, accountID, activity.GroupRemovedFromPeer, map[string]any{ "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(), "peer_fqdn": peer.FQDN(am.GetDNSDomain()), @@ -241,7 +241,7 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) return err } - am.storeEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta()) + am.StoreEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta()) am.updateAccountPeers(account) diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index ea4a18f56..46b8551c5 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -67,6 +67,7 @@ type MockAccountManager struct { CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error) GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) GetDNSDomainFunc func() string + StoreEventFunc func(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) GetEventsFunc func(accountID, userID string) ([]*activity.Event, error) GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error) SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *server.DNSSettings) error @@ -592,3 +593,10 @@ func (am *MockAccountManager) GetAllConnectedPeers() (map[string]struct{}, error } return nil, status.Errorf(codes.Unimplemented, "method GetAllConnectedPeers is not implemented") } + +// StoreEvent mocks StoreEvent of the AccountManager interface +func (am *MockAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) { + if am.StoreEventFunc != nil { + am.StoreEventFunc(initiatorID, targetID, accountID, activityID, meta) + } +} diff --git a/management/server/nameserver.go b/management/server/nameserver.go index 8ae71dbae..807adf28a 100644 --- a/management/server/nameserver.go +++ b/management/server/nameserver.go @@ -76,7 +76,7 @@ func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, d am.updateAccountPeers(account) - am.storeEvent(userID, newNSGroup.ID, accountID, activity.NameserverGroupCreated, newNSGroup.EventMeta()) + am.StoreEvent(userID, newNSGroup.ID, accountID, activity.NameserverGroupCreated, newNSGroup.EventMeta()) return newNSGroup.Copy(), nil } @@ -111,7 +111,7 @@ func (am *DefaultAccountManager) SaveNameServerGroup(accountID, userID string, n am.updateAccountPeers(account) - am.storeEvent(userID, nsGroupToSave.ID, accountID, activity.NameserverGroupUpdated, nsGroupToSave.EventMeta()) + am.StoreEvent(userID, nsGroupToSave.ID, accountID, activity.NameserverGroupUpdated, nsGroupToSave.EventMeta()) return nil } @@ -141,7 +141,7 @@ func (am *DefaultAccountManager) DeleteNameServerGroup(accountID, nsGroupID, use am.updateAccountPeers(account) - am.storeEvent(userID, nsGroup.ID, accountID, activity.NameserverGroupDeleted, nsGroup.EventMeta()) + am.StoreEvent(userID, nsGroup.ID, accountID, activity.NameserverGroupDeleted, nsGroup.EventMeta()) return nil } diff --git a/management/server/peer.go b/management/server/peer.go index 33c9430fc..963bb3e80 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -310,7 +310,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe if !update.SSHEnabled { event = activity.PeerSSHDisabled } - am.storeEvent(userID, peer.IP.String(), accountID, event, peer.EventMeta(am.GetDNSDomain())) + am.StoreEvent(userID, peer.IP.String(), accountID, event, peer.EventMeta(am.GetDNSDomain())) } if peer.Name != update.Name { @@ -325,7 +325,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe peer.DNSLabel = newLabel - am.storeEvent(userID, peer.ID, accountID, activity.PeerRenamed, peer.EventMeta(am.GetDNSDomain())) + am.StoreEvent(userID, peer.ID, accountID, activity.PeerRenamed, peer.EventMeta(am.GetDNSDomain())) } if peer.LoginExpirationEnabled != update.LoginExpirationEnabled { @@ -340,7 +340,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe if !update.LoginExpirationEnabled { event = activity.PeerLoginExpirationDisabled } - am.storeEvent(userID, peer.IP.String(), accountID, event, peer.EventMeta(am.GetDNSDomain())) + am.StoreEvent(userID, peer.IP.String(), accountID, event, peer.EventMeta(am.GetDNSDomain())) if peer.AddedWithSSOLogin() && peer.LoginExpirationEnabled && account.Settings.PeerLoginExpirationEnabled { am.checkAndSchedulePeerLoginExpiration(account) @@ -394,7 +394,7 @@ func (am *DefaultAccountManager) deletePeers(account *Account, peerIDs []string, }, }) am.peersUpdateManager.CloseChannel(peer.ID) - am.storeEvent(userID, peer.ID, account.Id, activity.PeerRemovedByUser, peer.EventMeta(am.GetDNSDomain())) + am.StoreEvent(userID, peer.ID, account.Id, activity.PeerRemovedByUser, peer.EventMeta(am.GetDNSDomain())) } return nil @@ -590,7 +590,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* opEvent.TargetID = newPeer.ID opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) - am.storeEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) + am.StoreEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) am.updateAccountPeers(account) @@ -686,7 +686,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, updateRemotePeers = true shouldStoreAccount = true - am.storeEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) + am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) } peer, updated := updatePeerMeta(peer, login.Meta, account) diff --git a/management/server/policy.go b/management/server/policy.go index b7b5b331c..24188f93c 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -353,7 +353,7 @@ func (am *DefaultAccountManager) SavePolicy(accountID, userID string, policy *Po if exists { action = activity.PolicyUpdated } - am.storeEvent(userID, policy.ID, accountID, action, policy.EventMeta()) + am.StoreEvent(userID, policy.ID, accountID, action, policy.EventMeta()) am.updateAccountPeers(account) @@ -380,7 +380,7 @@ func (am *DefaultAccountManager) DeletePolicy(accountID, policyID, userID string return err } - am.storeEvent(userID, policy.ID, accountID, activity.PolicyRemoved, policy.EventMeta()) + am.StoreEvent(userID, policy.ID, accountID, activity.PolicyRemoved, policy.EventMeta()) am.updateAccountPeers(account) diff --git a/management/server/route.go b/management/server/route.go index 6b5aa982d..14b5bef3f 100644 --- a/management/server/route.go +++ b/management/server/route.go @@ -4,11 +4,12 @@ import ( "net/netip" "unicode/utf8" + "github.com/rs/xid" + "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" - "github.com/rs/xid" ) // GetRoute gets a route object from account and route IDs @@ -186,7 +187,7 @@ func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, am.updateAccountPeers(account) - am.storeEvent(userID, newRoute.ID, accountID, activity.RouteCreated, newRoute.EventMeta()) + am.StoreEvent(userID, newRoute.ID, accountID, activity.RouteCreated, newRoute.EventMeta()) return &newRoute, nil } @@ -247,7 +248,7 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave am.updateAccountPeers(account) - am.storeEvent(userID, routeToSave.ID, accountID, activity.RouteUpdated, routeToSave.EventMeta()) + am.StoreEvent(userID, routeToSave.ID, accountID, activity.RouteUpdated, routeToSave.EventMeta()) return nil } @@ -273,7 +274,7 @@ func (am *DefaultAccountManager) DeleteRoute(accountID, routeID, userID string) return err } - am.storeEvent(userID, routy.ID, accountID, activity.RouteRemoved, routy.EventMeta()) + am.StoreEvent(userID, routy.ID, accountID, activity.RouteRemoved, routy.EventMeta()) am.updateAccountPeers(account) diff --git a/management/server/setupkey.go b/management/server/setupkey.go index a33f537a7..3bd14b61e 100644 --- a/management/server/setupkey.go +++ b/management/server/setupkey.go @@ -235,12 +235,12 @@ func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string return nil, status.Errorf(status.Internal, "failed adding account key") } - am.storeEvent(userID, setupKey.Id, accountID, activity.SetupKeyCreated, setupKey.EventMeta()) + am.StoreEvent(userID, setupKey.Id, accountID, activity.SetupKeyCreated, setupKey.EventMeta()) for _, g := range setupKey.AutoGroups { group := account.GetGroup(g) if group != nil { - am.storeEvent(userID, setupKey.Id, accountID, activity.GroupAddedToSetupKey, + am.StoreEvent(userID, setupKey.Id, accountID, activity.GroupAddedToSetupKey, map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": setupKey.Name}) } else { log.Errorf("group %s not found while saving setup key activity event of account %s", g, account.Id) @@ -292,7 +292,7 @@ func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *Setup } if !oldKey.Revoked && newKey.Revoked { - am.storeEvent(userID, newKey.Id, accountID, activity.SetupKeyRevoked, newKey.EventMeta()) + am.StoreEvent(userID, newKey.Id, accountID, activity.SetupKeyRevoked, newKey.EventMeta()) } defer func() { @@ -301,7 +301,7 @@ func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *Setup for _, g := range removedGroups { group := account.GetGroup(g) if group != nil { - am.storeEvent(userID, oldKey.Id, accountID, activity.GroupRemovedFromSetupKey, + am.StoreEvent(userID, oldKey.Id, accountID, activity.GroupRemovedFromSetupKey, map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": newKey.Name}) } else { log.Errorf("group %s not found while saving setup key activity event of account %s", g, account.Id) @@ -312,7 +312,7 @@ func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *Setup for _, g := range addedGroups { group := account.GetGroup(g) if group != nil { - am.storeEvent(userID, oldKey.Id, accountID, activity.GroupAddedToSetupKey, + am.StoreEvent(userID, oldKey.Id, accountID, activity.GroupAddedToSetupKey, map[string]any{"group": group.Name, "group_id": group.ID, "setupkey": newKey.Name}) } else { log.Errorf("group %s not found while saving setup key activity event of account %s", g, account.Id) diff --git a/management/server/user.go b/management/server/user.go index 71d3cd7f4..2eaea88eb 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -211,7 +211,7 @@ func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUs } meta := map[string]any{"name": newUser.ServiceUserName} - am.storeEvent(initiatorUserID, newUser.Id, accountID, activity.ServiceUserCreated, meta) + am.StoreEvent(initiatorUserID, newUser.Id, accountID, activity.ServiceUserCreated, meta) return &UserInfo{ ID: newUser.Id, @@ -312,7 +312,7 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite return nil, err } - am.storeEvent(userID, newUser.Id, accountID, activity.UserInvited, nil) + am.StoreEvent(userID, newUser.Id, accountID, activity.UserInvited, nil) return newUser.ToUserInfo(idpUser) } @@ -349,7 +349,7 @@ func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) ( if newLogin { meta := map[string]any{"timestamp": claims.LastLogin} - am.storeEvent(claims.UserId, claims.UserId, account.Id, activity.DashboardLogin, meta) + am.StoreEvent(claims.UserId, claims.UserId, account.Id, activity.DashboardLogin, meta) } return user, nil @@ -357,7 +357,7 @@ func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) ( func (am *DefaultAccountManager) deleteServiceUser(account *Account, initiatorUserID string, targetUser *User) { meta := map[string]any{"name": targetUser.ServiceUserName} - am.storeEvent(initiatorUserID, targetUser.Id, account.Id, activity.ServiceUserDeleted, meta) + am.StoreEvent(initiatorUserID, targetUser.Id, account.Id, activity.ServiceUserDeleted, meta) delete(account.Users, targetUser.Id) } @@ -428,7 +428,7 @@ func (am *DefaultAccountManager) deleteRegularUser(account *Account, initiatorUs } meta := map[string]any{"name": tuName, "email": tuEmail} - am.storeEvent(initiatorUserID, targetUserID, account.Id, activity.UserDeleted, meta) + am.StoreEvent(initiatorUserID, targetUserID, account.Id, activity.UserDeleted, meta) am.updateAccountPeers(account) @@ -484,7 +484,7 @@ func (am *DefaultAccountManager) InviteUser(accountID string, initiatorUserID st return err } - am.storeEvent(initiatorUserID, user.ID, accountID, activity.UserInvited, nil) + am.StoreEvent(initiatorUserID, user.ID, accountID, activity.UserInvited, nil) return nil } @@ -534,7 +534,7 @@ func (am *DefaultAccountManager) CreatePAT(accountID string, initiatorUserID str } meta := map[string]any{"name": pat.Name, "is_service_user": targetUser.IsServiceUser, "user_name": targetUser.ServiceUserName} - am.storeEvent(initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenCreated, meta) + am.StoreEvent(initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenCreated, meta) return pat, nil } @@ -578,7 +578,7 @@ func (am *DefaultAccountManager) DeletePAT(accountID string, initiatorUserID str } meta := map[string]any{"name": pat.Name, "is_service_user": targetUser.IsServiceUser, "user_name": targetUser.ServiceUserName} - am.storeEvent(initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenDeleted, meta) + am.StoreEvent(initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenDeleted, meta) delete(targetUser.PATs, tokenID) @@ -739,15 +739,15 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd defer func() { if oldUser.IsBlocked() != update.IsBlocked() { if update.IsBlocked() { - am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.UserBlocked, nil) + am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.UserBlocked, nil) } else { - am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.UserUnblocked, nil) + am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.UserUnblocked, nil) } } // store activity logs if oldUser.Role != newUser.Role { - am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.UserRoleUpdated, map[string]any{"role": newUser.Role}) + am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.UserRoleUpdated, map[string]any{"role": newUser.Role}) } if update.AutoGroups != nil { @@ -756,7 +756,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd for _, g := range removedGroups { group := account.GetGroup(g) if group != nil { - am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.GroupRemovedFromUser, + am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.GroupRemovedFromUser, map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName}) } else { log.Errorf("group %s not found while saving user activity event of account %s", g, account.Id) @@ -766,7 +766,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd for _, g := range addedGroups { group := account.GetGroup(g) if group != nil { - am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.GroupAddedToUser, + am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.GroupAddedToUser, map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName}) } } @@ -914,7 +914,7 @@ func (am *DefaultAccountManager) expireAndUpdatePeers(account *Account, peers [] if err := am.Store.SavePeerStatus(account.Id, peer.ID, *peer.Status); err != nil { return err } - am.storeEvent( + am.StoreEvent( peer.UserID, peer.ID, account.Id, activity.PeerLoginExpired, peer.EventMeta(am.GetDNSDomain()), ) From a40261ff7e9535f5962490e192536f1609b06dbf Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Thu, 9 Nov 2023 17:15:59 +0100 Subject: [PATCH 06/59] Log access control error (#1299) --- management/server/http/middleware/access_control.go | 1 + 1 file changed, 1 insertion(+) diff --git a/management/server/http/middleware/access_control.go b/management/server/http/middleware/access_control.go index fc362ea80..434f2f644 100644 --- a/management/server/http/middleware/access_control.go +++ b/management/server/http/middleware/access_control.go @@ -41,6 +41,7 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler { user, err := a.getUser(claims) if err != nil { + log.Errorf("failed to get user from claims: %s", err) util.WriteError(status.Errorf(status.Unauthorized, "invalid JWT"), w) return } From 9e8725618ea33344aa4b5aa01446fced029a1d6a Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Fri, 10 Nov 2023 16:33:13 +0100 Subject: [PATCH 07/59] Extend linter rules (#1300) - dupword checks for duplicate words in the source code - durationcheck checks for two durations multiplied together - forbidigo forbids identifiers - mirror reports wrong mirror patterns of bytes/strings usage - misspell finds commonly misspelled English words in comments - predeclared finds code that shadows one of Go's predeclared identifiers - thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers --- .golangci.yaml | 17 ++++++++++++++++- client/android/login.go | 4 ++-- client/cmd/login.go | 4 ++-- client/cmd/root.go | 2 +- client/cmd/testutil.go | 4 ++++ client/firewall/iptables/manager_linux_test.go | 1 + client/internal/dns/server_test.go | 1 + client/internal/engine.go | 2 +- client/internal/peer/status.go | 8 ++++---- .../routemanager/systemops_nonandroid_test.go | 4 ++-- client/system/info_darwin.go | 3 +-- client/system/info_linux.go | 7 ++++--- client/ui/client_ui.go | 2 +- iface/module_linux_test.go | 2 ++ management/client/client_test.go | 4 +++- management/cmd/root.go | 2 +- management/server/account.go | 2 +- management/server/account_test.go | 5 +++++ management/server/dns_test.go | 3 +++ management/server/event_test.go | 1 + management/server/file_store_test.go | 1 + management/server/http/groups_handler_test.go | 2 +- management/server/http/peers_handler_test.go | 3 +-- .../server/http/setupkeys_handler_test.go | 1 + management/server/idp/auth0_test.go | 1 + management/server/idp/okta.go | 1 - management/server/jwtclaims/extractor_test.go | 6 ++++++ management/server/management_proto_test.go | 1 + management/server/mock_server/account_mock.go | 4 ++-- management/server/nameserver_test.go | 3 +++ management/server/route_test.go | 2 ++ management/server/setupkey_test.go | 1 + management/server/store_test.go | 2 ++ management/server/telemetry/grpc_metrics.go | 4 ++-- management/server/turncredentials_test.go | 8 +++++--- sharedsock/example/main.go | 7 ++++--- signal/cmd/root.go | 2 +- 37 files changed, 90 insertions(+), 37 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 5034db708..b637c7ed1 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -32,9 +32,16 @@ linters: - unused # checks for unused constants, variables, functions and types ## disable by default but the have interesting results so lets add them - bodyclose # checks whether HTTP response body is closed successfully + - dupword # dupword checks for duplicate words in the source code + - durationcheck # durationcheck checks for two durations multiplied together + - forbidigo # forbidigo forbids identifiers + - mirror # mirror reports wrong mirror patterns of bytes/strings usage + - misspell # misspess finds commonly misspelled English words in comments - nilerr # finds the code that returns nil even if it checks that the error is not nil - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - predeclared # predeclared finds code that shadows one of Go's predeclared identifiers - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers. - wastedassign # wastedassign finds wasted assignment statements issues: # Maximum count of issues with the same text. @@ -43,12 +50,20 @@ issues: max-same-issues: 5 exclude-rules: + # allow fmt + - path: management/cmd/root.go + linters: forbidigo + - path: signal/cmd/root.go + linters: forbidigo - path: sharedsock/filter.go linters: - unused - path: client/firewall/iptables/rule.go linters: - unused + - path: test.go + linters: + - mirror - path: mock.go linters: - - nilnil \ No newline at end of file + - nilnil diff --git a/client/android/login.go b/client/android/login.go index afd61055f..3840c75c1 100644 --- a/client/android/login.go +++ b/client/android/login.go @@ -205,8 +205,8 @@ func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, err go urlOpener.Open(flowInfo.VerificationURIComplete) - waitTimeout := time.Duration(flowInfo.ExpiresIn) - waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second) + waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second + waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout) defer cancel() tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) if err != nil { diff --git a/client/cmd/login.go b/client/cmd/login.go index 2ddab46f3..b64355649 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -177,8 +177,8 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int openURL(cmd, flowInfo.VerificationURIComplete, flowInfo.UserCode) - waitTimeout := time.Duration(flowInfo.ExpiresIn) - waitCTX, c := context.WithTimeout(context.TODO(), waitTimeout*time.Second) + waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second + waitCTX, c := context.WithTimeout(context.TODO(), waitTimeout) defer c() tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) diff --git a/client/cmd/root.go b/client/cmd/root.go index 770bd21fa..24c027d0c 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -92,7 +92,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL)) rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location") rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level") - rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout") + rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout") rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)") rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.") rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device") diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index 47ae9ddb4..c90faa24a 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -22,6 +22,7 @@ import ( ) func startTestingServices(t *testing.T) string { + t.Helper() config := &mgmt.Config{} _, err := util.ReadJson("../testdata/management.json", config) if err != nil { @@ -44,6 +45,7 @@ func startTestingServices(t *testing.T) string { } func startSignal(t *testing.T) (*grpc.Server, net.Listener) { + t.Helper() lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) @@ -60,6 +62,7 @@ func startSignal(t *testing.T) (*grpc.Server, net.Listener) { } func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) { + t.Helper() lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) @@ -98,6 +101,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste func startClientDaemon( t *testing.T, ctx context.Context, managementURL, configPath string, ) (*grpc.Server, net.Listener) { + t.Helper() lis, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) diff --git a/client/firewall/iptables/manager_linux_test.go b/client/firewall/iptables/manager_linux_test.go index 2d2013aa2..90375d3e2 100644 --- a/client/firewall/iptables/manager_linux_test.go +++ b/client/firewall/iptables/manager_linux_test.go @@ -206,6 +206,7 @@ func TestIptablesManagerIPSet(t *testing.T) { } func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName string, mustExists bool, rulespec ...string) { + t.Helper() exists, err := ipv4Client.Exists("filter", chainName, rulespec...) require.NoError(t, err, "failed to check rule") require.Falsef(t, !exists && mustExists, "rule '%v' does not exist", rulespec) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 51cc9c8c4..62b4e1867 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -771,6 +771,7 @@ func TestDNSPermanent_matchOnly(t *testing.T) { } func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { + t.Helper() ov := os.Getenv("NB_WG_KERNEL_DISABLED") defer os.Setenv("NB_WG_KERNEL_DISABLED", ov) diff --git a/client/internal/engine.go b/client/internal/engine.go index a81fabde6..35fd822f2 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -640,7 +640,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error { if config.GetSshConfig() != nil && config.GetSshConfig().GetSshPubKey() != nil { err := e.sshServer.AddAuthorizedKey(config.WgPubKey, string(config.GetSshConfig().GetSshPubKey())) if err != nil { - log.Warnf("failed adding authroized key to SSH DefaultServer %v", err) + log.Warnf("failed adding authorized key to SSH DefaultServer %v", err) } } } diff --git a/client/internal/peer/status.go b/client/internal/peer/status.go index f75991d85..c69354eac 100644 --- a/client/internal/peer/status.go +++ b/client/internal/peer/status.go @@ -174,13 +174,13 @@ func (d *Status) UpdatePeerState(receivedState State) error { return nil } -func shouldSkipNotify(new, curr State) bool { +func shouldSkipNotify(received, curr State) bool { switch { - case new.ConnStatus == StatusConnecting: + case received.ConnStatus == StatusConnecting: return true - case new.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusConnecting: + case received.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusConnecting: return true - case new.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusDisconnected: + case received.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusDisconnected: return curr.IP != "" default: return false diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 68c0e1d26..bb31834d1 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -123,7 +123,7 @@ func TestGetExistingRIBRouteGateway(t *testing.T) { func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) - fmt.Println("defaultGateway: ", defaultGateway) + t.Log("defaultGateway: ", defaultGateway) if err != nil { t.Fatal("shouldn't return error when fetching the gateway: ", err) } @@ -207,7 +207,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { // route should either not have been added or should have been removed // In case of already existing route, it should not have been added (but still exist) ok, err := existsInRouteTable(testCase.prefix) - fmt.Println("Buffer string: ", buf.String()) + t.Log("Buffer string: ", buf.String()) require.NoError(t, err, "should not return err") if !strings.Contains(buf.String(), "because it already exists") { require.False(t, ok, "route should not exist") diff --git a/client/system/info_darwin.go b/client/system/info_darwin.go index e153539bb..621a82691 100644 --- a/client/system/info_darwin.go +++ b/client/system/info_darwin.go @@ -3,7 +3,6 @@ package system import ( "bytes" "context" - "fmt" "os" "os/exec" "runtime" @@ -21,7 +20,7 @@ func GetInfo(ctx context.Context) *Info { utsname := unix.Utsname{} err := unix.Uname(&utsname) if err != nil { - fmt.Println("getInfo:", err) + log.Warnf("getInfo: %s", err) } sysName := string(bytes.Split(utsname.Sysname[:], []byte{0})[0]) machine := string(bytes.Split(utsname.Machine[:], []byte{0})[0]) diff --git a/client/system/info_linux.go b/client/system/info_linux.go index 7d43fc035..a4ab9f931 100644 --- a/client/system/info_linux.go +++ b/client/system/info_linux.go @@ -6,13 +6,14 @@ package system import ( "bytes" "context" - "fmt" "os" "os/exec" "runtime" "strings" "time" + log "github.com/sirupsen/logrus" + "github.com/netbirdio/netbird/version" ) @@ -67,7 +68,7 @@ func _getInfo() string { cmd.Stderr = &stderr err := cmd.Run() if err != nil { - fmt.Println("getInfo:", err) + log.Warnf("getInfo: %s", err) } return out.String() } @@ -81,7 +82,7 @@ func _getReleaseInfo() string { cmd.Stderr = &stderr err := cmd.Run() if err != nil { - fmt.Println("getReleaseInfo:", err) + log.Warnf("geucwReleaseInfo: %s", err) } return out.String() } diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go index e66d03d95..da230e214 100644 --- a/client/ui/client_ui.go +++ b/client/ui/client_ui.go @@ -69,7 +69,7 @@ func main() { a.Run() } else { if err := checkPIDFile(); err != nil { - fmt.Println(err) + log.Errorf("check PID file: %v", err) return } systray.Run(client.onTrayReady, client.onTrayExit) diff --git a/iface/module_linux_test.go b/iface/module_linux_test.go index d61245549..97e9b1f78 100644 --- a/iface/module_linux_test.go +++ b/iface/module_linux_test.go @@ -132,6 +132,7 @@ func resetGlobals() { } func createFiles(t *testing.T) (string, []module) { + t.Helper() writeFile := func(path, text string) { if err := os.WriteFile(path, []byte(text), 0644); err != nil { t.Fatal(err) @@ -167,6 +168,7 @@ func createFiles(t *testing.T) (string, []module) { } func getRandomLoadedModule(t *testing.T) (string, error) { + t.Helper() f, err := os.Open("/proc/modules") if err != nil { return "", err diff --git a/management/client/client_test.go b/management/client/client_test.go index 889b7a131..ab8df99df 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -31,6 +31,7 @@ import ( const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB" func startManagement(t *testing.T) (*grpc.Server, net.Listener) { + t.Helper() level, _ := log.ParseLevel("debug") log.SetLevel(level) @@ -81,6 +82,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { } func startMockManagement(t *testing.T) (*grpc.Server, net.Listener, *mock_server.ManagementServiceServerMock, wgtypes.Key) { + t.Helper() lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) @@ -168,7 +170,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) { t.Error("expecting err on unregistered login, got nil") } if s, ok := status.FromError(err); !ok || s.Code() != codes.PermissionDenied { - t.Errorf("expecting err code %d denied on on unregistered login got %d", codes.PermissionDenied, s.Code()) + t.Errorf("expecting err code %d denied on unregistered login got %d", codes.PermissionDenied, s.Code()) } } diff --git a/management/cmd/root.go b/management/cmd/root.go index de8b5b8b3..1c9b95bfd 100644 --- a/management/cmd/root.go +++ b/management/cmd/root.go @@ -67,7 +67,7 @@ func init() { rootCmd.MarkFlagRequired("config") //nolint rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "") - rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout") + rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout") rootCmd.AddCommand(mgmtCmd) migrationCmd.PersistentFlags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location") diff --git a/management/server/account.go b/management/server/account.go index 729660650..7ec67bfa7 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -1574,7 +1574,7 @@ func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, er func isDomainValid(domain string) bool { re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) - return re.Match([]byte(domain)) + return re.MatchString(domain) } // GetDNSDomain returns the configured dnsDomain diff --git a/management/server/account_test.go b/management/server/account_test.go index 0d707bde9..181d0447b 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -25,6 +25,7 @@ import ( ) func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { + t.Helper() peer := &Peer{ Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=", Name: "test-host@netbird.io", @@ -52,6 +53,7 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac } func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy string, domain string, expectedUsers []string) { + t.Helper() if len(account.Peers) != 0 { t.Errorf("expected account to have len(Peers) = %v, got %v", 0, len(account.Peers)) } @@ -1131,6 +1133,7 @@ func TestAccountManager_DeletePeer(t *testing.T) { } func getEvent(t *testing.T, accountID string, manager AccountManager, eventType activity.Activity) *activity.Event { + t.Helper() for { select { case <-time.After(time.Second): @@ -2038,6 +2041,7 @@ func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) { } func createManager(t *testing.T) (*DefaultAccountManager, error) { + t.Helper() store, err := createStore(t) if err != nil { return nil, err @@ -2047,6 +2051,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) { } func createStore(t *testing.T) (Store, error) { + t.Helper() dataDir := t.TempDir() store, err := NewStoreFromJson(dataDir, nil) if err != nil { diff --git a/management/server/dns_test.go b/management/server/dns_test.go index a2c9d3aa2..54044cf93 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -186,6 +186,7 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) { } func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { + t.Helper() store, err := createDNSStore(t) if err != nil { return nil, err @@ -195,6 +196,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { } func createDNSStore(t *testing.T) (Store, error) { + t.Helper() dataDir := t.TempDir() store, err := NewStoreFromJson(dataDir, nil) if err != nil { @@ -205,6 +207,7 @@ func createDNSStore(t *testing.T) (Store, error) { } func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { + t.Helper() peer1 := &Peer{ Key: dnsPeer1Key, Name: "test-host1@netbird.io", diff --git a/management/server/event_test.go b/management/server/event_test.go index 320728eef..401c80759 100644 --- a/management/server/event_test.go +++ b/management/server/event_test.go @@ -11,6 +11,7 @@ import ( func generateAndStoreEvents(t *testing.T, manager *DefaultAccountManager, typ activity.Activity, initiatorID, targetID, accountID string, count int) { + t.Helper() for i := 0; i < count; i++ { _, err := manager.eventStore.Save(&activity.Event{ Timestamp: time.Now().UTC(), diff --git a/management/server/file_store_test.go b/management/server/file_store_test.go index 705e9f149..206873d20 100644 --- a/management/server/file_store_test.go +++ b/management/server/file_store_test.go @@ -580,6 +580,7 @@ func TestFileStore_SavePeerStatus(t *testing.T) { } func newStore(t *testing.T) *FileStore { + t.Helper() store, err := NewFileStore(t.TempDir(), nil) if err != nil { t.Errorf("failed creating a new store") diff --git a/management/server/http/groups_handler_test.go b/management/server/http/groups_handler_test.go index aad03d50b..5f40be7a9 100644 --- a/management/server/http/groups_handler_test.go +++ b/management/server/http/groups_handler_test.go @@ -230,7 +230,7 @@ func TestWriteGroup(t *testing.T) { expectedBody: false, }, { - name: "Write Group PUT not not change Issue", + name: "Write Group PUT not change Issue", requestType: http.MethodPut, requestPath: "/api/groups/id-jwt-group", requestBody: bytes.NewBuffer( diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index e61c6905c..673cd90c5 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -3,7 +3,6 @@ package http import ( "bytes" "encoding/json" - "fmt" "io" "net" "net/http" @@ -232,7 +231,7 @@ func TestGetPeers(t *testing.T) { } } - fmt.Println(got) + t.Log(got) assert.Equal(t, got.Name, tc.expectedPeer.Name) assert.Equal(t, got.Version, tc.expectedPeer.Meta.WtVersion) diff --git a/management/server/http/setupkeys_handler_test.go b/management/server/http/setupkeys_handler_test.go index d931a5e0b..7b68479ed 100644 --- a/management/server/http/setupkeys_handler_test.go +++ b/management/server/http/setupkeys_handler_test.go @@ -220,6 +220,7 @@ func TestSetupKeysHandlers(t *testing.T) { } func assertKeys(t *testing.T, got *api.SetupKey, expected *api.SetupKey) { + t.Helper() // this comparison is done manually because when converting to JSON dates formatted differently // assert.Equal(t, got.UpdatedAt, tc.expectedSetupKey.UpdatedAt) //doesn't work assert.WithinDurationf(t, got.UpdatedAt, expected.UpdatedAt, 0, "") diff --git a/management/server/idp/auth0_test.go b/management/server/idp/auth0_test.go index 63c634d4e..febc0ab89 100644 --- a/management/server/idp/auth0_test.go +++ b/management/server/idp/auth0_test.go @@ -65,6 +65,7 @@ func (mc *mockAuth0Credentials) Authenticate() (JWTToken, error) { } func newTestJWT(t *testing.T, expInt int) string { + t.Helper() now := time.Now() token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iat": now.Unix(), diff --git a/management/server/idp/okta.go b/management/server/idp/okta.go index 67341a26f..d20ee7e48 100644 --- a/management/server/idp/okta.go +++ b/management/server/idp/okta.go @@ -256,7 +256,6 @@ func (om *OktaManager) InviteUserByID(_ string) error { func (om *OktaManager) DeleteUser(userID string) error { resp, err := om.client.User.DeactivateOrDeleteUser(context.Background(), userID, nil) if err != nil { - fmt.Println(err.Error()) return err } diff --git a/management/server/jwtclaims/extractor_test.go b/management/server/jwtclaims/extractor_test.go index f2cbb1ebe..e9316b194 100644 --- a/management/server/jwtclaims/extractor_test.go +++ b/management/server/jwtclaims/extractor_test.go @@ -11,6 +11,7 @@ import ( ) func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audience string) *http.Request { + t.Helper() const layout = "2006-01-02T15:04:05.999Z" claimMaps := jwt.MapClaims{} @@ -143,6 +144,7 @@ func TestExtractClaimsFromRequestContext(t *testing.T) { } func TestExtractClaimsSetOptions(t *testing.T) { + t.Helper() type test struct { name string extractor *ClaimsExtractor @@ -153,6 +155,7 @@ func TestExtractClaimsSetOptions(t *testing.T) { name: "No custom options", extractor: NewClaimsExtractor(), check: func(t *testing.T, c test) { + t.Helper() if c.extractor.authAudience != "" { t.Error("audience should be empty") return @@ -172,6 +175,7 @@ func TestExtractClaimsSetOptions(t *testing.T) { name: "Custom audience", extractor: NewClaimsExtractor(WithAudience("https://login/")), check: func(t *testing.T, c test) { + t.Helper() if c.extractor.authAudience != "https://login/" { t.Errorf("audience expected %s, got %s", "https://login/", c.extractor.authAudience) return @@ -183,6 +187,7 @@ func TestExtractClaimsSetOptions(t *testing.T) { name: "Custom user id claim", extractor: NewClaimsExtractor(WithUserIDClaim("customUserId")), check: func(t *testing.T, c test) { + t.Helper() if c.extractor.userIDClaim != "customUserId" { t.Errorf("user id claim expected %s, got %s", "customUserId", c.extractor.userIDClaim) return @@ -199,6 +204,7 @@ func TestExtractClaimsSetOptions(t *testing.T) { } })), check: func(t *testing.T, c test) { + t.Helper() claims := c.extractor.FromRequestContext(&http.Request{}) if claims.UserId != "testCustomRequest" { t.Errorf("user id claim expected %s, got %s", "testCustomRequest", claims.UserId) diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index 06fc6669d..000f6c9de 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -400,6 +400,7 @@ func TestServer_GetDeviceAuthorizationFlow(t *testing.T) { } func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error) { + t.Helper() lis, err := net.Listen("tcp", "localhost:0") if err != nil { return nil, "", err diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 46b8551c5..33ce5498c 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -355,7 +355,7 @@ func (am *MockAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) err if am.UpdatePeerSSHKeyFunc != nil { return am.UpdatePeerSSHKeyFunc(peerID, sshKey) } - return status.Errorf(codes.Unimplemented, "method UpdatePeerSSHKey is is not implemented") + return status.Errorf(codes.Unimplemented, "method UpdatePeerSSHKey is not implemented") } // UpdatePeer mocks UpdatePeerFunc function of the account manager @@ -363,7 +363,7 @@ func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server. if am.UpdatePeerFunc != nil { return am.UpdatePeerFunc(accountID, userID, peer) } - return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is is not implemented") + return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is not implemented") } // CreateRoute mock implementation of CreateRoute from server.AccountManager interface diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 6210ae538..6a08706ca 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -741,6 +741,7 @@ func TestGetNameServerGroup(t *testing.T) { } func createNSManager(t *testing.T) (*DefaultAccountManager, error) { + t.Helper() store, err := createNSStore(t) if err != nil { return nil, err @@ -750,6 +751,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) { } func createNSStore(t *testing.T) (Store, error) { + t.Helper() dataDir := t.TempDir() store, err := NewStoreFromJson(dataDir, nil) if err != nil { @@ -760,6 +762,7 @@ func createNSStore(t *testing.T) (Store, error) { } func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { + t.Helper() peer1 := &Peer{ Key: nsGroupPeer1Key, Name: "test-host1@netbird.io", diff --git a/management/server/route_test.go b/management/server/route_test.go index efd73d6c2..e55e76928 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -1007,6 +1007,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) { } func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { + t.Helper() store, err := createRouterStore(t) if err != nil { return nil, err @@ -1016,6 +1017,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { } func createRouterStore(t *testing.T) (Store, error) { + t.Helper() dataDir := t.TempDir() store, err := NewStoreFromJson(dataDir, nil) if err != nil { diff --git a/management/server/setupkey_test.go b/management/server/setupkey_test.go index 6da01bd82..b104a6959 100644 --- a/management/server/setupkey_test.go +++ b/management/server/setupkey_test.go @@ -237,6 +237,7 @@ func TestSetupKey_IsValid(t *testing.T) { func assertKey(t *testing.T, key *SetupKey, expectedName string, expectedRevoke bool, expectedType string, expectedUsedTimes int, expectedCreatedAt time.Time, expectedExpiresAt time.Time, expectedID string, expectedUpdatedAt time.Time, expectedAutoGroups []string) { + t.Helper() if key.Name != expectedName { t.Errorf("expected setup key to have Name %v, got %v", expectedName, key.Name) } diff --git a/management/server/store_test.go b/management/server/store_test.go index 72bbaf949..3f8c5d18b 100644 --- a/management/server/store_test.go +++ b/management/server/store_test.go @@ -14,11 +14,13 @@ type benchCase struct { } var newFs = func(b *testing.B) Store { + b.Helper() store, _ := NewFileStore(b.TempDir(), nil) return store } var newSqlite = func(b *testing.B) Store { + b.Helper() store, _ := NewSqliteStore(b.TempDir(), nil) return store } diff --git a/management/server/telemetry/grpc_metrics.go b/management/server/telemetry/grpc_metrics.go index 25789f5c7..8ad2f3bde 100644 --- a/management/server/telemetry/grpc_metrics.go +++ b/management/server/telemetry/grpc_metrics.go @@ -116,6 +116,6 @@ func (grpcMetrics *GRPCMetrics) RegisterConnectedStreams(producer func() int64) } // UpdateChannelQueueLength update the histogram that keep distribution of the update messages channel queue -func (metrics *GRPCMetrics) UpdateChannelQueueLength(len int) { - metrics.channelQueueLength.Record(metrics.ctx, int64(len)) +func (metrics *GRPCMetrics) UpdateChannelQueueLength(length int) { + metrics.channelQueueLength.Record(metrics.ctx, int64(length)) } diff --git a/management/server/turncredentials_test.go b/management/server/turncredentials_test.go index c91176ac3..a8d282266 100644 --- a/management/server/turncredentials_test.go +++ b/management/server/turncredentials_test.go @@ -4,9 +4,10 @@ import ( "crypto/hmac" "crypto/sha1" "encoding/base64" - "github.com/netbirdio/netbird/util" "testing" "time" + + "github.com/netbirdio/netbird/util" ) var TurnTestHost = &Host{ @@ -36,7 +37,7 @@ func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) { t.Errorf("expected generated TURN password not to be empty, got empty") } - validateMAC(credentials.Username, credentials.Password, []byte(secret), t) + validateMAC(t, credentials.Username, credentials.Password, []byte(secret)) } @@ -112,7 +113,8 @@ func TestTimeBasedAuthSecretsManager_CancelRefresh(t *testing.T) { } } -func validateMAC(username string, actualMAC string, key []byte, t *testing.T) { +func validateMAC(t *testing.T, username string, actualMAC string, key []byte) { + t.Helper() mac := hmac.New(sha1.New, key) _, err := mac.Write([]byte(username)) diff --git a/sharedsock/example/main.go b/sharedsock/example/main.go index 7c879b4c9..9384d2b1c 100644 --- a/sharedsock/example/main.go +++ b/sharedsock/example/main.go @@ -2,10 +2,11 @@ package main import ( "context" - "github.com/netbirdio/netbird/sharedsock" - log "github.com/sirupsen/logrus" "os" "os/signal" + + "github.com/netbirdio/netbird/sharedsock" + log "github.com/sirupsen/logrus" ) func main() { @@ -16,7 +17,7 @@ func main() { panic(err) } - log.Infof("attached to to the raw socket on port %d", port) + log.Infof("attached to the raw socket on port %d", port) ctx, cancel := context.WithCancel(context.Background()) // read packets diff --git a/signal/cmd/root.go b/signal/cmd/root.go index 479579c7e..7fa75d923 100644 --- a/signal/cmd/root.go +++ b/signal/cmd/root.go @@ -47,7 +47,7 @@ func init() { } rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "") - rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout") + rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout") rootCmd.AddCommand(runCmd) } From d1d01a0611771b5604bf5806f385ebb9d70c58df Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Mon, 13 Nov 2023 14:04:18 +0100 Subject: [PATCH 08/59] Extend AccountManager with external cache and group/user management methods (#1289) --- management/server/account.go | 37 ++++++-- management/server/mock_server/account_mock.go | 36 ++++++- management/server/user.go | 68 ++++++++++++- management/server/user_test.go | 95 ++++++++++++++++++- 4 files changed, 217 insertions(+), 19 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 7ec67bfa7..25b034668 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -42,6 +42,8 @@ const ( DefaultPeerLoginExpiration = 24 * time.Hour ) +type ExternalCacheManager cache.CacheInterface[*idp.UserData] + func cacheEntryExpiration() time.Duration { r := rand.Intn(int(CacheExpirationMax.Milliseconds()-CacheExpirationMin.Milliseconds())) + int(CacheExpirationMin.Milliseconds()) return time.Duration(r) * time.Millisecond @@ -57,12 +59,14 @@ type AccountManager interface { InviteUser(accountID string, initiatorUserID string, targetUserID string) error ListSetupKeys(accountID, userID string) ([]*SetupKey, error) SaveUser(accountID, initiatorUserID string, update *User) (*UserInfo, error) + SaveOrAddUser(accountID, initiatorUserID string, update *User, addIfNotExists bool) (*UserInfo, error) GetSetupKey(accountID, userID, keyID string) (*SetupKey, error) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, *User, error) GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error) MarkPATUsed(tokenID string) error GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) + ListUsers(accountID string) ([]*User, error) GetPeers(accountID, userID string) ([]*Peer, error) MarkPeerConnected(peerKey string, connected bool) error DeletePeer(accountID, peerID, userID string) error @@ -106,6 +110,7 @@ type AccountManager interface { LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API GetAllConnectedPeers() (map[string]struct{}, error) + GetExternalCacheManager() ExternalCacheManager } type DefaultAccountManager struct { @@ -113,12 +118,13 @@ type DefaultAccountManager struct { // cacheMux and cacheLoading helps to make sure that only a single cache reload runs at a time per accountID cacheMux sync.Mutex // cacheLoading keeps the accountIDs that are currently reloading. The accountID has to be removed once cache has been reloaded - cacheLoading map[string]chan struct{} - peersUpdateManager *PeersUpdateManager - idpManager idp.Manager - cacheManager cache.CacheInterface[[]*idp.UserData] - ctx context.Context - eventStore activity.Store + cacheLoading map[string]chan struct{} + peersUpdateManager *PeersUpdateManager + idpManager idp.Manager + cacheManager cache.CacheInterface[[]*idp.UserData] + externalCacheManager ExternalCacheManager + ctx context.Context + eventStore activity.Store // singleAccountMode indicates whether the instance has a single account. // If true, then every new user will end up under the same account. @@ -817,9 +823,13 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage goCacheClient := gocache.New(CacheExpirationMax, 30*time.Minute) goCacheStore := cacheStore.NewGoCache(goCacheClient) - am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](goCacheStore)) + // TODO: what is max expiration time? Should be quite long + am.externalCacheManager = cache.New[*idp.UserData]( + cacheStore.NewGoCache(goCacheClient), + ) + if !isNil(am.idpManager) { go func() { err := am.warmupIDPCache() @@ -834,6 +844,10 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage return am, nil } +func (am *DefaultAccountManager) GetExternalCacheManager() ExternalCacheManager { + return am.externalCacheManager +} + // UpdateAccountSettings updates Account settings. // Only users with role UserRoleAdmin can update the account. // User that performs the update has to belong to the account. @@ -1095,10 +1109,15 @@ func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountI // lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Account) (*idp.UserData, error) { users := make(map[string]struct{}, len(account.Users)) + // ignore service users and users provisioned by integrations than are never logged in for _, user := range account.Users { - if !user.IsServiceUser { - users[user.Id] = struct{}{} + if user.IsServiceUser { + continue } + if user.Issued == UserIssuedIntegration && user.LastLogin.IsZero() { + continue + } + users[user.Id] = struct{}{} } log.Debugf("looking up user %s of account %s in cache", userID, account.Id) userData, err := am.lookupCache(users, account.Id) diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 33ce5498c..f6b2e1641 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -20,6 +20,7 @@ type MockAccountManager struct { GetSetupKeyFunc func(accountID, userID, keyID string) (*server.SetupKey, error) GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error) GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error) + ListUsersFunc func(accountID string) ([]*server.User, error) GetPeersFunc func(accountID, userID string) ([]*server.Peer, error) MarkPeerConnectedFunc func(peerKey string, connected bool) error DeletePeerFunc func(accountID, peerKey, userID string) error @@ -54,6 +55,7 @@ type MockAccountManager struct { SaveSetupKeyFunc func(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error) ListSetupKeysFunc func(accountID, userID string) ([]*server.SetupKey, error) SaveUserFunc func(accountID, userID string, user *server.User) (*server.UserInfo, error) + SaveOrAddUserFunc func(accountID, userID string, user *server.User, addIfNotExists bool) (*server.UserInfo, error) DeleteUserFunc func(accountID string, initiatorUserID string, targetUserID string) error CreatePATFunc func(accountID string, initiatorUserID string, targetUserId string, tokenName string, expiresIn int) (*server.PersonalAccessTokenGenerated, error) DeletePATFunc func(accountID string, initiatorUserID string, targetUserId string, tokenID string) error @@ -77,6 +79,7 @@ type MockAccountManager struct { SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error GetAllConnectedPeersFunc func() (map[string]struct{}, error) + GetExternalCacheManagerFunc func() server.ExternalCacheManager } // GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface @@ -339,7 +342,7 @@ func (am *MockAccountManager) UpdatePeerMeta(peerID string, meta server.PeerSyst if am.UpdatePeerMetaFunc != nil { return am.UpdatePeerMetaFunc(peerID, meta) } - return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc is not implemented") + return status.Errorf(codes.Unimplemented, "method UpdatePeerMeta is not implemented") } // GetUser mock implementation of GetUser from server.AccountManager interface @@ -347,7 +350,14 @@ func (am *MockAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) (*se if am.GetUserFunc != nil { return am.GetUserFunc(claims) } - return nil, status.Errorf(codes.Unimplemented, "method IsUserGetUserAdmin is not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetUser is not implemented") +} + +func (am *MockAccountManager) ListUsers(accountID string) ([]*server.User, error) { + if am.ListUsersFunc != nil { + return am.ListUsers(accountID) + } + return nil, status.Errorf(codes.Unimplemented, "method ListUsers is not implemented") } // UpdatePeerSSHKey mocks UpdatePeerSSHKey function of the account manager @@ -363,7 +373,7 @@ func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server. if am.UpdatePeerFunc != nil { return am.UpdatePeerFunc(accountID, userID, peer) } - return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is not implemented") + return nil, status.Errorf(codes.Unimplemented, "method UpdatePeer is not implemented") } // CreateRoute mock implementation of CreateRoute from server.AccountManager interface @@ -441,6 +451,14 @@ func (am *MockAccountManager) SaveUser(accountID, userID string, user *server.Us return nil, status.Errorf(codes.Unimplemented, "method SaveUser is not implemented") } +// SaveOrAddUser mocks SaveOrAddUser of the AccountManager interface +func (am *MockAccountManager) SaveOrAddUser(accountID, userID string, user *server.User, addIfNotExists bool) (*server.UserInfo, error) { + if am.SaveUserFunc != nil { + return am.SaveOrAddUserFunc(accountID, userID, user, addIfNotExists) + } + return nil, status.Errorf(codes.Unimplemented, "method SaveOrAddUser is not implemented") +} + // DeleteUser mocks DeleteUser of the AccountManager interface func (am *MockAccountManager) DeleteUser(accountID string, initiatorUserID string, targetUserID string) error { if am.DeleteUserFunc != nil { @@ -519,7 +537,7 @@ func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*server.Peer if am.GetAccountFromTokenFunc != nil { return am.GetPeersFunc(accountID, userID) } - return nil, status.Errorf(codes.Unimplemented, "method GetAllPeers is not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetPeers is not implemented") } // GetDNSDomain mocks GetDNSDomain of the AccountManager interface @@ -535,7 +553,7 @@ func (am *MockAccountManager) GetEvents(accountID, userID string) ([]*activity.E if am.GetEventsFunc != nil { return am.GetEventsFunc(accountID, userID) } - return nil, status.Errorf(codes.Unimplemented, "method GetAllEvents is not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetEvents is not implemented") } // GetDNSSettings mocks GetDNSSettings of the AccountManager interface @@ -600,3 +618,11 @@ func (am *MockAccountManager) StoreEvent(initiatorID, targetID, accountID string am.StoreEventFunc(initiatorID, targetID, accountID, activityID, meta) } } + +// GetExternalCacheManager mocks GetExternalCacheManager of the AccountManager interface +func (am *MockAccountManager) GetExternalCacheManager() server.ExternalCacheManager { + if am.GetExternalCacheManagerFunc() != nil { + return am.GetExternalCacheManagerFunc() + } + return nil +} diff --git a/management/server/user.go b/management/server/user.go index 2eaea88eb..af8044d69 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -52,7 +52,14 @@ type IntegrationReference struct { } func (ir IntegrationReference) String() string { - return fmt.Sprintf("%d:%s", ir.ID, ir.IntegrationType) + return fmt.Sprintf("%s:%d", ir.IntegrationType, ir.ID) +} + +func (ir IntegrationReference) CacheKey(path ...string) string { + if len(path) == 0 { + return ir.String() + } + return fmt.Sprintf("%s:%s", ir.String(), strings.Join(path, ":")) } // User represents a user of the system @@ -355,6 +362,25 @@ func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) ( return user, nil } +// ListUsers returns lists of all users under the account. +// It doesn't populate user information such a email or name. +func (am *DefaultAccountManager) ListUsers(accountID string) ([]*User, error) { + unlock := am.Store.AcquireAccountLock(accountID) + defer unlock() + + account, err := am.Store.GetAccount(accountID) + if err != nil { + return nil, err + } + + users := make([]*User, 0, len(account.Users)) + for _, item := range account.Users { + users = append(users, item) + } + + return users, nil +} + func (am *DefaultAccountManager) deleteServiceUser(account *Account, initiatorUserID string, targetUser *User) { meta := map[string]any{"name": targetUser.ServiceUserName} am.StoreEvent(initiatorUserID, targetUser.Id, account.Id, activity.ServiceUserDeleted, meta) @@ -654,8 +680,13 @@ func (am *DefaultAccountManager) GetAllPATs(accountID string, initiatorUserID st } // SaveUser saves updates to the given user. If the user doesn't exit it will throw status.NotFound error. -// Only User.AutoGroups, User.Role, and User.Blocked fields are allowed to be updated for now. func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, update *User) (*UserInfo, error) { + return am.SaveOrAddUser(accountID, initiatorUserID, update, false) // false means do not create user and throw status.NotFound +} + +// SaveOrAddUser updates the given user. If addIfNotExists is set to true it will add user when no exist +// Only User.AutoGroups, User.Role, and User.Blocked fields are allowed to be updated for now. +func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string, update *User, addIfNotExists bool) (*UserInfo, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -679,7 +710,11 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd oldUser := account.Users[update.Id] if oldUser == nil { - return nil, status.Errorf(status.NotFound, "user to update doesn't exist") + if !addIfNotExists { + return nil, status.Errorf(status.NotFound, "user to update doesn't exist") + } + // will add a user based on input + oldUser = update } if initiatorUser.IsAdmin() && initiatorUserID == update.Id && oldUser.Blocked != update.Blocked { @@ -691,6 +726,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd } // only auto groups, revoked status, and name can be updated for now + // when addIfNotExists is set to true the newUser will use all fields from the update input newUser := oldUser.Copy() newUser.Role = update.Role newUser.Blocked = update.Blocked @@ -779,7 +815,16 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd return nil, err } if userData == nil { - return nil, status.Errorf(status.NotFound, "user %s not found in the IdP", newUser.Id) + // lets check external cache + key := newUser.IntegrationReference.CacheKey(account.Id, newUser.Id) + log.Debugf("looking up user %s of account %s in external cache", key, account.Id) + info, err := am.externalCacheManager.Get(am.ctx, key) + if err != nil { + log.Infof("Get ExternalCache for key: %s, error: %s", key, err) + return nil, status.Errorf(status.NotFound, "user %s not found in the IdP", newUser.Id) + } + + return newUser.ToUserInfo(info) } return newUser.ToUserInfo(userData) } @@ -839,7 +884,19 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( queriedUsers := make([]*idp.UserData, 0) if !isNil(am.idpManager) { users := make(map[string]struct{}, len(account.Users)) + usersFromIntegration := make([]*idp.UserData, 0) for _, user := range account.Users { + if user.Issued == UserIssuedIntegration && user.LastLogin.IsZero() { + key := user.IntegrationReference.CacheKey(accountID, user.Id) + info, err := am.externalCacheManager.Get(am.ctx, key) + if err != nil { + log.Infof("Get ExternalCache for key: %s, error: %s", key, err) + users[user.Id] = struct{}{} + continue + } + usersFromIntegration = append(usersFromIntegration, info) + continue + } if !user.IsServiceUser { users[user.Id] = struct{}{} } @@ -848,6 +905,9 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( if err != nil { return nil, err } + log.Debugf("Got %d users from ExternalCache for account %s", len(usersFromIntegration), accountID) + log.Debugf("Got %d users from InternalCache for account %s", len(queriedUsers), accountID) + queriedUsers = append(queriedUsers, usersFromIntegration...) } userInfos := make([]*UserInfo, 0) diff --git a/management/server/user_test.go b/management/server/user_test.go index 40e7f9a2d..818a267d4 100644 --- a/management/server/user_test.go +++ b/management/server/user_test.go @@ -1,16 +1,21 @@ package server import ( + "context" "fmt" "reflect" "testing" "time" + "github.com/eko/gocache/v3/cache" + cacheStore "github.com/eko/gocache/v3/store" "github.com/google/go-cmp/cmp" + gocache "github.com/patrickmn/go-cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" ) @@ -549,6 +554,95 @@ func TestDefaultAccountManager_GetUser(t *testing.T) { assert.False(t, user.IsBlocked()) } +func TestDefaultAccountManager_ListUsers(t *testing.T) { + store := newStore(t) + account := newAccountWithId(mockAccountID, mockUserID, "") + account.Users["normal_user1"] = NewRegularUser("normal_user1") + account.Users["normal_user2"] = NewRegularUser("normal_user2") + + err := store.SaveAccount(account) + if err != nil { + t.Fatalf("Error when saving account: %s", err) + } + + am := DefaultAccountManager{ + Store: store, + eventStore: &activity.InMemoryEventStore{}, + } + + users, err := am.ListUsers(mockAccountID) + if err != nil { + t.Fatalf("Error when checking user role: %s", err) + } + + admins := 0 + regular := 0 + for _, user := range users { + if user.IsAdmin() { + admins++ + continue + } + regular++ + } + assert.Equal(t, 3, len(users)) + assert.Equal(t, 1, admins) + assert.Equal(t, 2, regular) +} + +func TestDefaultAccountManager_ExternalCache(t *testing.T) { + store := newStore(t) + account := newAccountWithId(mockAccountID, mockUserID, "") + externalUser := &User{ + Id: "externalUser", + Role: UserRoleUser, + Issued: UserIssuedIntegration, + IntegrationReference: IntegrationReference{ + ID: 1, + IntegrationType: "external", + }, + } + account.Users[externalUser.Id] = externalUser + + err := store.SaveAccount(account) + if err != nil { + t.Fatalf("Error when saving account: %s", err) + } + + am := DefaultAccountManager{ + Store: store, + eventStore: &activity.InMemoryEventStore{}, + idpManager: &idp.GoogleWorkspaceManager{}, // empty manager + cacheLoading: map[string]chan struct{}{}, + cacheManager: cache.New[[]*idp.UserData]( + cacheStore.NewGoCache(gocache.New(CacheExpirationMax, 30*time.Minute)), + ), + externalCacheManager: cache.New[*idp.UserData]( + cacheStore.NewGoCache(gocache.New(CacheExpirationMax, 30*time.Minute)), + ), + } + + // pretend that we receive mockUserID from IDP + err = am.cacheManager.Set(am.ctx, mockAccountID, []*idp.UserData{{Name: mockUserID, ID: mockUserID}}) + assert.NoError(t, err) + + cacheManager := am.GetExternalCacheManager() + cacheKey := externalUser.IntegrationReference.CacheKey(mockAccountID, externalUser.Id) + err = cacheManager.Set(context.Background(), cacheKey, &idp.UserData{ID: externalUser.Id, Name: "Test User", Email: "user@example.com"}) + assert.NoError(t, err) + + infos, err := am.GetUsersFromAccount(mockAccountID, mockUserID) + assert.NoError(t, err) + assert.Equal(t, 2, len(infos)) + var user *UserInfo + for _, info := range infos { + if info.ID == externalUser.Id { + user = info + } + } + assert.NotNil(t, user) + assert.Equal(t, "user@example.com", user.Email) +} + func TestUser_IsAdmin(t *testing.T) { user := NewAdminUser(mockUserID) @@ -710,5 +804,4 @@ func TestDefaultAccountManager_SaveUser(t *testing.T) { assert.Equal(t, tc.update.IsBlocked(), updated.IsBlocked) } } - } From 70c7543e36c97a0a2c56ffa8b1583f6dd563faed Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Tue, 14 Nov 2023 12:25:21 +0100 Subject: [PATCH 09/59] Allow to update IntegrationReference for user (#1308) This should not happen via an API but be possible when calling the method directly. --- management/server/user.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/management/server/user.go b/management/server/user.go index af8044d69..fd92bf35f 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -363,7 +363,7 @@ func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) ( } // ListUsers returns lists of all users under the account. -// It doesn't populate user information such a email or name. +// It doesn't populate user information such as email or name. func (am *DefaultAccountManager) ListUsers(accountID string) ([]*User, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -713,7 +713,7 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string if !addIfNotExists { return nil, status.Errorf(status.NotFound, "user to update doesn't exist") } - // will add a user based on input + // when addIfNotExists is set to true the newUser will use all fields from the update input oldUser = update } @@ -725,11 +725,13 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string return nil, status.Errorf(status.PermissionDenied, "admins can't change their role") } - // only auto groups, revoked status, and name can be updated for now - // when addIfNotExists is set to true the newUser will use all fields from the update input + // only auto groups, revoked status, and integration reference can be updated for now newUser := oldUser.Copy() newUser.Role = update.Role newUser.Blocked = update.Blocked + // these two fields can't be set via API, only via direct call to the method + newUser.Issued = update.Issued + newUser.IntegrationReference = update.IntegrationReference for _, newGroupID := range update.AutoGroups { if _, ok := account.Groups[newGroupID]; !ok { From 53fb0a9754952053f584c772c1aaa66c26d171f6 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 14 Nov 2023 17:40:14 +0100 Subject: [PATCH 10/59] refactor activity get queries to only consider 1 deleted user entry --- management/server/activity/sqlite/sqlite.go | 30 ++++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/management/server/activity/sqlite/sqlite.go b/management/server/activity/sqlite/sqlite.go index a5130b0c5..1eefa573a 100644 --- a/management/server/activity/sqlite/sqlite.go +++ b/management/server/activity/sqlite/sqlite.go @@ -14,7 +14,7 @@ import ( ) const ( - //eventSinkDB is the default name of the events database + // eventSinkDB is the default name of the events database eventSinkDB = "events.db" createTableQuery = "CREATE TABLE IF NOT EXISTS events " + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " + @@ -28,16 +28,32 @@ const ( creatTableDeletedUsersQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL, name TEXT);` selectDescQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta - FROM events - LEFT JOIN deleted_users i ON events.initiator_id = i.id - LEFT JOIN deleted_users t ON events.target_id = t.id + FROM events + LEFT JOIN ( + SELECT id, MAX(name) as name, MAX(email) as email + FROM deleted_users + GROUP BY id + ) i ON events.initiator_id = i.id + LEFT JOIN ( + SELECT id, MAX(name) as name, MAX(email) as email + FROM deleted_users + GROUP BY id + ) t ON events.target_id = t.id WHERE account_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?;` selectAscQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta - FROM events - LEFT JOIN deleted_users i ON events.initiator_id = i.id - LEFT JOIN deleted_users t ON events.target_id = t.id + FROM events + LEFT JOIN ( + SELECT id, MAX(name) as name, MAX(email) as email + FROM deleted_users + GROUP BY id + ) i ON events.initiator_id = i.id + LEFT JOIN ( + SELECT id, MAX(name) as name, MAX(email) as email + FROM deleted_users + GROUP BY id + ) t ON events.target_id = t.id WHERE account_id = ? ORDER BY timestamp ASC LIMIT ? OFFSET ?;` From 9eb1e90bbe1118edf766f53b5ed8718a8a61ea12 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 15 Nov 2023 11:21:59 +0100 Subject: [PATCH 11/59] refactor activity get queries to only add comment --- management/server/activity/sqlite/sqlite.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/management/server/activity/sqlite/sqlite.go b/management/server/activity/sqlite/sqlite.go index 1eefa573a..b54db5276 100644 --- a/management/server/activity/sqlite/sqlite.go +++ b/management/server/activity/sqlite/sqlite.go @@ -60,6 +60,14 @@ const ( insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " + "VALUES(?, ?, ?, ?, ?, ?)" + /* + TODO: + The insert should avoid duplicated IDs in the table. So the query should be changes to something like: + `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?) ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, name = EXCLUDED.name;` + For this to work we have to set the id column as primary key. But this is not possible because the id column is not unique + and some selfhosted deployments might have duplicates already so we need to clean the table first. + */ + insertDeleteUserQuery = `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?)` fallbackName = "unknown" From fb42fedb58f34c4c45e4dd714e4e10b9dad703a6 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Wed, 15 Nov 2023 14:15:12 +0100 Subject: [PATCH 12/59] Fix PAT copy for GetUserByTokenID in SQLite store (#1312) --- management/server/sqlite_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/server/sqlite_store.go b/management/server/sqlite_store.go index ed473e143..bbb13f8c6 100644 --- a/management/server/sqlite_store.go +++ b/management/server/sqlite_store.go @@ -298,7 +298,7 @@ func (s *SqliteStore) GetUserByTokenID(tokenID string) (*User, error) { user.PATs = make(map[string]*PersonalAccessToken, len(user.PATsG)) for _, pat := range user.PATsG { - user.PATs[pat.ID] = &pat + user.PATs[pat.ID] = pat.Copy() } return &user, nil From e7d063126dd5a19a3eeaa34ccc074d1f542a53fe Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Wed, 15 Nov 2023 18:22:00 +0300 Subject: [PATCH 13/59] Add non-deletable service user (#1311) * Add non-deletable flag for service users * fix non deletable service user created as deletable * Exclude non deletable service users in service users api response * Fix broken tests * Add test for non deletable service user * Add handling for non-deletable service users in tests * Remove non-deletable service users when fetching all users * Ensure non-deletable users are filtered out when fetching all user data --- management/server/account.go | 1 + management/server/http/users_handler.go | 4 ++ management/server/http/users_handler_test.go | 13 +++- management/server/user.go | 21 ++++-- management/server/user_test.go | 73 ++++++++++++++------ 5 files changed, 83 insertions(+), 29 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 25b034668..c8d4a1943 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -211,6 +211,7 @@ type UserInfo struct { Status string `json:"-"` IsServiceUser bool `json:"is_service_user"` IsBlocked bool `json:"is_blocked"` + NonDeletable bool `json:"non_deletable"` LastLogin time.Time `json:"last_login"` Issued string `json:"issued"` IntegrationReference IntegrationReference `json:"-"` diff --git a/management/server/http/users_handler.go b/management/server/http/users_handler.go index 0441e8cc0..e2bf77de6 100644 --- a/management/server/http/users_handler.go +++ b/management/server/http/users_handler.go @@ -197,10 +197,14 @@ func (h *UsersHandler) GetAllUsers(w http.ResponseWriter, r *http.Request) { users := make([]*api.User, 0) for _, r := range data { + if r.NonDeletable { + continue + } if serviceUser == "" { users = append(users, toUserResponse(r, claims.UserId)) continue } + includeServiceUser, err := strconv.ParseBool(serviceUser) log.Debugf("Should include service user: %v", includeServiceUser) if err != nil { diff --git a/management/server/http/users_handler_test.go b/management/server/http/users_handler_test.go index b4d449be3..ff886ca9f 100644 --- a/management/server/http/users_handler_test.go +++ b/management/server/http/users_handler_test.go @@ -20,8 +20,9 @@ import ( ) const ( - serviceUserID = "serviceUserID" - regularUserID = "regularUserID" + serviceUserID = "serviceUserID" + nonDeletableServiceUserID = "nonDeletableServiceUserID" + regularUserID = "regularUserID" ) var usersTestAccount = &server.Account{ @@ -49,6 +50,13 @@ var usersTestAccount = &server.Account{ AutoGroups: []string{"group_1"}, Issued: server.UserIssuedAPI, }, + nonDeletableServiceUserID: { + Id: serviceUserID, + Role: "admin", + IsServiceUser: true, + NonDeletable: true, + Issued: server.UserIssuedIntegration, + }, }, } @@ -67,6 +75,7 @@ func initUsersTestData() *UsersHandler { Name: "", Email: "", IsServiceUser: v.IsServiceUser, + NonDeletable: v.NonDeletable, Issued: v.Issued, }) } diff --git a/management/server/user.go b/management/server/user.go index fd92bf35f..e25817703 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -69,6 +69,8 @@ type User struct { AccountID string `json:"-" gorm:"index"` Role UserRole IsServiceUser bool + // NonDeletable indicates whether the service user can be deleted + NonDeletable bool // ServiceUserName is only set if IsServiceUser is true ServiceUserName string // AutoGroups is a list of Group IDs to auto-assign to peers registered by this user @@ -158,6 +160,7 @@ func (u *User) Copy() *User { Role: u.Role, AutoGroups: autoGroups, IsServiceUser: u.IsServiceUser, + NonDeletable: u.NonDeletable, ServiceUserName: u.ServiceUserName, PATs: pats, Blocked: u.Blocked, @@ -168,11 +171,12 @@ func (u *User) Copy() *User { } // NewUser creates a new user -func NewUser(id string, role UserRole, isServiceUser bool, serviceUserName string, autoGroups []string, issued string) *User { +func NewUser(id string, role UserRole, isServiceUser bool, nonDeletable bool, serviceUserName string, autoGroups []string, issued string) *User { return &User{ Id: id, Role: role, IsServiceUser: isServiceUser, + NonDeletable: nonDeletable, ServiceUserName: serviceUserName, AutoGroups: autoGroups, Issued: issued, @@ -181,16 +185,16 @@ func NewUser(id string, role UserRole, isServiceUser bool, serviceUserName strin // NewRegularUser creates a new user with role UserRoleUser func NewRegularUser(id string) *User { - return NewUser(id, UserRoleUser, false, "", []string{}, UserIssuedAPI) + return NewUser(id, UserRoleUser, false, false, "", []string{}, UserIssuedAPI) } // NewAdminUser creates a new user with role UserRoleAdmin func NewAdminUser(id string) *User { - return NewUser(id, UserRoleAdmin, false, "", []string{}, UserIssuedAPI) + return NewUser(id, UserRoleAdmin, false, false, "", []string{}, UserIssuedAPI) } // createServiceUser creates a new service user under the given account. -func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUserID string, role UserRole, serviceUserName string, autoGroups []string) (*UserInfo, error) { +func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUserID string, role UserRole, serviceUserName string, nonDeletable bool, autoGroups []string) (*UserInfo, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -208,7 +212,7 @@ func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUs } newUserID := uuid.New().String() - newUser := NewUser(newUserID, role, true, serviceUserName, autoGroups, UserIssuedAPI) + newUser := NewUser(newUserID, role, true, nonDeletable, serviceUserName, autoGroups, UserIssuedAPI) log.Debugf("New User: %v", newUser) account.Users[newUserID] = newUser @@ -236,7 +240,7 @@ func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUs // CreateUser creates a new user under the given account. Effectively this is a user invite. func (am *DefaultAccountManager) CreateUser(accountID, userID string, user *UserInfo) (*UserInfo, error) { if user.IsServiceUser { - return am.createServiceUser(accountID, userID, StrRoleToUserRole(user.Role), user.Name, user.AutoGroups) + return am.createServiceUser(accountID, userID, StrRoleToUserRole(user.Role), user.Name, user.NonDeletable, user.AutoGroups) } return am.inviteNewUser(accountID, userID, user) } @@ -420,6 +424,10 @@ func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, t // handle service user first and exit, no need to fetch extra data from IDP, etc if targetUser.IsServiceUser { + if targetUser.NonDeletable { + return status.Errorf(status.PermissionDenied, "service user is marked as non-deletable") + } + am.deleteServiceUser(account, initiatorUserID, targetUser) return am.Store.SaveAccount(account) } @@ -955,6 +963,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( AutoGroups: localUser.AutoGroups, Status: string(UserStatusActive), IsServiceUser: localUser.IsServiceUser, + NonDeletable: localUser.NonDeletable, } } userInfos = append(userInfos, info) diff --git a/management/server/user_test.go b/management/server/user_test.go index 818a267d4..e628981b0 100644 --- a/management/server/user_test.go +++ b/management/server/user_test.go @@ -332,7 +332,7 @@ func TestUser_CreateServiceUser(t *testing.T) { eventStore: &activity.InMemoryEventStore{}, } - user, err := am.createServiceUser(mockAccountID, mockUserID, mockRole, mockServiceUserName, []string{"group1", "group2"}) + user, err := am.createServiceUser(mockAccountID, mockUserID, mockRole, mockServiceUserName, false, []string{"group1", "group2"}) if err != nil { t.Fatalf("Error when creating service user: %s", err) } @@ -413,31 +413,62 @@ func TestUser_CreateUser_RegularUser(t *testing.T) { } func TestUser_DeleteUser_ServiceUser(t *testing.T) { - store := newStore(t) - account := newAccountWithId(mockAccountID, mockUserID, "") - account.Users[mockServiceUserID] = &User{ - Id: mockServiceUserID, - IsServiceUser: true, - ServiceUserName: mockServiceUserName, + tests := []struct { + name string + serviceUser *User + assertErrFunc assert.ErrorAssertionFunc + assertErrMessage string + }{ + { + name: "Can delete service user", + serviceUser: &User{ + Id: mockServiceUserID, + IsServiceUser: true, + ServiceUserName: mockServiceUserName, + }, + assertErrFunc: assert.NoError, + }, + { + name: "Cannot delete non-deletable service user", + serviceUser: &User{ + Id: mockServiceUserID, + IsServiceUser: true, + ServiceUserName: mockServiceUserName, + NonDeletable: true, + }, + assertErrFunc: assert.Error, + assertErrMessage: "service user is marked as non-deletable", + }, } - err := store.SaveAccount(account) - if err != nil { - t.Fatalf("Error when saving account: %s", err) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := newStore(t) + account := newAccountWithId(mockAccountID, mockUserID, "") + account.Users[mockServiceUserID] = tt.serviceUser - am := DefaultAccountManager{ - Store: store, - eventStore: &activity.InMemoryEventStore{}, - } + err := store.SaveAccount(account) + if err != nil { + t.Fatalf("Error when saving account: %s", err) + } - err = am.DeleteUser(mockAccountID, mockUserID, mockServiceUserID) - if err != nil { - t.Fatalf("Error when deleting user: %s", err) - } + am := DefaultAccountManager{ + Store: store, + eventStore: &activity.InMemoryEventStore{}, + } - assert.Equal(t, 1, len(store.Accounts[mockAccountID].Users)) - assert.Nil(t, store.Accounts[mockAccountID].Users[mockServiceUserID]) + err = am.DeleteUser(mockAccountID, mockUserID, mockServiceUserID) + tt.assertErrFunc(t, err, tt.assertErrMessage) + + if err != nil { + assert.Equal(t, 2, len(store.Accounts[mockAccountID].Users)) + assert.NotNil(t, store.Accounts[mockAccountID].Users[mockServiceUserID]) + } else { + assert.Equal(t, 1, len(store.Accounts[mockAccountID].Users)) + assert.Nil(t, store.Accounts[mockAccountID].Users[mockServiceUserID]) + } + }) + } } func TestUser_DeleteUser_SelfDelete(t *testing.T) { From 5a45f79fec0e1785827ede7424cce8ebc277b9a4 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Thu, 16 Nov 2023 16:46:08 +0100 Subject: [PATCH 14/59] use the email address to set the iphone name for iOS 16+ --- management/server/peer.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/management/server/peer.go b/management/server/peer.go index 963bb3e80..503ef774e 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -9,6 +9,7 @@ import ( "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/status" log "github.com/sirupsen/logrus" @@ -485,6 +486,13 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* return nil, nil, err } + if peer.Meta.Hostname == "iPhone" && userID != "" { + userdata, err := am.idpManager.GetUserDataByID(userID, idp.AppMetadata{}) + if err == nil { + peer.Meta.Hostname = fmt.Sprintf("iPhone-%s", strings.Split(userdata.Email, "@")[0]) + } + } + // This is a handling for the case when the same machine (with the same WireGuard pub key) tries to register twice. // Such case is possible when AddPeer function takes long time to finish after AcquireAccountLock (e.g., database is slow) // and the peer disconnects with a timeout and tries to register again. @@ -638,6 +646,7 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er // If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so. func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) { account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey) + if err != nil { if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { // we couldn't find this peer by its public key which can mean that peer hasn't been registered yet. From f86ed12cf59f128be731f8b57d0e0150877a4feb Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Thu, 16 Nov 2023 17:01:01 +0100 Subject: [PATCH 15/59] add support for ipad as well --- management/server/peer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/management/server/peer.go b/management/server/peer.go index 503ef774e..65ab24c8b 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -486,10 +486,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* return nil, nil, err } - if peer.Meta.Hostname == "iPhone" && userID != "" { + if strings.ToLower(peer.Meta.Hostname) == "iphone" || strings.ToLower(peer.Meta.Hostname) == "ipad" && userID != "" { userdata, err := am.idpManager.GetUserDataByID(userID, idp.AppMetadata{}) if err == nil { - peer.Meta.Hostname = fmt.Sprintf("iPhone-%s", strings.Split(userdata.Email, "@")[0]) + peer.Meta.Hostname = fmt.Sprintf("%s-%s", peer.Meta.Hostname, strings.Split(userdata.Email, "@")[0]) } } From d379c25ff5230d651dbb8d53c58111f9688cb1bc Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Thu, 16 Nov 2023 17:13:04 +0100 Subject: [PATCH 16/59] use idp cache instead of idp manager --- management/server/peer.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/management/server/peer.go b/management/server/peer.go index 65ab24c8b..27912f1f4 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -9,7 +9,6 @@ import ( "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" - "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/status" log "github.com/sirupsen/logrus" @@ -487,9 +486,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } if strings.ToLower(peer.Meta.Hostname) == "iphone" || strings.ToLower(peer.Meta.Hostname) == "ipad" && userID != "" { - userdata, err := am.idpManager.GetUserDataByID(userID, idp.AppMetadata{}) - if err == nil { - peer.Meta.Hostname = fmt.Sprintf("%s-%s", peer.Meta.Hostname, strings.Split(userdata.Email, "@")[0]) + if am.idpManager != nil { + userdata, err := am.lookupUserInCache(userID, account) + if err == nil { + peer.Meta.Hostname = fmt.Sprintf("%s-%s", peer.Meta.Hostname, strings.Split(userdata.Email, "@")[0]) + } } } From b58094de0f1fa51aaf039a94e782bb9cb228c270 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Thu, 16 Nov 2023 18:21:52 +0100 Subject: [PATCH 17/59] Add metrics for PeersUpdateManager (#1310) With this change we should be able to collect and expose the following histograms: * `management.updatechannel.create.duration.ms` with `closed` boolean label * `management.updatechannel.create.duration.micro` with `closed` boolean label * `management.updatechannel.close.one.duration.ms` * `management.updatechannel.close.one.duration.micro` * `management.updatechannel.close.multiple.duration.ms` * `management.updatechannel.close.multiple.duration.micro` * `management.updatechannel.close.multiple.channels` * `management.updatechannel.send.duration.ms` with `found` and `dropped` boolean labels * `management.updatechannel.send.duration.micro` with `found` and `dropped` boolean labels * `management.updatechannel.get.all.duration.ms` * `management.updatechannel.get.all.duration.micro` * `management.updatechannel.get.all.peers` --- client/cmd/testutil.go | 2 +- client/internal/engine_test.go | 2 +- management/client/client_test.go | 2 +- management/cmd/management.go | 2 +- management/server/account_test.go | 2 +- management/server/dns_test.go | 2 +- management/server/management_proto_test.go | 2 +- management/server/management_test.go | 2 +- management/server/nameserver_test.go | 2 +- management/server/route_test.go | 2 +- management/server/telemetry/app_metrics.go | 60 ++++++-- management/server/telemetry/store_metrics.go | 2 +- .../server/telemetry/updatechannel_metrics.go | 141 ++++++++++++++++++ management/server/turncredentials_test.go | 6 +- management/server/updatechannel.go | 69 ++++++++- management/server/updatechannel_test.go | 9 +- 16 files changed, 266 insertions(+), 41 deletions(-) create mode 100644 management/server/telemetry/updatechannel_metrics.go diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index c90faa24a..28423776f 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -73,7 +73,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste t.Fatal(err) } - peersUpdateManager := mgmt.NewPeersUpdateManager() + peersUpdateManager := mgmt.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} if err != nil { return nil, nil diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 42012bd0a..a855cf051 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -1044,7 +1044,7 @@ func startManagement(dataDir string) (*grpc.Server, string, error) { return nil, "", err } - peersUpdateManager := server.NewPeersUpdateManager() + peersUpdateManager := server.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} if err != nil { return nil, "", err diff --git a/management/client/client_test.go b/management/client/client_test.go index ab8df99df..c5e5b8140 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -58,7 +58,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { t.Fatal(err) } - peersUpdateManager := mgmt.NewPeersUpdateManager() + peersUpdateManager := mgmt.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, false) diff --git a/management/cmd/management.go b/management/cmd/management.go index 1a00a0f57..f05de4e4e 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -130,7 +130,7 @@ var ( if err != nil { return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err) } - peersUpdateManager := server.NewPeersUpdateManager() + peersUpdateManager := server.NewPeersUpdateManager(appMetrics) var idpManager idp.Manager if config.IdpManagerConfig != nil { diff --git a/management/server/account_test.go b/management/server/account_test.go index 181d0447b..ad0ccfbce 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -2047,7 +2047,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.cloud", eventStore, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, false) } func createStore(t *testing.T) (Store, error) { diff --git a/management/server/dns_test.go b/management/server/dns_test.go index 54044cf93..62408e4b3 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -192,7 +192,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.test", eventStore, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, false) } func createDNSStore(t *testing.T) (Store, error) { diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index 000f6c9de..21e9862f0 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -410,7 +410,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error) if err != nil { return nil, "", err } - peersUpdateManager := NewPeersUpdateManager() + peersUpdateManager := NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "", eventStore, false) diff --git a/management/server/management_test.go b/management/server/management_test.go index 375e7e634..ee9641a8c 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -501,7 +501,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) { if err != nil { log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) } - peersUpdateManager := server.NewPeersUpdateManager() + peersUpdateManager := server.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, false) diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 6a08706ca..d1f4cd015 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -747,7 +747,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, false) } func createNSStore(t *testing.T) (Store, error) { diff --git a/management/server/route_test.go b/management/server/route_test.go index e55e76928..5112a5780 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -1013,7 +1013,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { return nil, err } eventStore := &activity.InMemoryEventStore{} - return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore, false) + return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, false) } func createRouterStore(t *testing.T) (Store, error) { diff --git a/management/server/telemetry/app_metrics.go b/management/server/telemetry/app_metrics.go index de5d278b9..56f4fb9c8 100644 --- a/management/server/telemetry/app_metrics.go +++ b/management/server/telemetry/app_metrics.go @@ -20,13 +20,14 @@ const defaultEndpoint = "/metrics" // MockAppMetrics mocks the AppMetrics interface type MockAppMetrics struct { - GetMeterFunc func() metric2.Meter - CloseFunc func() error - ExposeFunc func(port int, endpoint string) error - IDPMetricsFunc func() *IDPMetrics - HTTPMiddlewareFunc func() *HTTPMiddleware - GRPCMetricsFunc func() *GRPCMetrics - StoreMetricsFunc func() *StoreMetrics + GetMeterFunc func() metric2.Meter + CloseFunc func() error + ExposeFunc func(port int, endpoint string) error + IDPMetricsFunc func() *IDPMetrics + HTTPMiddlewareFunc func() *HTTPMiddleware + GRPCMetricsFunc func() *GRPCMetrics + StoreMetricsFunc func() *StoreMetrics + UpdateChannelMetricsFunc func() *UpdateChannelMetrics } // GetMeter mocks the GetMeter function of the AppMetrics interface @@ -85,6 +86,14 @@ func (mock *MockAppMetrics) StoreMetrics() *StoreMetrics { return nil } +// UpdateChannelMetrics mocks the MockAppMetrics function of the UpdateChannelMetrics interface +func (mock *MockAppMetrics) UpdateChannelMetrics() *UpdateChannelMetrics { + if mock.UpdateChannelMetricsFunc != nil { + return mock.UpdateChannelMetricsFunc() + } + return nil +} + // AppMetrics is metrics interface type AppMetrics interface { GetMeter() metric2.Meter @@ -94,18 +103,20 @@ type AppMetrics interface { HTTPMiddleware() *HTTPMiddleware GRPCMetrics() *GRPCMetrics StoreMetrics() *StoreMetrics + UpdateChannelMetrics() *UpdateChannelMetrics } // defaultAppMetrics are core application metrics based on OpenTelemetry https://opentelemetry.io/ type defaultAppMetrics struct { // Meter can be used by different application parts to create counters and measure things - Meter metric2.Meter - listener net.Listener - ctx context.Context - idpMetrics *IDPMetrics - httpMiddleware *HTTPMiddleware - grpcMetrics *GRPCMetrics - storeMetrics *StoreMetrics + Meter metric2.Meter + listener net.Listener + ctx context.Context + idpMetrics *IDPMetrics + httpMiddleware *HTTPMiddleware + grpcMetrics *GRPCMetrics + storeMetrics *StoreMetrics + updateChannelMetrics *UpdateChannelMetrics } // IDPMetrics returns metrics for the idp package @@ -128,6 +139,11 @@ func (appMetrics *defaultAppMetrics) StoreMetrics() *StoreMetrics { return appMetrics.storeMetrics } +// UpdateChannelMetrics returns metrics for the updatechannel +func (appMetrics *defaultAppMetrics) UpdateChannelMetrics() *UpdateChannelMetrics { + return appMetrics.updateChannelMetrics +} + // Close stop application metrics HTTP handler and closes listener. func (appMetrics *defaultAppMetrics) Close() error { if appMetrics.listener == nil { @@ -199,6 +215,18 @@ func NewDefaultAppMetrics(ctx context.Context) (AppMetrics, error) { return nil, err } - return &defaultAppMetrics{Meter: meter, ctx: ctx, idpMetrics: idpMetrics, httpMiddleware: middleware, - grpcMetrics: grpcMetrics, storeMetrics: storeMetrics}, nil + updateChannelMetrics, err := NewUpdateChannelMetrics(ctx, meter) + if err != nil { + return nil, err + } + + return &defaultAppMetrics{ + Meter: meter, + ctx: ctx, + idpMetrics: idpMetrics, + httpMiddleware: middleware, + grpcMetrics: grpcMetrics, + storeMetrics: storeMetrics, + updateChannelMetrics: updateChannelMetrics, + }, nil } diff --git a/management/server/telemetry/store_metrics.go b/management/server/telemetry/store_metrics.go index 98c13f12a..6415f765e 100644 --- a/management/server/telemetry/store_metrics.go +++ b/management/server/telemetry/store_metrics.go @@ -9,7 +9,7 @@ import ( "go.opentelemetry.io/otel/metric/instrument/syncint64" ) -// StoreMetrics represents all metrics related to the FileStore +// StoreMetrics represents all metrics related to the Store type StoreMetrics struct { globalLockAcquisitionDurationMicro syncint64.Histogram globalLockAcquisitionDurationMs syncint64.Histogram diff --git a/management/server/telemetry/updatechannel_metrics.go b/management/server/telemetry/updatechannel_metrics.go new file mode 100644 index 000000000..ede1671fc --- /dev/null +++ b/management/server/telemetry/updatechannel_metrics.go @@ -0,0 +1,141 @@ +package telemetry + +import ( + "context" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/instrument/syncint64" +) + +// UpdateChannelMetrics represents all metrics related to the UpdateChannel +type UpdateChannelMetrics struct { + createChannelDurationMs syncint64.Histogram + createChannelDurationMicro syncint64.Histogram + closeChannelDurationMs syncint64.Histogram + closeChannelDurationMicro syncint64.Histogram + closeChannelsDurationMs syncint64.Histogram + closeChannelsDurationMicro syncint64.Histogram + closeChannels syncint64.Histogram + sendUpdateDurationMs syncint64.Histogram + sendUpdateDurationMicro syncint64.Histogram + getAllConnectedPeersDurationMs syncint64.Histogram + getAllConnectedPeersDurationMicro syncint64.Histogram + getAllConnectedPeers syncint64.Histogram + ctx context.Context +} + +// NewUpdateChannelMetrics creates an instance of UpdateChannel +func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateChannelMetrics, error) { + createChannelDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.create.duration.ms") + if err != nil { + return nil, err + } + + createChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.create.duration.micro") + if err != nil { + return nil, err + } + + closeChannelDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.close.one.duration.ms") + if err != nil { + return nil, err + } + + closeChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.close.one.duration.micro") + if err != nil { + return nil, err + } + + closeChannelsDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.duration.ms") + if err != nil { + return nil, err + } + + closeChannelsDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.duration.micro") + if err != nil { + return nil, err + } + + closeChannels, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.channels") + if err != nil { + return nil, err + } + + sendUpdateDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.send.duration.ms") + if err != nil { + return nil, err + } + + sendUpdateDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.send.duration.micro") + if err != nil { + return nil, err + } + + getAllConnectedPeersDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.duration.ms") + if err != nil { + return nil, err + } + + getAllConnectedPeersDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.duration.micro") + if err != nil { + return nil, err + } + + getAllConnectedPeers, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.peers") + if err != nil { + return nil, err + } + + return &UpdateChannelMetrics{ + createChannelDurationMs: createChannelDurationMs, + createChannelDurationMicro: createChannelDurationMicro, + closeChannelDurationMs: closeChannelDurationMs, + closeChannelDurationMicro: closeChannelDurationMicro, + closeChannelsDurationMs: closeChannelsDurationMs, + closeChannelsDurationMicro: closeChannelsDurationMicro, + closeChannels: closeChannels, + sendUpdateDurationMs: sendUpdateDurationMs, + sendUpdateDurationMicro: sendUpdateDurationMicro, + getAllConnectedPeersDurationMs: getAllConnectedPeersDurationMs, + getAllConnectedPeersDurationMicro: getAllConnectedPeersDurationMicro, + getAllConnectedPeers: getAllConnectedPeers, + ctx: ctx, + }, nil +} + +// CountCreateChannelDuration counts the duration of the CreateChannel method, +// closed indicates if existing channel was closed before creation of a new one +func (metrics *UpdateChannelMetrics) CountCreateChannelDuration(duration time.Duration, closed bool) { + metrics.createChannelDurationMs.Record(metrics.ctx, duration.Milliseconds(), attribute.Bool("closed", closed)) + metrics.createChannelDurationMicro.Record(metrics.ctx, duration.Microseconds(), attribute.Bool("closed", closed)) +} + +// CountCloseChannelDuration counts the duration of the CloseChannel method +func (metrics *UpdateChannelMetrics) CountCloseChannelDuration(duration time.Duration) { + metrics.closeChannelDurationMs.Record(metrics.ctx, duration.Milliseconds()) + metrics.closeChannelDurationMicro.Record(metrics.ctx, duration.Microseconds()) +} + +// CountCloseChannelsDuration counts the duration of the CloseChannels method and the number of channels have been closed +func (metrics *UpdateChannelMetrics) CountCloseChannelsDuration(duration time.Duration, channels int) { + metrics.closeChannelsDurationMs.Record(metrics.ctx, duration.Milliseconds()) + metrics.closeChannelsDurationMicro.Record(metrics.ctx, duration.Microseconds()) + metrics.closeChannels.Record(metrics.ctx, int64(channels)) +} + +// CountSendUpdateDuration counts the duration of the SendUpdate method +// found indicates if peer had channel, dropped indicates if the message was dropped due channel buffer overload +func (metrics *UpdateChannelMetrics) CountSendUpdateDuration(duration time.Duration, found, dropped bool) { + attrs := []attribute.KeyValue{attribute.Bool("found", found), attribute.Bool("dropped", dropped)} + metrics.sendUpdateDurationMs.Record(metrics.ctx, duration.Milliseconds(), attrs...) + metrics.sendUpdateDurationMicro.Record(metrics.ctx, duration.Microseconds(), attrs...) +} + +// CountGetAllConnectedPeersDuration counts the duration of the GetAllConnectedPeers method and the number of peers have been returned +func (metrics *UpdateChannelMetrics) CountGetAllConnectedPeersDuration(duration time.Duration, peers int) { + metrics.getAllConnectedPeersDurationMs.Record(metrics.ctx, duration.Milliseconds()) + metrics.getAllConnectedPeersDurationMicro.Record(metrics.ctx, duration.Microseconds()) + metrics.getAllConnectedPeers.Record(metrics.ctx, int64(peers)) +} diff --git a/management/server/turncredentials_test.go b/management/server/turncredentials_test.go index a8d282266..5066fdbe9 100644 --- a/management/server/turncredentials_test.go +++ b/management/server/turncredentials_test.go @@ -20,7 +20,7 @@ var TurnTestHost = &Host{ func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) { ttl := util.Duration{Duration: time.Hour} secret := "some_secret" - peersManager := NewPeersUpdateManager() + peersManager := NewPeersUpdateManager(nil) tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{ CredentialsTTL: ttl, @@ -44,7 +44,7 @@ func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) { func TestTimeBasedAuthSecretsManager_SetupRefresh(t *testing.T) { ttl := util.Duration{Duration: 2 * time.Second} secret := "some_secret" - peersManager := NewPeersUpdateManager() + peersManager := NewPeersUpdateManager(nil) peer := "some_peer" updateChannel := peersManager.CreateChannel(peer) @@ -93,7 +93,7 @@ loop: func TestTimeBasedAuthSecretsManager_CancelRefresh(t *testing.T) { ttl := util.Duration{Duration: time.Hour} secret := "some_secret" - peersManager := NewPeersUpdateManager() + peersManager := NewPeersUpdateManager(nil) peer := "some_peer" tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{ diff --git a/management/server/updatechannel.go b/management/server/updatechannel.go index 5e6bcbb1c..0f14497fd 100644 --- a/management/server/updatechannel.go +++ b/management/server/updatechannel.go @@ -2,10 +2,12 @@ package server import ( "sync" + "time" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/proto" + "github.com/netbirdio/netbird/management/server/telemetry" ) const channelBufferSize = 100 @@ -17,26 +19,41 @@ type UpdateMessage struct { type PeersUpdateManager struct { // peerChannels is an update channel indexed by Peer.ID peerChannels map[string]chan *UpdateMessage - channelsMux *sync.Mutex + // channelsMux keeps the mutex to access peerChannels + channelsMux *sync.Mutex + // metrics provides method to collect application metrics + metrics telemetry.AppMetrics } // NewPeersUpdateManager returns a new instance of PeersUpdateManager -func NewPeersUpdateManager() *PeersUpdateManager { +func NewPeersUpdateManager(metrics telemetry.AppMetrics) *PeersUpdateManager { return &PeersUpdateManager{ peerChannels: make(map[string]chan *UpdateMessage), channelsMux: &sync.Mutex{}, + metrics: metrics, } } // SendUpdate sends update message to the peer's channel func (p *PeersUpdateManager) SendUpdate(peerID string, update *UpdateMessage) { + start := time.Now() + var found, dropped bool + p.channelsMux.Lock() - defer p.channelsMux.Unlock() + defer func() { + p.channelsMux.Unlock() + if p.metrics != nil { + p.metrics.UpdateChannelMetrics().CountSendUpdateDuration(time.Since(start), found, dropped) + } + }() + if channel, ok := p.peerChannels[peerID]; ok { + found = true select { case channel <- update: log.Debugf("update was sent to channel for peer %s", peerID) default: + dropped = true log.Warnf("channel for peer %s is %d full", peerID, len(channel)) } } else { @@ -46,10 +63,20 @@ func (p *PeersUpdateManager) SendUpdate(peerID string, update *UpdateMessage) { // CreateChannel creates a go channel for a given peer used to deliver updates relevant to the peer. func (p *PeersUpdateManager) CreateChannel(peerID string) chan *UpdateMessage { + start := time.Now() + + closed := false + p.channelsMux.Lock() - defer p.channelsMux.Unlock() + defer func() { + p.channelsMux.Unlock() + if p.metrics != nil { + p.metrics.UpdateChannelMetrics().CountCreateChannelDuration(time.Since(start), closed) + } + }() if channel, ok := p.peerChannels[peerID]; ok { + closed = true delete(p.peerChannels, peerID) close(channel) } @@ -58,6 +85,7 @@ func (p *PeersUpdateManager) CreateChannel(peerID string) chan *UpdateMessage { p.peerChannels[peerID] = channel log.Debugf("opened updates channel for a peer %s", peerID) + return channel } @@ -72,8 +100,16 @@ func (p *PeersUpdateManager) closeChannel(peerID string) { // CloseChannels closes updates channel for each given peer func (p *PeersUpdateManager) CloseChannels(peerIDs []string) { + start := time.Now() + p.channelsMux.Lock() - defer p.channelsMux.Unlock() + defer func() { + p.channelsMux.Unlock() + if p.metrics != nil { + p.metrics.UpdateChannelMetrics().CountCloseChannelsDuration(time.Since(start), len(peerIDs)) + } + }() + for _, id := range peerIDs { p.closeChannel(id) } @@ -81,18 +117,37 @@ func (p *PeersUpdateManager) CloseChannels(peerIDs []string) { // CloseChannel closes updates channel of a given peer func (p *PeersUpdateManager) CloseChannel(peerID string) { + start := time.Now() + p.channelsMux.Lock() - defer p.channelsMux.Unlock() + defer func() { + p.channelsMux.Unlock() + if p.metrics != nil { + p.metrics.UpdateChannelMetrics().CountCloseChannelDuration(time.Since(start)) + } + }() + p.closeChannel(peerID) } // GetAllConnectedPeers returns a copy of the connected peers map func (p *PeersUpdateManager) GetAllConnectedPeers() map[string]struct{} { + start := time.Now() + p.channelsMux.Lock() - defer p.channelsMux.Unlock() + m := make(map[string]struct{}) + + defer func() { + p.channelsMux.Unlock() + if p.metrics != nil { + p.metrics.UpdateChannelMetrics().CountGetAllConnectedPeersDuration(time.Since(start), len(m)) + } + }() + for ID := range p.peerChannels { m[ID] = struct{}{} } + return m } diff --git a/management/server/updatechannel_test.go b/management/server/updatechannel_test.go index 6cfb4d52f..187e404c5 100644 --- a/management/server/updatechannel_test.go +++ b/management/server/updatechannel_test.go @@ -1,16 +1,17 @@ package server import ( - "github.com/netbirdio/netbird/management/proto" "testing" "time" + + "github.com/netbirdio/netbird/management/proto" ) //var peersUpdater *PeersUpdateManager func TestCreateChannel(t *testing.T) { peer := "test-create" - peersUpdater := NewPeersUpdateManager() + peersUpdater := NewPeersUpdateManager(nil) defer peersUpdater.CloseChannel(peer) _ = peersUpdater.CreateChannel(peer) @@ -21,7 +22,7 @@ func TestCreateChannel(t *testing.T) { func TestSendUpdate(t *testing.T) { peer := "test-sendupdate" - peersUpdater := NewPeersUpdateManager() + peersUpdater := NewPeersUpdateManager(nil) update1 := &UpdateMessage{Update: &proto.SyncResponse{ NetworkMap: &proto.NetworkMap{ Serial: 0, @@ -65,7 +66,7 @@ func TestSendUpdate(t *testing.T) { func TestCloseChannel(t *testing.T) { peer := "test-close" - peersUpdater := NewPeersUpdateManager() + peersUpdater := NewPeersUpdateManager(nil) _ = peersUpdater.CreateChannel(peer) if _, ok := peersUpdater.peerChannels[peer]; !ok { t.Error("Error creating the channel") From 67906f6da56431f02a4c2bbf114c078ac6ff35d9 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Mon, 20 Nov 2023 12:05:32 +0100 Subject: [PATCH 18/59] Improve Account cache reload condition (#1319) To take in consideration that cache may know more users --- management/server/account.go | 8 +++++++- management/server/user.go | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index c8d4a1943..1a4578238 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -1082,6 +1082,9 @@ func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interf if user.IsServiceUser { continue } + if user.Issued == UserIssuedIntegration { + continue + } datum, ok := dataMap[user.Id] if !ok { log.Warnf("user %s not found in IDP", user.Id) @@ -1115,7 +1118,7 @@ func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Accou if user.IsServiceUser { continue } - if user.Issued == UserIssuedIntegration && user.LastLogin.IsZero() { + if user.Issued == UserIssuedIntegration { continue } users[user.Id] = struct{}{} @@ -1194,10 +1197,13 @@ func (am *DefaultAccountManager) lookupCache(accountUsers map[string]struct{}, a for user := range accountUsers { if _, ok := userDataMap[user]; !ok { reload = true + log.Debugf("idp cache doesn't have user %s", user) + break } } if reload { + log.Debugf("reload cache, len(accountUsers) = %d, len(data) = %d", len(accountUsers), len(data)) // reload cache once avoiding loops data, err = am.refreshCache(accountID) if err != nil { diff --git a/management/server/user.go b/management/server/user.go index e25817703..9e280bc6b 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -896,7 +896,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( users := make(map[string]struct{}, len(account.Users)) usersFromIntegration := make([]*idp.UserData, 0) for _, user := range account.Users { - if user.Issued == UserIssuedIntegration && user.LastLogin.IsZero() { + if user.Issued == UserIssuedIntegration { key := user.IntegrationReference.CacheKey(accountID, user.Id) info, err := am.externalCacheManager.Get(am.ctx, key) if err != nil { From d78b7e5d93a6fc5da51a67eab68efa5d6312a75c Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Mon, 20 Nov 2023 16:56:21 +0300 Subject: [PATCH 19/59] Skip user deletion if the user does not exist in IdP (#1320) --- management/server/user.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/management/server/user.go b/management/server/user.go index 9e280bc6b..b96bf743c 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -443,10 +443,17 @@ func (am *DefaultAccountManager) deleteRegularUser(account *Account, initiatorUs } if !isNil(am.idpManager) { - err = am.deleteUserFromIDP(targetUserID, account.Id) - if err != nil { - log.Debugf("failed to delete user from IDP: %s", targetUserID) - return err + // Delete if the user already exists in the IdP.Necessary in cases where a user account + // was created where a user account was provisioned but the user did not sign in + _, err = am.idpManager.GetUserDataByID(targetUserID, idp.AppMetadata{WTAccountID: account.Id}) + if err == nil { + err = am.deleteUserFromIDP(targetUserID, account.Id) + if err != nil { + log.Debugf("failed to delete user from IDP: %s", targetUserID) + return err + } + } else { + log.Debugf("skipped deleting user %s from IDP, error: %v", targetUserID, err) } } From afece95ae599de146e42587d078a2d7de1414cf0 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Mon, 20 Nov 2023 16:47:11 +0100 Subject: [PATCH 20/59] Fix lookupCache to work when idp has more users (#1321) --- management/server/account.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 1a4578238..8f9b1e151 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -1082,9 +1082,6 @@ func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interf if user.IsServiceUser { continue } - if user.Issued == UserIssuedIntegration { - continue - } datum, ok := dataMap[user.Id] if !ok { log.Warnf("user %s not found in IDP", user.Id) @@ -1191,19 +1188,20 @@ func (am *DefaultAccountManager) lookupCache(accountUsers map[string]struct{}, a userDataMap[datum.ID] = struct{}{} } - // check whether we need to reload the cache - // the accountUsers ID list is the source of truth and all the users should be in the cache - reload := len(accountUsers) != len(data) + // the accountUsers ID list of non integration users from store, we check if cache has all of them + // as result of for loop knownUsersCount will have number of users are not presented in the cashed + knownUsersCount := len(accountUsers) for user := range accountUsers { - if _, ok := userDataMap[user]; !ok { - reload = true - log.Debugf("idp cache doesn't have user %s", user) - break + if _, ok := userDataMap[user]; ok { + knownUsersCount-- + continue } + log.Debugf("cache doesn't know about %s user", user) } - if reload { - log.Debugf("reload cache, len(accountUsers) = %d, len(data) = %d", len(accountUsers), len(data)) + // if we know users that are not yet in cache more likely cache is outdated + if knownUsersCount > 0 { + log.Debugf("cache doesn't know about %d users from store, reloading", knownUsersCount) // reload cache once avoiding loops data, err = am.refreshCache(accountID) if err != nil { From 63f6514be50f30ae849df02f99fd8939dfc596e0 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Tue, 21 Nov 2023 17:38:33 +0100 Subject: [PATCH 21/59] Add tenv linter (#1322) Tenv is analyzer that detects using `os.Setenv` instead of `t.Setenv` since Go 1.17. --- .golangci.yaml | 7 +++++++ client/internal/dns/server_test.go | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index b637c7ed1..d0926fffc 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -19,6 +19,12 @@ linters-settings: enable: - 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: disable-all: true enable: @@ -28,6 +34,7 @@ linters: - 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 - 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 - unused # checks for unused constants, variables, functions and types ## disable by default but the have interesting results so lets add them diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 62b4e1867..875a1a46f 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -322,9 +322,9 @@ func TestUpdateDNSServer(t *testing.T) { func TestDNSFakeResolverHandleUpdates(t *testing.T) { 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) if err != nil { t.Errorf("create stdnet: %v", err) @@ -773,9 +773,9 @@ func TestDNSPermanent_matchOnly(t *testing.T) { func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { t.Helper() 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) if err != nil { t.Fatalf("create stdnet: %v", err) From 96cdcf8e49c15c159bd874c1ae5ebc8de2bf84f8 Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Tue, 21 Nov 2023 20:02:16 +0300 Subject: [PATCH 22/59] Add client UI shortcut links for standard users in Windows (#1323) * Change SetShellVarContext scope to create program links for standard users * Include guidelines for building the Windows Netbird installer during development * Add Wireguard driver requirement to Windows build instructions --- CONTRIBUTING.md | 36 ++++++++++++++++++++++++++++++++++++ client/installer.nsis | 9 +++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80be72fa9..29a12402e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,6 +183,42 @@ To start NetBird the management service: ./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 The tests can be started via: diff --git a/client/installer.nsis b/client/installer.nsis index e2e3ac118..fbffa326d 100644 --- a/client/installer.nsis +++ b/client/installer.nsis @@ -166,10 +166,9 @@ WriteRegStr ${REG_ROOT} "${UI_REG_APP_PATH}" "" "$INSTDIR\${UI_APP_EXE}" EnVar::SetHKLM EnVar::AddValueEx "path" "$INSTDIR" -SetShellVarContext current +SetShellVarContext all CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}" CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}" -SetShellVarContext all SectionEnd Section -Post @@ -196,10 +195,9 @@ Delete "$INSTDIR\${MAIN_APP_EXE}" Delete "$INSTDIR\wintun.dll" RmDir /r "$INSTDIR" -SetShellVarContext current +SetShellVarContext all Delete "$DESKTOP\${APP_NAME}.lnk" Delete "$SMPROGRAMS\${APP_NAME}.lnk" -SetShellVarContext all DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}" DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}" @@ -209,8 +207,7 @@ SectionEnd Function LaunchLink -SetShellVarContext current +SetShellVarContext all SetOutPath $INSTDIR ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk" -SetShellVarContext all FunctionEnd From ab895be4a39bfdde1453d14fcb1022ff20465006 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 22 Nov 2023 16:23:19 +0100 Subject: [PATCH 23/59] fix get os info for windows to report correct versions --- client/system/info_windows.go | 41 ++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/client/system/info_windows.go b/client/system/info_windows.go index c8c3276c9..ede689fa1 100644 --- a/client/system/info_windows.go +++ b/client/system/info_windows.go @@ -5,17 +5,24 @@ import ( "fmt" "os" "runtime" + "strings" log "github.com/sirupsen/logrus" + "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows/registry" "github.com/netbirdio/netbird/version" ) +type Win32_OperatingSystem struct { + Caption string +} + // GetInfo retrieves and parses the system information func GetInfo(ctx context.Context) *Info { - ver := getOSVersion() - gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + osName, osVersion := getOSNameAndVersion() + buildVersion := getBuildVersion() + gio := &Info{Kernel: "windows", OSVersion: buildVersion, Core: osVersion, Platform: "unknown", OS: osName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() @@ -24,7 +31,35 @@ func GetInfo(ctx context.Context) *Info { 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) if err != nil { log.Error(err) From 5a3ee4f9c49c10b79d5f5de300108afde3b4b614 Mon Sep 17 00:00:00 2001 From: hg <36822348+hg@users.noreply.github.com> Date: Fri, 24 Nov 2023 01:15:07 +0600 Subject: [PATCH 24/59] Add systemd .service files (#1316) (#1318) Add systemd .service files --- release_files/systemd/env | 3 ++ .../systemd/netbird-management.service | 41 +++++++++++++++++++ release_files/systemd/netbird-signal.service | 41 +++++++++++++++++++ release_files/systemd/netbird@.service | 41 +++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 release_files/systemd/env create mode 100644 release_files/systemd/netbird-management.service create mode 100644 release_files/systemd/netbird-signal.service create mode 100644 release_files/systemd/netbird@.service diff --git a/release_files/systemd/env b/release_files/systemd/env new file mode 100644 index 000000000..9e7f2e138 --- /dev/null +++ b/release_files/systemd/env @@ -0,0 +1,3 @@ +# Extra flags you might want to pass to the daemon +FLAGS="" + diff --git a/release_files/systemd/netbird-management.service b/release_files/systemd/netbird-management.service new file mode 100644 index 000000000..7fc0aa9ed --- /dev/null +++ b/release_files/systemd/netbird-management.service @@ -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 + diff --git a/release_files/systemd/netbird-signal.service b/release_files/systemd/netbird-signal.service new file mode 100644 index 000000000..c7e775f49 --- /dev/null +++ b/release_files/systemd/netbird-signal.service @@ -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 + diff --git a/release_files/systemd/netbird@.service b/release_files/systemd/netbird@.service new file mode 100644 index 000000000..39e3b6b23 --- /dev/null +++ b/release_files/systemd/netbird@.service @@ -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 + From fdd23d4644168c5d014732b2d1466f4a6b023dc3 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 24 Nov 2023 11:31:22 +0100 Subject: [PATCH 25/59] Remove the gateway check for routes (#1317) Most operating systems add a /32 route for the default gateway address to its routing table This will allow routes to be configured into the system even when the incoming range contains the default gateway. In case a range is a sub-range of an existing route and this range happens to contain the default gateway it attempts to create a default gateway route to prevent loop issues --- client/internal/routemanager/client.go | 2 + client/internal/routemanager/manager.go | 2 +- client/internal/routemanager/systemops_bsd.go | 55 ++++++----- .../internal/routemanager/systemops_linux.go | 34 +++++-- .../routemanager/systemops_nonandroid.go | 91 ++++++++++++++---- .../routemanager/systemops_nonandroid_test.go | 96 ++++++++++++++++--- .../routemanager/systemops_nonlinux.go | 8 +- .../routemanager/systemops_windows.go | 25 +++-- 8 files changed, 236 insertions(+), 77 deletions(-) diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go index fda7b012f..ee98d503d 100644 --- a/client/internal/routemanager/client.go +++ b/client/internal/routemanager/client.go @@ -12,6 +12,8 @@ import ( "github.com/netbirdio/netbird/route" ) +const minRangeBits = 7 + type routerPeerStatus struct { connected bool relayed bool diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index 1f812983c..479ac873f 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -155,7 +155,7 @@ func (m *DefaultManager) classifiesRoutes(newRoutes []*route.Route) (map[string] if !ownNetworkIDs[networkID] { // if prefix is too small, lets assume is a possible default route which is not yet supported // 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", version.NetbirdVersion(), newRoute.Network) continue diff --git a/client/internal/routemanager/systemops_bsd.go b/client/internal/routemanager/systemops_bsd.go index e777ec8ec..b2da8075c 100644 --- a/client/internal/routemanager/systemops_bsd.go +++ b/client/internal/routemanager/systemops_bsd.go @@ -27,24 +27,24 @@ const ( 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) if err != nil { - return false, err + return nil, err } msgs, err := route.ParseRIB(route.RIBTypeRoute, tab) if err != nil { - return false, err + return nil, err } - + var prefixList []netip.Prefix for _, msg := range msgs { m := msg.(*route.RouteMessage) 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 */ { - 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 || @@ -52,31 +52,42 @@ func existsInRouteTable(prefix netip.Prefix) (bool, error) { continue } - dst, err := toIPAddr(m.Addrs[0]) - if err != nil { - return true, fmt.Errorf("unexpected RIB destination: %v", err) + addr, ok := toNetIPAddr(m.Addrs[0]) + if !ok { + continue } - mask, _ := toIPAddr(m.Addrs[2]) - cidr, _ := net.IPMask(mask.To4()).Size() - if dst.String() == prefix.Addr().String() && cidr == prefix.Bits() { - return true, nil + mask, ok := toNetIPMASK(m.Addrs[2]) + if !ok { + continue + } + cidr, _ := mask.Size() + + routePrefix := netip.PrefixFrom(addr, cidr) + if routePrefix.IsValid() { + prefixList = append(prefixList, routePrefix) } } - - return false, nil + return prefixList, nil } -func toIPAddr(a route.Addr) (net.IP, error) { +func toNetIPAddr(a route.Addr) (netip.Addr, bool) { switch t := a.(type) { case *route.Inet4Addr: ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3]) - return ip, nil - case *route.Inet6Addr: - ip := make(net.IP, net.IPv6len) - copy(ip, t.IP[:]) - return ip, nil + addr := netip.MustParseAddr(ip.String()) + return addr, true 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 } } diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index fb2938d55..b5b4f5696 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -60,15 +60,26 @@ func addToRouteTable(prefix netip.Prefix, addr string) error { return nil } -func removeFromRouteTable(prefix netip.Prefix) error { +func removeFromRouteTable(prefix netip.Prefix, addr string) error { _, ipNet, err := net.ParseCIDR(prefix.String()) if err != nil { 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{ Scope: netlink.SCOPE_UNIVERSE, Dst: ipNet, + Gw: ip, } err = netlink.RouteDel(route) @@ -79,15 +90,16 @@ func removeFromRouteTable(prefix netip.Prefix) error { return nil } -func existsInRouteTable(prefix netip.Prefix) (bool, error) { +func getRoutesFromTable() ([]netip.Prefix, error) { tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC) if err != nil { - return true, err + return nil, err } msgs, err := syscall.ParseNetlinkMessage(tab) if err != nil { - return true, err + return nil, err } + var prefixList []netip.Prefix loop: for _, m := range msgs { switch m.Header.Type { @@ -97,7 +109,7 @@ loop: rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) attrs, err := syscall.ParseNetlinkRouteAttr(&m) if err != nil { - return true, err + return nil, err } if rt.Family != syscall.AF_INET { continue loop @@ -105,17 +117,21 @@ loop: for _, attr := range attrs { 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) cidr, _ := mask.Size() - if ip.String() == 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 } func enableIPForwarding() error { diff --git a/client/internal/routemanager/systemops_nonandroid.go b/client/internal/routemanager/systemops_nonandroid.go index 3ddf72686..b229a580f 100644 --- a/client/internal/routemanager/systemops_nonandroid.go +++ b/client/internal/routemanager/systemops_nonandroid.go @@ -14,17 +14,6 @@ import ( var errRouteNotFound = fmt.Errorf("route not found") 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) if err != nil { return err @@ -34,20 +23,82 @@ func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { return nil } - return addToRouteTable(prefix, addr) -} - -func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error { - addrIP := net.ParseIP(addr) - prefixGateway, err := getExistingRIBRouteGateway(prefix) + ok, err = isSubRange(prefix) if err != nil { 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 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) { diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index bb31834d1..3646dc3da 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -24,13 +24,13 @@ func TestAddRemoveRoutes(t *testing.T) { 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"), shouldRouteToWireguard: 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"), shouldRouteToWireguard: false, shouldBeRemoved: false, @@ -51,29 +51,32 @@ func TestAddRemoveRoutes(t *testing.T) { require.NoError(t, err, "should create testing wireguard interface") 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) - require.NoError(t, err, "should not return err") + require.NoError(t, err, "getExistingRIBRouteGateway should not return err") if testCase.shouldRouteToWireguard { require.Equal(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP") } else { require.NotEqual(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to a different interface") } + exists, err := existsInRouteTable(testCase.prefix) + require.NoError(t, err, "existsInRouteTable should not return err") + if exists && testCase.shouldRouteToWireguard { + err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String()) + require.NoError(t, err, "removeFromRouteTableIfNonSystem should not return err") - err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String()) - require.NoError(t, err, "should not return err") + prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix) + require.NoError(t, err, "getExistingRIBRouteGateway should not return err") - prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix) - require.NoError(t, err, "should not return err") + internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) + require.NoError(t, err) - internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) - require.NoError(t, err) - - if testCase.shouldBeRemoved { - require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway") - } else { - require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway") + if testCase.shouldBeRemoved { + require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway") + } else { + require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway") + } } }) } @@ -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) + } + } +} diff --git a/client/internal/routemanager/systemops_nonlinux.go b/client/internal/routemanager/systemops_nonlinux.go index 537042099..47bd60eb0 100644 --- a/client/internal/routemanager/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops_nonlinux.go @@ -21,8 +21,12 @@ func addToRouteTable(prefix netip.Prefix, addr string) error { return nil } -func removeFromRouteTable(prefix netip.Prefix) error { - cmd := exec.Command("route", "delete", prefix.String()) +func removeFromRouteTable(prefix netip.Prefix, addr string) error { + args := []string{"delete", prefix.String()} + if runtime.GOOS == "darwin" { + args = append(args, addr) + } + cmd := exec.Command("route", args...) out, err := cmd.Output() if err != nil { return err diff --git a/client/internal/routemanager/systemops_windows.go b/client/internal/routemanager/systemops_windows.go index 2233748bf..309c184b9 100644 --- a/client/internal/routemanager/systemops_windows.go +++ b/client/internal/routemanager/systemops_windows.go @@ -15,23 +15,32 @@ type Win32_IP4RouteTable struct { Mask string } -func existsInRouteTable(prefix netip.Prefix) (bool, error) { +func getRoutesFromTable() ([]netip.Prefix, error) { var routes []Win32_IP4RouteTable query := "SELECT Destination, Mask FROM Win32_IP4RouteTable" err := wmi.Query(query, &routes) if err != nil { - return true, err + return nil, err } + var prefixList []netip.Prefix for _, route := range routes { - ip := net.ParseIP(route.Mask) - ip = ip.To4() - mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]) + addr, err := netip.ParseAddr(route.Destination) + if err != nil { + 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() - 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 } From cf9e447bf0b3b2579f4d34459dc8e4a4759dd413 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 24 Nov 2023 14:27:40 +0100 Subject: [PATCH 26/59] Update signing pipelines to version 0.0.10 (#1329) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5833638c5..c3d43a65b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ on: - 'client/ui/**' env: - SIGN_PIPE_VER: "v0.0.9" + SIGN_PIPE_VER: "v0.0.10" GORELEASER_VER: "v1.14.1" concurrency: From 0ca06b566ae168b42562d6b44073cfbda69ba9c1 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 24 Nov 2023 17:49:39 +0100 Subject: [PATCH 27/59] Add Windows version to correct system info field (#1330) --- client/system/info_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/system/info_windows.go b/client/system/info_windows.go index ede689fa1..69b4ad008 100644 --- a/client/system/info_windows.go +++ b/client/system/info_windows.go @@ -22,7 +22,7 @@ type Win32_OperatingSystem struct { func GetInfo(ctx context.Context) *Info { osName, osVersion := getOSNameAndVersion() buildVersion := getBuildVersion() - gio := &Info{Kernel: "windows", OSVersion: buildVersion, Core: osVersion, Platform: "unknown", OS: osName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + gio := &Info{Kernel: "windows", OSVersion: osVersion, Core: buildVersion, Platform: "unknown", OS: osName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() From 63d211c698d319a7fe673399fcaa770b957b67e8 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Mon, 27 Nov 2023 13:01:00 +0100 Subject: [PATCH 28/59] Prepare regexps on compile time (#1327) --- client/internal/dns/network_manager_linux.go | 9 ++------- dns/dns.go | 4 ++-- management/server/account.go | 5 +++-- management/server/http/middleware/access_control.go | 10 +++------- management/server/metrics/selfhosted.go | 8 +------- management/server/nameserver.go | 3 ++- version/version.go | 11 +++++++++++ 7 files changed, 24 insertions(+), 26 deletions(-) diff --git a/client/internal/dns/network_manager_linux.go b/client/internal/dns/network_manager_linux.go index 0b7ae7d4c..8bc3ef297 100644 --- a/client/internal/dns/network_manager_linux.go +++ b/client/internal/dns/network_manager_linux.go @@ -7,12 +7,12 @@ import ( "encoding/binary" "fmt" "net/netip" - "regexp" "time" "github.com/godbus/dbus/v5" "github.com/hashicorp/go-version" "github.com/miekg/dns" + nbversion "github.com/netbirdio/netbird/version" log "github.com/sirupsen/logrus" ) @@ -289,12 +289,7 @@ func isNetworkManagerSupportedVersion() bool { } func parseVersion(inputVersion string) (*version.Version, error) { - reg, err := regexp.Compile(version.SemverRegexpRaw) - if err != nil { - return nil, err - } - - if inputVersion == "" || !reg.MatchString(inputVersion) { + if inputVersion == "" || !nbversion.SemverRegexp.MatchString(inputVersion) { return nil, fmt.Errorf("couldn't parse the provided version: Not SemVer") } diff --git a/dns/dns.go b/dns/dns.go index b58b79b53..18528c743 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -86,6 +86,8 @@ func (s SimpleRecord) Len() uint16 { } } +var invalidHostMatcher = regexp.MustCompile(invalidHostLabel) + // GetParsedDomainLabel returns a domain label with max 59 characters, // parsed for old Hosts.txt requirements, and converted to ASCII and lowercase 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) } - invalidHostMatcher := regexp.MustCompile(invalidHostLabel) - validHost := strings.ToLower(invalidHostMatcher.ReplaceAllString(ascii, "-")) if len(validHost) > 58 { validHost = validHost[:59] diff --git a/management/server/account.go b/management/server/account.go index 8f9b1e151..fb5198190 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -1596,9 +1596,10 @@ func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, er return am.peersUpdateManager.GetAllConnectedPeers(), nil } +var invalidDomainRegexp = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) + func isDomainValid(domain string) bool { - re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) - return re.MatchString(domain) + return invalidDomainRegexp.MatchString(domain) } // GetDNSDomain returns the configured dnsDomain diff --git a/management/server/http/middleware/access_control.go b/management/server/http/middleware/access_control.go index 434f2f644..31b5a2a9d 100644 --- a/management/server/http/middleware/access_control.go +++ b/management/server/http/middleware/access_control.go @@ -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 // It also adds 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 { case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut: - ok, err := regexp.MatchString(`^.*/api/users/.*/tokens.*$`, r.URL.Path) - if err != nil { - log.Debugf("regex failed") - util.WriteError(status.Errorf(status.Internal, ""), w) - return - } - if ok { + if tokenPathRegexp.MatchString(r.URL.Path) { log.Debugf("valid Path") h.ServeHTTP(w, r) return diff --git a/management/server/metrics/selfhosted.go b/management/server/metrics/selfhosted.go index cf6b2e440..e5ef6893e 100644 --- a/management/server/metrics/selfhosted.go +++ b/management/server/metrics/selfhosted.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "net/http" - "regexp" "sort" "strings" "time" @@ -381,15 +380,10 @@ func createPostRequest(ctx context.Context, endpoint string, payloadStr string) } func getMinMaxVersion(inputList []string) (string, string) { - reg, err := regexp.Compile(version.SemverRegexpRaw) - if err != nil { - return "", "" - } - versions := make([]*version.Version, 0) for _, raw := range inputList { - if raw != "" && reg.MatchString(raw) { + if raw != "" && nbversion.SemverRegexp.MatchString(raw) { v, err := version.NewVersion(raw) if err == nil { versions = append(versions, v) diff --git a/management/server/nameserver.go b/management/server/nameserver.go index 807adf28a..1b8d59e29 100644 --- a/management/server/nameserver.go +++ b/management/server/nameserver.go @@ -267,8 +267,9 @@ func validateGroups(list []string, groups map[string]*Group) error { return nil } +var domainMatcher = regexp.MustCompile(domainPattern) + func validateDomain(domain string) error { - domainMatcher := regexp.MustCompile(domainPattern) if !domainMatcher.MatchString(domain) { return errors.New("domain should consists of only letters, numbers, and hyphens with no leading, trailing hyphens, or spaces") } diff --git a/version/version.go b/version/version.go index d9c119f90..d70a5effa 100644 --- a/version/version.go +++ b/version/version.go @@ -1,8 +1,19 @@ package version +import ( + "regexp" + + v "github.com/hashicorp/go-version" +) + // will be replaced with the release version when using goreleaser var version = "development" +var ( + VersionRegexp = regexp.MustCompile("^" + v.VersionRegexpRaw + "$") + SemverRegexp = regexp.MustCompile("^" + v.SemverRegexpRaw + "$") +) + // NetbirdVersion returns the Netbird version func NetbirdVersion() string { return version From d1a323fa9dc36e34f0af5703ff162a71c197f6e8 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Mon, 27 Nov 2023 16:40:02 +0100 Subject: [PATCH 29/59] Add gocritic linter (#1324) * Add gocritic linter `gocritic` provides diagnostics that check for bugs, performance, and style issues We disable the following checks: - commentFormatting - captLocal - deprecatedComment This PR contains many `//nolint:gocritic` to disable `appendAssign`. --- .golangci.yaml | 7 ++++++ client/cmd/status.go | 4 ++-- client/firewall/iptables/manager_linux.go | 12 ++++++---- client/firewall/nftables/manager_linux.go | 2 +- client/internal/acl/manager_test.go | 18 +++++++------- client/internal/auth/device_flow.go | 5 ++-- client/internal/auth/oauth.go | 8 +++---- client/internal/config.go | 4 ++-- client/internal/dns/network_manager_linux.go | 2 +- client/internal/dns/server.go | 2 +- client/internal/ebpf/ebpf/manager_linux.go | 2 +- client/internal/engine.go | 24 ++++++++----------- client/internal/engine_test.go | 2 +- .../internal/routemanager/iptables_linux.go | 8 +++---- .../internal/routemanager/nftables_linux.go | 8 +++---- .../routemanager/nftables_linux_test.go | 20 ++++++++-------- client/ssh/client.go | 8 +++---- client/system/info_linux.go | 4 ++-- iface/wg_configurer_nonandroid.go | 2 +- management/client/client_test.go | 14 +++++------ management/server/account.go | 7 +++--- management/server/http/policies_handler.go | 2 +- management/server/http/setupkeys_handler.go | 9 +++---- management/server/idp/zitadel.go | 8 +++---- management/server/metrics/selfhosted.go | 10 ++++---- management/server/network.go | 2 +- management/server/policy.go | 2 +- management/server/setupkey.go | 2 +- sharedsock/sock_linux.go | 2 +- signal/client/grpc.go | 9 +++---- util/retry.go | 2 +- 31 files changed, 110 insertions(+), 101 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index d0926fffc..d847e63c3 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -12,6 +12,12 @@ linters-settings: # Default: false check-type-assertions: false + gocritic: + disabled-checks: + - commentFormatting + - captLocal + - deprecatedComment + govet: # Enable all analyzers. # Default: false @@ -42,6 +48,7 @@ linters: - dupword # dupword checks for duplicate words in the source code - durationcheck # durationcheck checks for two durations multiplied together - forbidigo # forbidigo forbids identifiers + - gocritic # provides diagnostics that check for bugs, performance and style issues - mirror # mirror reports wrong mirror patterns of bytes/strings usage - misspell # misspess finds commonly misspelled English words in comments - nilerr # finds the code that returns nil even if it checks that the error is not nil diff --git a/client/cmd/status.go b/client/cmd/status.go index 9dfd042f8..74d2061ff 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -234,7 +234,7 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput { continue } if isPeerConnected { - peersConnected = peersConnected + 1 + peersConnected++ localICE = pbPeerState.GetLocalIceCandidateType() remoteICE = pbPeerState.GetRemoteIceCandidateType() @@ -407,7 +407,7 @@ func parsePeers(peers peersStateOutput) string { peerState.LastStatusUpdate.Format("2006-01-02 15:04:05"), ) - peersString = peersString + peerString + peersString += peerString } return peersString } diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go index 4ce904df6..b9243f4ca 100644 --- a/client/firewall/iptables/manager_linux.go +++ b/client/firewall/iptables/manager_linux.go @@ -463,14 +463,16 @@ func (m *Manager) actionToStr(action fw.Action) string { } func (m *Manager) transformIPsetName(ipsetName string, sPort, dPort string) string { - if ipsetName == "" { + switch { + case ipsetName == "": return "" - } else if sPort != "" && dPort != "" { + case sPort != "" && dPort != "": return ipsetName + "-sport-dport" - } else if sPort != "" { + case sPort != "": return ipsetName + "-sport" - } else if dPort != "" { + case dPort != "": return ipsetName + "-dport" + default: + return ipsetName } - return ipsetName } diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index 6c46048b4..93379bad8 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -791,7 +791,7 @@ func (m *Manager) flushWithBackoff() (err error) { return err } time.Sleep(backoffTime) - backoffTime = backoffTime * 2 + backoffTime *= 2 continue } break diff --git a/client/internal/acl/manager_test.go b/client/internal/acl/manager_test.go index 25de2a57f..d55a1cad6 100644 --- a/client/internal/acl/manager_test.go +++ b/client/internal/acl/manager_test.go @@ -189,31 +189,33 @@ func TestDefaultManagerSquashRules(t *testing.T) { } 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) return - } else if r.Direction != mgmProto.FirewallRule_IN { + case r.Direction != mgmProto.FirewallRule_IN: t.Errorf("direction should be IN, got: %v", r.Direction) return - } else if r.Protocol != mgmProto.FirewallRule_ALL { + case r.Protocol != mgmProto.FirewallRule_ALL: t.Errorf("protocol should be ALL, got: %v", r.Protocol) return - } else if r.Action != mgmProto.FirewallRule_ACCEPT { + case r.Action != mgmProto.FirewallRule_ACCEPT: t.Errorf("action should be ACCEPT, got: %v", r.Action) return } 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) return - } else if r.Direction != mgmProto.FirewallRule_OUT { + case r.Direction != mgmProto.FirewallRule_OUT: t.Errorf("direction should be OUT, got: %v", r.Direction) return - } else if r.Protocol != mgmProto.FirewallRule_ALL { + case r.Protocol != mgmProto.FirewallRule_ALL: t.Errorf("protocol should be ALL, got: %v", r.Protocol) return - } else if r.Action != mgmProto.FirewallRule_ACCEPT { + case r.Action != mgmProto.FirewallRule_ACCEPT: t.Errorf("action should be ACCEPT, got: %v", r.Action) return } diff --git a/client/internal/auth/device_flow.go b/client/internal/auth/device_flow.go index c28e42772..3c51fe4f5 100644 --- a/client/internal/auth/device_flow.go +++ b/client/internal/auth/device_flow.go @@ -4,12 +4,13 @@ import ( "context" "encoding/json" "fmt" - "github.com/netbirdio/netbird/client/internal" "io" "net/http" "net/url" "strings" "time" + + "github.com/netbirdio/netbird/client/internal" ) // 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" { continue } else if tokenResponse.Error == "slow_down" { - interval = interval + (3 * time.Second) + interval += (3 * time.Second) ticker.Reset(interval) continue } diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go index 82adf91b9..23bde2be2 100644 --- a/client/internal/auth/oauth.go +++ b/client/internal/auth/oauth.go @@ -92,15 +92,15 @@ func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAu func authenticateWithDeviceCodeFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) { deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) if err != nil { - s, ok := gstatus.FromError(err) - if ok && s.Code() == codes.NotFound { + switch s, ok := gstatus.FromError(err); { + case ok && s.Code() == codes.NotFound: return nil, fmt.Errorf("no SSO provider returned from management. " + "Please proceed with setting up this device 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, "+ "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) } } diff --git a/client/internal/config.go b/client/internal/config.go index cd665016b..646848a2f 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -273,9 +273,9 @@ func parseURL(serviceName, serviceURL string) (*url.URL, error) { if parsedMgmtURL.Port() == "" { switch parsedMgmtURL.Scheme { case "https": - parsedMgmtURL.Host = parsedMgmtURL.Host + ":443" + parsedMgmtURL.Host += ":443" case "http": - parsedMgmtURL.Host = parsedMgmtURL.Host + ":80" + parsedMgmtURL.Host += ":80" default: log.Infof("unable to determine a default port for schema %s in URL %s", parsedMgmtURL.Scheme, serviceURL) } diff --git a/client/internal/dns/network_manager_linux.go b/client/internal/dns/network_manager_linux.go index 8bc3ef297..d5c2f60b2 100644 --- a/client/internal/dns/network_manager_linux.go +++ b/client/internal/dns/network_manager_linux.go @@ -122,7 +122,7 @@ func (n *networkManagerDbusConfigurator) applyDNSConfig(config hostDNSConfig) er searchDomains = append(searchDomains, dns.Fqdn(dConf.domain)) } - newDomainList := append(searchDomains, matchDomains...) + newDomainList := append(searchDomains, matchDomains...) //nolint:gocritic priority := networkManagerDbusSearchDomainOnlyPriority switch { diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 6655a6e4e..9bb9a76a9 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -252,7 +252,7 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error { if err != nil { return fmt.Errorf("not applying dns update, error: %v", err) } - muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) + muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) //nolint:gocritic s.updateMux(muxUpdates) s.updateLocalResolver(localRecords) diff --git a/client/internal/ebpf/ebpf/manager_linux.go b/client/internal/ebpf/ebpf/manager_linux.go index 9dfdc0ad1..7520a6387 100644 --- a/client/internal/ebpf/ebpf/manager_linux.go +++ b/client/internal/ebpf/ebpf/manager_linux.go @@ -50,7 +50,7 @@ func GetEbpfManagerInstance() manager.Manager { } func (tf *GeneralManager) setFeatureFlag(feature uint16) { - tf.featureFlags = tf.featureFlags | feature + tf.featureFlags |= feature } func (tf *GeneralManager) loadXdp() error { diff --git a/client/internal/engine.go b/client/internal/engine.go index 35fd822f2..4d461b746 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -204,14 +204,12 @@ func (e *Engine) Start() error { e.dnsServer = dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener) go e.mobileDep.DnsReadyListener.OnReady() } - } else { + } else if e.dnsServer == nil { // todo fix custom address - if e.dnsServer == nil { - e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) - if err != nil { - e.close() - return err - } + e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) + if err != nil { + e.close() + return err } } @@ -490,15 +488,13 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error { } else { log.Debugf("SSH server is already running") } - } else { + } else if !isNil(e.sshServer) { // Disable SSH server request, so stop it if it was running - if !isNil(e.sshServer) { - err := e.sshServer.Stop() - if err != nil { - log.Warnf("failed to stop SSH server %v", err) - } - e.sshServer = nil + err := e.sshServer.Stop() + if err != nil { + log.Warnf("failed to stop SSH server %v", err) } + e.sshServer = nil } return nil } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index a855cf051..08cd29da8 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -869,7 +869,7 @@ loop: case <-ticker.C: totalConnected := 0 for _, engine := range engines { - totalConnected = totalConnected + getConnectedPeers(engine) + totalConnected += getConnectedPeers(engine) } if totalConnected == expectedConnected { log.Infof("total connected=%d", totalConnected) diff --git a/client/internal/routemanager/iptables_linux.go b/client/internal/routemanager/iptables_linux.go index 9f6019305..e9fbb7d3c 100644 --- a/client/internal/routemanager/iptables_linux.go +++ b/client/internal/routemanager/iptables_linux.go @@ -173,7 +173,7 @@ func (i *iptablesManager) addJumpRules() error { return err } if i.ipv4Client != nil { - rule := append(iptablesDefaultForwardingRule, ipv4Forwarding) + rule := append(iptablesDefaultForwardingRule, ipv4Forwarding) //nolint:gocritic err = i.ipv4Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...) if err != nil { @@ -181,7 +181,7 @@ func (i *iptablesManager) addJumpRules() error { } i.rules[ipv4][ipv4Forwarding] = rule - rule = append(iptablesDefaultNatRule, ipv4Nat) + rule = append(iptablesDefaultNatRule, ipv4Nat) //nolint:gocritic err = i.ipv4Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...) if err != nil { return err @@ -190,14 +190,14 @@ func (i *iptablesManager) addJumpRules() error { } if i.ipv6Client != nil { - rule := append(iptablesDefaultForwardingRule, ipv6Forwarding) + rule := append(iptablesDefaultForwardingRule, ipv6Forwarding) //nolint:gocritic err = i.ipv6Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...) if err != nil { return err } i.rules[ipv6][ipv6Forwarding] = rule - rule = append(iptablesDefaultNatRule, ipv6Nat) + rule = append(iptablesDefaultNatRule, ipv6Nat) //nolint:gocritic err = i.ipv6Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...) if err != nil { return err diff --git a/client/internal/routemanager/nftables_linux.go b/client/internal/routemanager/nftables_linux.go index e62b1a404..3ecfa9630 100644 --- a/client/internal/routemanager/nftables_linux.go +++ b/client/internal/routemanager/nftables_linux.go @@ -300,7 +300,7 @@ func (n *nftablesManager) acceptForwardRule(sourceNetwork string) error { dst := generateCIDRMatcherExpressions("destination", "0.0.0.0/0") var exprs []expr.Any - exprs = append(src, append(dst, &expr.Verdict{ + exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic Kind: expr.VerdictAccept, })...) @@ -322,7 +322,7 @@ func (n *nftablesManager) acceptForwardRule(sourceNetwork string) error { src = generateCIDRMatcherExpressions("source", "0.0.0.0/0") dst = generateCIDRMatcherExpressions("destination", sourceNetwork) - exprs = append(src, append(dst, &expr.Verdict{ + exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic Kind: expr.VerdictAccept, })...) @@ -421,9 +421,9 @@ func (n *nftablesManager) insertRoutingRule(format, chain string, pair routerPai var expression []expr.Any if isNat { - expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) + expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...) //nolint:gocritic } else { - expression = append(sourceExp, append(destExp, exprCounterAccept...)...) + expression = append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic } ruleKey := genKey(format, pair.ID) diff --git a/client/internal/routemanager/nftables_linux_test.go b/client/internal/routemanager/nftables_linux_test.go index dec800156..d60d53e50 100644 --- a/client/internal/routemanager/nftables_linux_test.go +++ b/client/internal/routemanager/nftables_linux_test.go @@ -44,7 +44,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) { sourceExp := generateCIDRMatcherExpressions("source", pair.source) destExp := generateCIDRMatcherExpressions("destination", pair.destination) - forward4Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) + forward4Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic forward4RuleKey := genKey(forwardingFormat, pair.ID) inserted4Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ Table: manager.tableIPv4, @@ -53,7 +53,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) { 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) inserted4Nat := nftablesTestingClient.InsertRule(&nftables.Rule{ @@ -76,7 +76,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) { sourceExp = generateCIDRMatcherExpressions("source", pair.source) destExp = generateCIDRMatcherExpressions("destination", pair.destination) - forward6Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) + forward6Exp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic forward6RuleKey := genKey(forwardingFormat, pair.ID) inserted6Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ Table: manager.tableIPv6, @@ -85,7 +85,7 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) { 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) inserted6Nat := nftablesTestingClient.InsertRule(&nftables.Rule{ @@ -149,7 +149,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source) destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination) - testingExpression := append(sourceExp, destExp...) + testingExpression := append(sourceExp, destExp...) //nolint:gocritic fwdRuleKey := genKey(forwardingFormat, testCase.inputPair.ID) found := 0 @@ -188,7 +188,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source) destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination) - testingExpression = append(sourceExp, destExp...) + testingExpression = append(sourceExp, destExp...) //nolint:gocritic inFwdRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID) found = 0 @@ -252,7 +252,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source) 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) insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ Table: table, @@ -261,7 +261,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { 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) insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{ @@ -274,7 +274,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source) 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) insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ Table: table, @@ -283,7 +283,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { 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) insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{ diff --git a/client/ssh/client.go b/client/ssh/client.go index 29ebb2481..2dc70e8fc 100644 --- a/client/ssh/client.go +++ b/client/ssh/client.go @@ -2,11 +2,12 @@ package ssh import ( "fmt" - "golang.org/x/crypto/ssh" - "golang.org/x/term" "net" "os" "time" + + "golang.org/x/crypto/ssh" + "golang.org/x/term" ) // Client wraps crypto/ssh Client to simplify usage @@ -73,8 +74,7 @@ func (c *Client) OpenTerminal() error { if err := session.Wait(); err != nil { if e, ok := err.(*ssh.ExitError); ok { - switch e.ExitStatus() { - case 130: + if e.ExitStatus() == 130 { return nil } } diff --git a/client/system/info_linux.go b/client/system/info_linux.go index a4ab9f931..21a4d482a 100644 --- a/client/system/info_linux.go +++ b/client/system/info_linux.go @@ -44,8 +44,8 @@ func GetInfo(ctx context.Context) *Info { } } - osStr := strings.Replace(info, "\n", "", -1) - osStr = strings.Replace(osStr, "\r\n", "", -1) + osStr := strings.ReplaceAll(info, "\n", "") + osStr = strings.ReplaceAll(osStr, "\r\n", "") osInfo := strings.Split(osStr, " ") if osName == "" { osName = osInfo[3] diff --git a/iface/wg_configurer_nonandroid.go b/iface/wg_configurer_nonandroid.go index 6749c0966..3d9aff7a9 100644 --- a/iface/wg_configurer_nonandroid.go +++ b/iface/wg_configurer_nonandroid.go @@ -141,7 +141,7 @@ func (c *wGConfigurer) removeAllowedIP(peerKey string, allowedIP string) error { for i, existingAllowedIP := range existingPeer.AllowedIPs { 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 } } diff --git a/management/client/client_test.go b/management/client/client_test.go index c5e5b8140..9ebb58420 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -285,7 +285,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) { testKey, err := wgtypes.GenerateKey() if err != nil { - log.Fatal(err) + t.Fatal(err) } serverAddr := lis.Addr().String() @@ -293,12 +293,12 @@ func Test_SystemMetaDataFromClient(t *testing.T) { testClient, err := NewClient(ctx, serverAddr, testKey, false) if err != nil { - log.Fatalf("error while creating testClient: %v", err) + t.Fatalf("error while creating testClient: %v", err) } key, err := testClient.GetServerPublicKey() 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 @@ -364,7 +364,7 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) { testKey, err := wgtypes.GenerateKey() if err != nil { - log.Fatal(err) + t.Fatal(err) } serverAddr := lis.Addr().String() @@ -372,7 +372,7 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) { client, err := NewClient(ctx, serverAddr, testKey, false) if err != nil { - log.Fatalf("error while creating testClient: %v", err) + t.Fatalf("error while creating testClient: %v", err) } expectedFlowInfo := &mgmtProto.DeviceAuthorizationFlow{ @@ -408,7 +408,7 @@ func Test_GetPKCEAuthorizationFlow(t *testing.T) { testKey, err := wgtypes.GenerateKey() if err != nil { - log.Fatal(err) + t.Fatal(err) } serverAddr := lis.Addr().String() @@ -416,7 +416,7 @@ func Test_GetPKCEAuthorizationFlow(t *testing.T) { client, err := NewClient(ctx, serverAddr, testKey, false) if err != nil { - log.Fatalf("error while creating testClient: %v", err) + t.Fatalf("error while creating testClient: %v", err) } expectedFlowInfo := &mgmtProto.PKCEAuthorizationFlow{ diff --git a/management/server/account.go b/management/server/account.go index fb5198190..09071d103 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -950,14 +950,15 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er _, err := am.Store.GetAccount(accountId) statusErr, _ := status.FromError(err) - if err == nil { + switch { + case err == nil: log.Warnf("an account with ID already exists, retrying...") continue - } else if statusErr.Type() == status.NotFound { + case statusErr.Type() == status.NotFound: newAccount := newAccountWithId(accountId, userID, domain) am.StoreEvent(userID, newAccount.Id, accountId, activity.AccountCreated, nil) return newAccount, nil - } else { + default: return nil, err } } diff --git a/management/server/http/policies_handler.go b/management/server/http/policies_handler.go index c8f58f8a4..f8c876a41 100644 --- a/management/server/http/policies_handler.go +++ b/management/server/http/policies_handler.go @@ -300,7 +300,7 @@ func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Polic Action: api.PolicyRuleAction(r.Action), } if len(r.Ports) != 0 { - portsCopy := r.Ports[:] + portsCopy := r.Ports rule.Ports = &portsCopy } for _, gid := range r.Sources { diff --git a/management/server/http/setupkeys_handler.go b/management/server/http/setupkeys_handler.go index cddae672c..4adf3fdd0 100644 --- a/management/server/http/setupkeys_handler.go +++ b/management/server/http/setupkeys_handler.go @@ -192,13 +192,14 @@ func writeSuccess(w http.ResponseWriter, key *server.SetupKey) { func toResponseBody(key *server.SetupKey) *api.SetupKey { var state string - if key.IsExpired() { + switch { + case key.IsExpired(): state = "expired" - } else if key.IsRevoked() { + case key.IsRevoked(): state = "revoked" - } else if key.IsOverUsed() { + case key.IsOverUsed(): state = "overused" - } else { + default: state = "valid" } diff --git a/management/server/idp/zitadel.go b/management/server/idp/zitadel.go index 5325e51be..926f078b2 100644 --- a/management/server/idp/zitadel.go +++ b/management/server/idp/zitadel.go @@ -463,11 +463,9 @@ func (zp zitadelProfile) userData() *UserData { if zp.Human != nil { email = zp.Human.Email.Email name = zp.Human.Profile.DisplayName - } else { - if len(zp.LoginNames) > 0 { - email = zp.LoginNames[0] - name = zp.LoginNames[0] - } + } else if len(zp.LoginNames) > 0 { + email = zp.LoginNames[0] + name = zp.LoginNames[0] } return &UserData{ diff --git a/management/server/metrics/selfhosted.go b/management/server/metrics/selfhosted.go index e5ef6893e..90d69b47b 100644 --- a/management/server/metrics/selfhosted.go +++ b/management/server/metrics/selfhosted.go @@ -200,14 +200,14 @@ func (w *Worker) generateProperties() properties { expirationEnabled++ } - groups = groups + len(account.Groups) - routes = routes + len(account.Routes) + groups += len(account.Groups) + routes += len(account.Routes) for _, route := range account.Routes { if len(route.PeerGroups) > 0 { routesWithRGGroups++ } } - nameservers = nameservers + len(account.NameServerGroups) + nameservers += len(account.NameServerGroups) for _, policy := range account.Policies { for _, rule := range policy.Rules { @@ -231,10 +231,10 @@ func (w *Worker) generateProperties() properties { } for _, key := range account.SetupKeys { - setupKeysUsage = setupKeysUsage + key.UsedTimes + setupKeysUsage += key.UsedTimes if key.Ephemeral { ephemeralPeersSKs++ - ephemeralPeersSKUsage = ephemeralPeersSKUsage + key.UsedTimes + ephemeralPeersSKUsage += key.UsedTimes } } diff --git a/management/server/network.go b/management/server/network.go index c5b165cae..daa67f2dc 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -66,7 +66,7 @@ func NewNetwork() *Network { func (n *Network) IncSerial() { n.mu.Lock() defer n.mu.Unlock() - n.Serial = n.Serial + 1 + n.Serial++ } // CurrentSerial returns the Network.Serial of the network (latest state id) diff --git a/management/server/policy.go b/management/server/policy.go index 24188f93c..fc222cb40 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -406,7 +406,7 @@ func (am *DefaultAccountManager) ListPolicies(accountID, userID string) ([]*Poli 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) { diff --git a/management/server/setupkey.go b/management/server/setupkey.go index 3bd14b61e..d347fb181 100644 --- a/management/server/setupkey.go +++ b/management/server/setupkey.go @@ -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 func (key *SetupKey) IncrementUsage() *SetupKey { c := key.Copy() - c.UsedTimes = c.UsedTimes + 1 + c.UsedTimes++ c.LastUsed = time.Now().UTC() return c } diff --git a/sharedsock/sock_linux.go b/sharedsock/sock_linux.go index b823bb508..656fdc8ca 100644 --- a/sharedsock/sock_linux.go +++ b/sharedsock/sock_linux.go @@ -248,7 +248,7 @@ func (s *SharedSocket) ReadFrom(b []byte) (n int, addr net.Addr, err error) { decodedLayers := make([]gopacket.LayerType, 0, 3) - err = parser.DecodeLayers(pkt.buf[:], &decodedLayers) + err = parser.DecodeLayers(pkt.buf, &decodedLayers) if err != nil { return 0, nil, err } diff --git a/signal/client/grpc.go b/signal/client/grpc.go index 9f70234e9..7aa9f9ce9 100644 --- a/signal/client/grpc.go +++ b/signal/client/grpc.go @@ -354,16 +354,17 @@ func (c *GrpcClient) receive(stream proto.SignalExchange_ConnectStreamClient, for { 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)") return err - } else if s.Code() == codes.Unavailable { + case s.Code() == codes.Unavailable: log.Debugf("Signal Service is unavailable") return err - } else if err == io.EOF { + case err == io.EOF: log.Debugf("Signal Service stream closed by server") return err - } else if err != nil { + case err != nil: return err } log.Tracef("received a new message from Peer [fingerprint: %s]", msg.Key) diff --git a/util/retry.go b/util/retry.go index 3bffcf288..2d5fbf6cf 100644 --- a/util/retry.go +++ b/util/retry.go @@ -15,7 +15,7 @@ func Retry(attempts int, sleep time.Duration, toExec func() error, onError func( if attempts--; attempts > 0 { jitter := time.Duration(rand.Int63n(int64(sleep))) - sleep = sleep + jitter/2 + sleep += jitter / 2 onError(err) time.Sleep(sleep) From b7c0eba1e541599fa302a9d4013d5f05f1b00c0e Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Nov 2023 17:04:40 +0100 Subject: [PATCH 30/59] add extra settings struct --- management/server/account.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/management/server/account.go b/management/server/account.go index 8f9b1e151..bea1886a9 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -158,6 +158,14 @@ type Settings struct { // JWTGroupsClaimName from which we extract groups name to add it to account groups JWTGroupsClaimName string + + // Extra contains additional settings that are not supported in the open-source version + Extra *ExtraSettings +} + +type ExtraSettings struct { + // PeerApprovalEnabled enables or disables the need for peers to be approved by an administrator + PeerApprovalEnabled bool } // Copy copies the Settings struct From a7e55cc5e399f85b7f5c131fb1828781fd3549a8 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 11:44:08 +0100 Subject: [PATCH 31/59] add signatures and frame for peer approval --- go.mod | 2 +- go.sum | 14 +++++++-- management/server/account.go | 35 ++++++++++++++++++++-- management/server/activity/codes.go | 12 ++++++++ management/server/http/accounts_handler.go | 26 +++++++++++----- management/server/http/api/openapi.yml | 17 +++++++++++ management/server/http/api/types.gen.go | 19 ++++++++++++ management/server/http/peers_handler.go | 7 +++++ management/server/peer.go | 24 +++++++++++++-- 9 files changed, 140 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index c6c8221e1..d4b76653e 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 diff --git a/go.sum b/go.sum index 84b8816e9..f316fa058 100644 --- a/go.sum +++ b/go.sum @@ -495,8 +495,18 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 h1:zhe8qseauBuYOS910jpl5sv8Tb+36zxQPXrwYXqll0g= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944 h1:n7o2/NCZzn0+73LGdl/VMh7DOTdWZ98le2woeZ4HlB0= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a h1:6aipBr80s0GPKO9Wl+f5TUOSwebQ91uX2thk9tElyqc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127165733-0d0650c84683 h1:jJMO8KL2u3ok5VtGgZtFpuVK0GBEXXIb84idlmqGe68= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127165733-0d0650c84683/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127170523-a11fee39970a h1:DIe9xdl6RcxeZFu5Pr3OPC8SHM6yadF212W3LJlzfhQ= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127170523-a11fee39970a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a h1:iMEPP7MC3/7DTs/BNMshsBoviG3yWSTRbIzXKdrUwHw= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 h1:PTQ2SSijkoN8Qkctq9oLzEdzCLLv7WoD2dqScmpb15o= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= diff --git a/management/server/account.go b/management/server/account.go index bea1886a9..a08416e2c 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -17,6 +17,7 @@ import ( "github.com/eko/gocache/v3/cache" cacheStore "github.com/eko/gocache/v3/store" + "github.com/netbirdio/management-integrations/integrations" gocache "github.com/patrickmn/go-cache" "github.com/rs/xid" log "github.com/sirupsen/logrus" @@ -159,12 +160,12 @@ type Settings struct { // JWTGroupsClaimName from which we extract groups name to add it to account groups JWTGroupsClaimName string - // Extra contains additional settings that are not supported in the open-source version + // Extra is a dictionary of Account settings Extra *ExtraSettings } type ExtraSettings struct { - // PeerApprovalEnabled enables or disables the need for peers to be approved by an administrator + // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator PeerApprovalEnabled bool } @@ -352,7 +353,20 @@ func (a *Account) GetGroup(groupID string) *Group { // GetPeerNetworkMap returns a group by ID if exists, nil otherwise func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { + peer := a.Peers[peerID] + if peer == nil { + return &NetworkMap{ + Network: a.Network.Copy(), + } + } + validatedPeers := integrations.ValidatePeers([]*Peer{peer}, a) + if len(validatedPeers) == 0 { + return &NetworkMap{ + Network: a.Network.Copy(), + } + } aclPeers, firewallRules := a.getPeerConnectionResources(peerID) + aclPeers = integrations.ValidatePeers(aclPeers, a) // exclude expired peers var peersToConnect []*Peer var expiredPeers []*Peer @@ -879,6 +893,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } + err = integrations.ValidateExtraSettings(newSettings.Extra, account, am) + if err != nil { + return nil, err + } + user, err := account.FindUser(userID) if err != nil { return nil, err @@ -905,6 +924,18 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, am.checkAndSchedulePeerLoginExpiration(account) } + // if oldSettings.PeerApprovalEnabled != newSettings.PeerApprovalEnabled { + // event := activity.AccountPeerApprovalEnabled + // if !newSettings.PeerApprovalEnabled { + // event = activity.AccountPeerApprovalDisabled + // } + // am.StoreEvent(userID, accountID, accountID, event, nil) + // + // for _, peer := range account.Peers { + // peer.Status.RequiresApproval = false + // } + // } + updatedAccount := account.UpdateSettings(newSettings) err = am.Store.SaveAccount(account) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 7bad9f627..bcd788b6f 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -120,6 +120,14 @@ const ( IntegrationUpdated // IntegrationDeleted indicates that the user deleted an integration IntegrationDeleted + // AccountPeerApprovalEnabled indicates that the user enabled peer approval for the account + AccountPeerApprovalEnabled + // AccountPeerApprovalDisabled indicates that the user disabled peer approval for the account + AccountPeerApprovalDisabled + // PeerApproved indicates that the peer has been approved + PeerApproved + // PeerApprovalRevoked indicates that the peer approval has been revoked + PeerApprovalRevoked ) var activityMap = map[Activity]Code{ @@ -178,6 +186,10 @@ var activityMap = map[Activity]Code{ IntegrationCreated: {"Integration created", "integration.create"}, IntegrationUpdated: {"Integration updated", "integration.update"}, IntegrationDeleted: {"Integration deleted", "integration.delete"}, + AccountPeerApprovalEnabled: {"Account peer approval enabled", "account.setting.peer.approval.enable"}, + AccountPeerApprovalDisabled: {"Account peer approval disabled", "account.setting.peer.approval.disable"}, + PeerApproved: {"Peer approved", "peer.approve"}, + PeerApprovalRevoked: {"Peer approval revoked", "peer.approval.revoke"}, } // StringCode returns a string code of the activity diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index a5d7a9501..8c356bb7a 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -77,6 +77,10 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)), } + if req.Settings.Extra != nil { + settings.Extra = &server.ExtraSettings{PeerApprovalEnabled: *req.Settings.Extra.PeerApprovalEnabled} + } + if req.Settings.JwtGroupsEnabled != nil { settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled } @@ -99,14 +103,20 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) } func toAccountResponse(account *server.Account) *api.Account { + settings := api.AccountSettings{ + PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), + PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled, + GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, + JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, + JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, + } + + if account.Settings.Extra != nil { + settings.Extra = &api.AccountExtraSettings{PeerApprovalEnabled: &account.Settings.Extra.PeerApprovalEnabled} + } + return &api.Account{ - Id: account.Id, - Settings: api.AccountSettings{ - PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), - PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled, - GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, - JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, - JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, - }, + Id: account.Id, + Settings: settings, } } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 64e97426a..6e98f4c7e 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -66,9 +66,18 @@ components: description: Name of the claim from which we extract groups names to add it to account groups. type: string example: "roles" + extra: + $ref: '#/components/schemas/AccountExtraSettings' required: - peer_login_expiration_enabled - peer_login_expiration + AccountExtraSettings: + type: object + properties: + peer_approval_enabled: + description: Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + type: boolean + example: true AccountRequest: type: object properties: @@ -213,6 +222,10 @@ components: login_expiration_enabled: type: boolean example: false + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true required: - name - ssh_enabled @@ -281,6 +294,10 @@ components: type: string format: date-time example: 2023-05-05T09:00:35.477782Z + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true required: - ip - connected diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index ea70b7f3a..5298e887b 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -142,6 +142,12 @@ type Account struct { Settings AccountSettings `json:"settings"` } +// AccountExtraSettings defines model for AccountExtraSettings. +type AccountExtraSettings struct { + // PeerApprovalEnabled Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + PeerApprovalEnabled *bool `json:"peer_approval_enabled,omitempty"` +} + // AccountRequest defines model for AccountRequest. type AccountRequest struct { Settings AccountSettings `json:"settings"` @@ -149,6 +155,8 @@ type AccountRequest struct { // AccountSettings defines model for AccountSettings. type AccountSettings struct { + Extra *AccountExtraSettings `json:"extra,omitempty"` + // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` @@ -323,6 +331,9 @@ type Peer struct { // AccessiblePeers List of accessible peers AccessiblePeers []AccessiblePeer `json:"accessible_peers"` + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -374,6 +385,9 @@ type Peer struct { // PeerBase defines model for PeerBase. type PeerBase struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -428,6 +442,9 @@ type PeerBatch struct { // AccessiblePeersCount Number of accessible peers AccessiblePeersCount int `json:"accessible_peers_count"` + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -488,6 +505,8 @@ type PeerMinimum struct { // PeerRequest defines model for PeerRequest. type PeerRequest struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` LoginExpirationEnabled bool `json:"login_expiration_enabled"` Name string `json:"name"` SshEnabled bool `json:"ssh_enabled"` diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 3d0d735ec..c143a8076 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -81,6 +81,11 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, LoginExpirationEnabled: req.LoginExpirationEnabled} + + if req.ApprovalRequired != nil { + update.Status = &server.PeerStatus{RequiresApproval: *req.ApprovalRequired} + } + peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) if err != nil { util.WriteError(err, w) @@ -248,6 +253,7 @@ func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsD LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeers: accessiblePeer, + Approved: &peer.Status.Approved, } } @@ -270,6 +276,7 @@ func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dn LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeersCount: accessiblePeersCount, + Approved: peer.Status.Approved, } } diff --git a/management/server/peer.go b/management/server/peer.go index 27912f1f4..6268ae794 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/netbirdio/management-integrations/integrations" "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" @@ -46,6 +47,8 @@ type PeerStatus struct { Connected bool // LoginExpired LoginExpired bool + // RequiresApproval indicates whether peer requires approval or not + RequiresApproval bool } // PeerSync used as a data object between the gRPC API and AccountManager on Sync request. @@ -192,9 +195,10 @@ func (p *Peer) EventMeta(dnsDomain string) map[string]any { // Copy PeerStatus func (p *PeerStatus) Copy() *PeerStatus { return &PeerStatus{ - LastSeen: p.LastSeen, - Connected: p.Connected, - LoginExpired: p.LoginExpired, + LastSeen: p.LastSeen, + Connected: p.Connected, + LoginExpired: p.LoginExpired, + RequiresApproval: p.RequiresApproval, } } @@ -304,6 +308,11 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } + update, err = integrations.ValidatePeersUpdateRequest(update, peer, am) + if err != nil { + return nil, err + } + if peer.SSHEnabled != update.SSHEnabled { peer.SSHEnabled = update.SSHEnabled event := activity.PeerSSHEnabled @@ -562,6 +571,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* Ephemeral: ephemeral, } + if account.Settings.Extra.PeerApprovalEnabled { + newPeer.Status.RequiresApproval = true + } + // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { @@ -632,6 +645,11 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } + validatedPeers := integrations.ValidatePeers([]*Peer{peer}, account) + if len(validatedPeers) == 0 { + return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") + } + err = checkIfPeerOwnerIsBlocked(peer, account) if err != nil { return nil, nil, err From dc05102b8f8ee8c1f3c17c51211ccb27f2685f8c Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Tue, 28 Nov 2023 13:09:33 +0100 Subject: [PATCH 32/59] Fix panic on empty username for invites (#1334) Validate email and user are not empty --- management/server/http/users_handler.go | 7 ++++++- management/server/user.go | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/management/server/http/users_handler.go b/management/server/http/users_handler.go index e2bf77de6..e474ac19a 100644 --- a/management/server/http/users_handler.go +++ b/management/server/http/users_handler.go @@ -155,9 +155,14 @@ func (h *UsersHandler) CreateUser(w http.ResponseWriter, r *http.Request) { email = *req.Email } + name := "" + if req.Name != nil { + name = *req.Name + } + newUser, err := h.accountManager.CreateUser(account.Id, user.Id, &server.UserInfo{ Email: email, - Name: *req.Name, + Name: name, Role: req.Role, AutoGroups: req.AutoGroups, IsServiceUser: req.IsServiceUser, diff --git a/management/server/user.go b/management/server/user.go index b96bf743c..b759bba42 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -258,6 +258,14 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite 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) if err != nil { return nil, status.Errorf(status.NotFound, "account %s doesn't exist", accountID) From a729c83b0659827159246a5755da6f16c076d12c Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 13:45:26 +0100 Subject: [PATCH 33/59] extract peer into seperate package --- go.mod | 2 +- go.sum | 2 + management/server/account.go | 88 +++--- management/server/account_test.go | 276 +++++++++++++++---- management/server/dns.go | 3 +- management/server/dns_test.go | 9 +- management/server/ephemeral.go | 5 +- management/server/ephemeral_test.go | 6 +- management/server/file_store.go | 5 +- management/server/file_store_test.go | 23 +- management/server/grpcserver.go | 15 +- management/server/http/peers_handler_test.go | 3 +- management/server/nameserver_test.go | 9 +- management/server/network.go | 5 +- management/server/peer.go | 218 ++------------- management/server/peer/peer.go | 181 ++++++++++++ management/server/peer_test.go | 36 +-- management/server/policy.go | 15 +- management/server/policy_test.go | 6 +- management/server/route_test.go | 21 +- management/server/sqlite_store.go | 22 +- management/server/sqlite_store_test.go | 30 +- management/server/store.go | 3 +- management/server/user.go | 3 +- 24 files changed, 587 insertions(+), 399 deletions(-) create mode 100644 management/server/peer/peer.go diff --git a/go.mod b/go.mod index d4b76653e..44d7cd6ec 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 diff --git a/go.sum b/go.sum index f316fa058..4038f9b5f 100644 --- a/go.sum +++ b/go.sum @@ -507,6 +507,8 @@ github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411- github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 h1:PTQ2SSijkoN8Qkctq9oLzEdzCLLv7WoD2dqScmpb15o= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 h1:JVImKBfZC2tC88GcJS0Hi6sdFL+FkhCcmbpU8lRAWss= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= diff --git a/management/server/account.go b/management/server/account.go index a08416e2c..1563570fa 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -27,6 +27,7 @@ import ( "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" ) @@ -68,13 +69,13 @@ type AccountManager interface { MarkPATUsed(tokenID string) error GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) ListUsers(accountID string) ([]*User, error) - GetPeers(accountID, userID string) ([]*Peer, error) + GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) MarkPeerConnected(peerKey string, connected bool) error DeletePeer(accountID, peerID, userID string) error - UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error) + UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) GetNetworkMap(peerID string) (*NetworkMap, error) GetPeerNetwork(peerID string) (*Network, error) - AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) + AddPeer(setupKey, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, error) CreatePAT(accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error GetPAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) @@ -106,10 +107,10 @@ type AccountManager interface { GetEvents(accountID, userID string) ([]*activity.Event, error) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error - GetPeer(accountID, peerID, userID string) (*Peer, error) + GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error) UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error) - LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API - SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API + LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API + SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API GetAllConnectedPeers() (map[string]struct{}, error) GetExternalCacheManager() ExternalCacheManager } @@ -164,20 +165,31 @@ type Settings struct { Extra *ExtraSettings } -type ExtraSettings struct { - // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator - PeerApprovalEnabled bool -} - // Copy copies the Settings struct func (s *Settings) Copy() *Settings { - return &Settings{ + settings := &Settings{ PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled, PeerLoginExpiration: s.PeerLoginExpiration, JWTGroupsEnabled: s.JWTGroupsEnabled, JWTGroupsClaimName: s.JWTGroupsClaimName, GroupsPropagationEnabled: s.GroupsPropagationEnabled, } + if s.Extra != nil { + settings.Extra = s.Extra.Copy() + } + return settings +} + +type ExtraSettings struct { + // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator + PeerApprovalEnabled bool +} + +// Copy copies the ExtraSettings struct +func (e *ExtraSettings) Copy() *ExtraSettings { + return &ExtraSettings{ + PeerApprovalEnabled: e.PeerApprovalEnabled, + } } // Account represents a unique account of the system @@ -193,8 +205,8 @@ type Account struct { SetupKeys map[string]*SetupKey `gorm:"-"` SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"` Network *Network `gorm:"embedded;embeddedPrefix:network_"` - Peers map[string]*Peer `gorm:"-"` - PeersG []Peer `json:"-" gorm:"foreignKey:AccountID;references:id"` + Peers map[string]*nbpeer.Peer `gorm:"-"` + PeersG []nbpeer.Peer `json:"-" gorm:"foreignKey:AccountID;references:id"` Users map[string]*User `gorm:"-"` UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"` Groups map[string]*Group `gorm:"-"` @@ -229,7 +241,7 @@ type UserInfo struct { // getRoutesToSync returns the enabled routes for the peer ID and the routes // from the ACL peers that have distribution groups associated with the peer ID. // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID. -func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Route { +func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*route.Route { routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID) peerRoutesMembership := make(lookupMap) for _, r := range append(routes, peerDisabledRoutes...) { @@ -359,7 +371,7 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { Network: a.Network.Copy(), } } - validatedPeers := integrations.ValidatePeers([]*Peer{peer}, a) + validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}, a) if len(validatedPeers) == 0 { return &NetworkMap{ Network: a.Network.Copy(), @@ -368,8 +380,8 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { aclPeers, firewallRules := a.getPeerConnectionResources(peerID) aclPeers = integrations.ValidatePeers(aclPeers, a) // exclude expired peers - var peersToConnect []*Peer - var expiredPeers []*Peer + var peersToConnect []*nbpeer.Peer + var expiredPeers []*nbpeer.Peer for _, p := range aclPeers { expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration) if a.Settings.PeerLoginExpirationEnabled && expired { @@ -407,8 +419,8 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { } // GetExpiredPeers returns peers that have been expired -func (a *Account) GetExpiredPeers() []*Peer { - var peers []*Peer +func (a *Account) GetExpiredPeers() []*nbpeer.Peer { + var peers []*nbpeer.Peer for _, peer := range a.GetPeersWithExpiration() { expired, _ := peer.LoginExpired(a.Settings.PeerLoginExpiration) if expired { @@ -447,8 +459,8 @@ func (a *Account) GetNextPeerExpiration() (time.Duration, bool) { } // GetPeersWithExpiration returns a list of peers that have Peer.LoginExpirationEnabled set to true and that were added by a user -func (a *Account) GetPeersWithExpiration() []*Peer { - peers := make([]*Peer, 0) +func (a *Account) GetPeersWithExpiration() []*nbpeer.Peer { + peers := make([]*nbpeer.Peer, 0) for _, peer := range a.Peers { if peer.LoginExpirationEnabled && peer.AddedWithSSOLogin() { peers = append(peers, peer) @@ -458,8 +470,8 @@ func (a *Account) GetPeersWithExpiration() []*Peer { } // GetPeers returns a list of all Account peers -func (a *Account) GetPeers() []*Peer { - var peers []*Peer +func (a *Account) GetPeers() []*nbpeer.Peer { + var peers []*nbpeer.Peer for _, peer := range a.Peers { peers = append(peers, peer) } @@ -473,7 +485,7 @@ func (a *Account) UpdateSettings(update *Settings) *Account { } // UpdatePeer saves new or replaces existing peer -func (a *Account) UpdatePeer(update *Peer) { +func (a *Account) UpdatePeer(update *nbpeer.Peer) { a.Peers[update.ID] = update } @@ -502,7 +514,7 @@ func (a *Account) DeletePeer(peerID string) { // FindPeerByPubKey looks for a Peer by provided WireGuard public key in the Account or returns error if it wasn't found. // It will return an object copy of the peer. -func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) { +func (a *Account) FindPeerByPubKey(peerPubKey string) (*nbpeer.Peer, error) { for _, peer := range a.Peers { if peer.Key == peerPubKey { return peer.Copy(), nil @@ -513,8 +525,8 @@ func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) { } // FindUserPeers returns a list of peers that user owns (created) -func (a *Account) FindUserPeers(userID string) ([]*Peer, error) { - peers := make([]*Peer, 0) +func (a *Account) FindUserPeers(userID string) ([]*nbpeer.Peer, error) { + peers := make([]*nbpeer.Peer, 0) for _, peer := range a.Peers { if peer.UserID == userID { peers = append(peers, peer) @@ -606,7 +618,7 @@ func (a *Account) getPeerDNSLabels() lookupMap { } func (a *Account) Copy() *Account { - peers := map[string]*Peer{} + peers := map[string]*nbpeer.Peer{} for id, peer := range a.Peers { peers[id] = peer.Copy() } @@ -683,7 +695,7 @@ func (a *Account) GetGroupAll() (*Group, error) { } // GetPeer looks up a Peer by ID -func (a *Account) GetPeer(peerID string) *Peer { +func (a *Account) GetPeer(peerID string) *nbpeer.Peer { return a.Peers[peerID] } @@ -893,7 +905,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } - err = integrations.ValidateExtraSettings(newSettings.Extra, account, am) + err = integrations.ValidateExtraSettings(newSettings.Extra, account, userID, am) if err != nil { return nil, err } @@ -924,18 +936,6 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, am.checkAndSchedulePeerLoginExpiration(account) } - // if oldSettings.PeerApprovalEnabled != newSettings.PeerApprovalEnabled { - // event := activity.AccountPeerApprovalEnabled - // if !newSettings.PeerApprovalEnabled { - // event = activity.AccountPeerApprovalDisabled - // } - // am.StoreEvent(userID, accountID, accountID, event, nil) - // - // for _, peer := range account.Peers { - // peer.Status.RequiresApproval = false - // } - // } - updatedAccount := account.UpdateSettings(newSettings) err = am.Store.SaveAccount(account) @@ -1683,7 +1683,7 @@ func newAccountWithId(accountID, userID, domain string) *Account { log.Debugf("creating new account") network := NewNetwork() - peers := make(map[string]*Peer) + peers := make(map[string]*nbpeer.Peer) users := make(map[string]*User) routes := make(map[string]*route.Route) setupKeys := map[string]*SetupKey{} diff --git a/management/server/account_test.go b/management/server/account_test.go index ad0ccfbce..9be9d8257 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -15,6 +15,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" "github.com/stretchr/testify/assert" @@ -26,10 +27,10 @@ import ( func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { t.Helper() - peer := &Peer{ + peer := &nbpeer.Peer{ Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=", Name: "test-host@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -110,13 +111,14 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy func TestAccount_GetPeerNetworkMap(t *testing.T) { peerID1 := "peer-1" peerID2 := "peer-2" + // peerID3 := "peer-3" tt := []struct { name string accountSettings Settings peerID string expectedPeers []string expectedOfflinePeers []string - peers map[string]*Peer + peers map[string]*nbpeer.Peer }{ { name: "Should return ALL peers when global peer login expiration disabled", @@ -124,14 +126,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { peerID: peerID1, expectedPeers: []string{peerID2}, expectedOfflinePeers: []string{}, - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: peerID1, Key: "peer-1-key", IP: net.IP{100, 64, 0, 1}, Name: peerID1, DNSLabel: peerID1, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: true, @@ -145,7 +147,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { IP: net.IP{100, 64, 0, 1}, Name: peerID2, DNSLabel: peerID2, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: false, @@ -162,14 +164,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { peerID: peerID1, expectedPeers: []string{}, expectedOfflinePeers: []string{peerID2}, - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: peerID1, Key: "peer-1-key", IP: net.IP{100, 64, 0, 1}, Name: peerID1, DNSLabel: peerID1, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: true, @@ -184,7 +186,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { IP: net.IP{100, 64, 0, 1}, Name: peerID2, DNSLabel: peerID2, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: true, @@ -195,6 +197,159 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { }, }, }, + // { + // name: "Should return only peers that are approved when peer approval is enabled", + // accountSettings: Settings{PeerApprovalEnabled: true}, + // peerID: peerID1, + // expectedPeers: []string{peerID3}, + // expectedOfflinePeers: []string{}, + // peers: map[string]*Peer{ + // "peer-1": { + // ID: peerID1, + // Key: "peer-1-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID1, + // DNSLabel: peerID1, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-2": { + // ID: peerID2, + // Key: "peer-2-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID2, + // DNSLabel: peerID2, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: false, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-3": { + // ID: peerID3, + // Key: "peer-3-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID3, + // DNSLabel: peerID3, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // }, + // }, + // { + // name: "Should return all peers when peer approval is disabled", + // accountSettings: Settings{PeerApprovalEnabled: false}, + // peerID: peerID1, + // expectedPeers: []string{peerID2, peerID3}, + // expectedOfflinePeers: []string{}, + // peers: map[string]*Peer{ + // "peer-1": { + // ID: peerID1, + // Key: "peer-1-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID1, + // DNSLabel: peerID1, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-2": { + // ID: peerID2, + // Key: "peer-2-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID2, + // DNSLabel: peerID2, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: false, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-3": { + // ID: peerID3, + // Key: "peer-3-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID3, + // DNSLabel: peerID3, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // }, + // }, + // { + // name: "Should return no peers when peer approval is enabled and the requesting peer is not approved", + // accountSettings: Settings{PeerApprovalEnabled: true}, + // peerID: peerID1, + // expectedPeers: []string{}, + // expectedOfflinePeers: []string{}, + // peers: map[string]*Peer{ + // "peer-1": { + // ID: peerID1, + // Key: "peer-1-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID1, + // DNSLabel: peerID1, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: false, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-2": { + // ID: peerID2, + // Key: "peer-2-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID2, + // DNSLabel: peerID2, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-3": { + // ID: peerID3, + // Key: "peer-3-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID3, + // DNSLabel: peerID3, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // }, + // }, } netIP := net.IP{100, 64, 0, 0} @@ -209,6 +364,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { for _, testCase := range tt { account := newAccountWithId("account-1", userID, "netbird.io") + account.UpdateSettings(&testCase.accountSettings) account.Network = network account.Peers = testCase.peers for _, peer := range account.Peers { @@ -780,9 +936,9 @@ func TestAccountManager_AddPeer(t *testing.T) { expectedPeerKey := key.PublicKey().String() expectedSetupKey := setupKey.Key - peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{Hostname: expectedPeerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -848,9 +1004,9 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) { expectedPeerKey := key.PublicKey().String() expectedUserID := userID - peer, _, err := manager.AddPeer("", userID, &Peer{ + peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{Hostname: expectedPeerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v, account users: %v", err, account.CreatedBy) @@ -915,7 +1071,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) { return } - getPeer := func() *Peer { + getPeer := func() *nbpeer.Peer { key, err := wgtypes.GeneratePrivateKey() if err != nil { t.Fatal(err) @@ -923,9 +1079,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) { } expectedPeerKey := key.PublicKey().String() - peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{Hostname: expectedPeerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Fatalf("expecting peer1 to be added, got failure %v", err) @@ -1097,9 +1253,9 @@ func TestAccountManager_DeletePeer(t *testing.T) { peerKey := key.PublicKey().String() - peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey, - Meta: PeerSystemMeta{Hostname: peerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: peerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -1238,8 +1394,8 @@ func TestAccount_GetRoutesToSync(t *testing.T) { t.Fatal(err) } account := &Account{ - Peers: map[string]*Peer{ - "peer-1": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, + Peers: map[string]*nbpeer.Peer{ + "peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, }, Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}}, Routes: map[string]*route.Route{ @@ -1282,7 +1438,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) { }, } - routes := account.getRoutesToSync("peer-2", []*Peer{{Key: "peer-1"}, {Key: "peer-3"}}) + routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}}) assert.Len(t, routes, 2) routeIDs := make(map[string]struct{}, 2) @@ -1292,7 +1448,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) { assert.Contains(t, routeIDs, "route-2") assert.Contains(t, routeIDs, "route-3") - emptyRoutes := account.getRoutesToSync("peer-3", []*Peer{{Key: "peer-1"}, {Key: "peer-2"}}) + emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}}) assert.Len(t, emptyRoutes, 0) } @@ -1313,10 +1469,10 @@ func TestAccount_Copy(t *testing.T) { Network: &Network{ Identifier: "net1", }, - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": { Key: "key1", - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now(), Connected: true, LoginExpired: false, @@ -1443,9 +1599,9 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) { key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - peer, _, err := manager.AddPeer("", userID, &Peer{ + peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1492,9 +1648,9 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing. key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - _, _, err = manager.AddPeer("", userID, &Peer{ + _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1533,9 +1689,9 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - _, _, err = manager.AddPeer("", userID, &Peer{ + _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1614,13 +1770,13 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) { func TestAccount_GetExpiredPeers(t *testing.T) { type test struct { name string - peers map[string]*Peer + peers map[string]*nbpeer.Peer expectedPeers map[string]struct{} } testCases := []test{ { name: "Peers with login expiration disabled, no expired peers", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { LoginExpirationEnabled: false, }, @@ -1632,11 +1788,11 @@ func TestAccount_GetExpiredPeers(t *testing.T) { }, { name: "Two peers expired", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: "peer-1", LoginExpirationEnabled: true, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: true, LoginExpired: false, @@ -1647,7 +1803,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) { "peer-2": { ID: "peer-2", LoginExpirationEnabled: true, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: true, LoginExpired: false, @@ -1659,7 +1815,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) { "peer-3": { ID: "peer-3", LoginExpirationEnabled: true, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: true, LoginExpired: false, @@ -1699,19 +1855,19 @@ func TestAccount_GetExpiredPeers(t *testing.T) { func TestAccount_GetPeersWithExpiration(t *testing.T) { type test struct { name string - peers map[string]*Peer + peers map[string]*nbpeer.Peer expectedPeers map[string]struct{} } testCases := []test{ { name: "No account peers, no peers with expiration", - peers: map[string]*Peer{}, + peers: map[string]*nbpeer.Peer{}, expectedPeers: map[string]struct{}{}, }, { name: "Peers with login expiration disabled, no peers with expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { LoginExpirationEnabled: false, UserID: userID, @@ -1725,7 +1881,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) { }, { name: "Peers with login expiration enabled, return peers with expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: "peer-1", LoginExpirationEnabled: true, @@ -1768,7 +1924,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) { func TestAccount_GetNextPeerExpiration(t *testing.T) { type test struct { name string - peers map[string]*Peer + peers map[string]*nbpeer.Peer expiration time.Duration expirationEnabled bool expectedNextRun bool @@ -1779,7 +1935,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { testCases := []test{ { name: "No peers, no expiration", - peers: map[string]*Peer{}, + peers: map[string]*nbpeer.Peer{}, expiration: time.Second, expirationEnabled: false, expectedNextRun: false, @@ -1787,16 +1943,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "No connected peers, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: false, }, LoginExpirationEnabled: true, UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, }, LoginExpirationEnabled: false, @@ -1810,16 +1966,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "Connected peers with disabled expiration, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, }, LoginExpirationEnabled: false, UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, }, LoginExpirationEnabled: false, @@ -1833,9 +1989,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "Expired peers, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1843,7 +1999,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1858,9 +2014,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "To be expired peer, return expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1869,7 +2025,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1884,9 +2040,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "Peers added with setup keys, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1894,7 +2050,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { SetupKey: "key", }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1929,7 +2085,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { func TestAccount_SetJWTGroups(t *testing.T) { // create a new account account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, @@ -1977,7 +2133,7 @@ func TestAccount_SetJWTGroups(t *testing.T) { func TestAccount_UserGroupsAddToPeers(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, @@ -2013,7 +2169,7 @@ func TestAccount_UserGroupsAddToPeers(t *testing.T) { func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, diff --git a/management/server/dns.go b/management/server/dns.go index 4f7f95aaf..236ba1f57 100644 --- a/management/server/dns.go +++ b/management/server/dns.go @@ -10,6 +10,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -200,7 +201,7 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup { } // peerIsNameserver returns true if the peer is a nameserver for a nsGroup -func peerIsNameserver(peer *Peer, nsGroup *nbdns.NameServerGroup) bool { +func peerIsNameserver(peer *nbpeer.Peer, nsGroup *nbdns.NameServerGroup) bool { for _, ns := range nsGroup.NameServers { if peer.IP.Equal(ns.IP.AsSlice()) { return true diff --git a/management/server/dns_test.go b/management/server/dns_test.go index 62408e4b3..bff0c9878 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -8,6 +8,7 @@ import ( "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -208,10 +209,10 @@ func createDNSStore(t *testing.T) (Store, error) { func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { t.Helper() - peer1 := &Peer{ + peer1 := &nbpeer.Peer{ Key: dnsPeer1Key, Name: "test-host1@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host1@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -223,10 +224,10 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro }, DNSLabel: dnsPeer1Key, } - peer2 := &Peer{ + peer2 := &nbpeer.Peer{ Key: dnsPeer2Key, Name: "test-host2@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host2@netbird.io", GoOS: "linux", Kernel: "Linux", diff --git a/management/server/ephemeral.go b/management/server/ephemeral.go index 0e76e58ac..9d70a05d1 100644 --- a/management/server/ephemeral.go +++ b/management/server/ephemeral.go @@ -7,6 +7,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) const ( @@ -72,7 +73,7 @@ func (e *EphemeralManager) Stop() { // OnPeerConnected remove the peer from the linked list of ephemeral peers. Because it has been called when the peer // is active the manager will not delete it while it is active. -func (e *EphemeralManager) OnPeerConnected(peer *Peer) { +func (e *EphemeralManager) OnPeerConnected(peer *nbpeer.Peer) { if !peer.Ephemeral { return } @@ -93,7 +94,7 @@ func (e *EphemeralManager) OnPeerConnected(peer *Peer) { // OnPeerDisconnected add the peer to the linked list of ephemeral peers. Because of the peer // is inactive it will be deleted after the ephemeralLifeTime period. -func (e *EphemeralManager) OnPeerDisconnected(peer *Peer) { +func (e *EphemeralManager) OnPeerDisconnected(peer *nbpeer.Peer) { if !peer.Ephemeral { return } diff --git a/management/server/ephemeral_test.go b/management/server/ephemeral_test.go index d271e5fca..3e36335e3 100644 --- a/management/server/ephemeral_test.go +++ b/management/server/ephemeral_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" "time" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) type MockStore struct { @@ -124,7 +126,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int) for i := 0; i < numberOfPeers; i++ { peerId := fmt.Sprintf("peer_%d", i) - p := &Peer{ + p := &nbpeer.Peer{ ID: peerId, Ephemeral: false, } @@ -133,7 +135,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int) for i := 0; i < numberOfEphemeralPeers; i++ { peerId := fmt.Sprintf("ephemeral_peer_%d", i) - p := &Peer{ + p := &nbpeer.Peer{ ID: peerId, Ephemeral: true, } diff --git a/management/server/file_store.go b/management/server/file_store.go index 73c52927e..ee32d5397 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -10,6 +10,7 @@ import ( "github.com/rs/xid" log "github.com/sirupsen/logrus" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/telemetry" @@ -204,7 +205,7 @@ func restore(file string) (*FileStore, error) { // Set the Peer.ID to the newly generated value. // Replace all the mentions of Peer.Key as ID (groups and routes). // Swap Peer.Key with Peer.ID in the Account.Peers map. - migrationPeers := make(map[string]*Peer) // key to Peer + migrationPeers := make(map[string]*nbpeer.Peer) // key to Peer for key, peer := range account.Peers { // set LastLogin for the peers that were onboarded before the peer login expiration feature if peer.LastLogin.IsZero() { @@ -571,7 +572,7 @@ func (s *FileStore) SaveInstallationID(ID string) error { // SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things. // PeerStatus will be saved eventually when some other changes occur. -func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStatus) error { +func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error { s.mux.Lock() defer s.mux.Unlock() diff --git a/management/server/file_store_test.go b/management/server/file_store_test.go index 206873d20..eb2e2042b 100644 --- a/management/server/file_store_test.go +++ b/management/server/file_store_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/util" ) @@ -35,7 +36,7 @@ func TestStalePeerIndices(t *testing.T) { peerID := "some_peer" peerKey := "some_peer_key" - account.Peers[peerID] = &Peer{ + account.Peers[peerID] = &nbpeer.Peer{ ID: peerID, Key: peerKey, } @@ -89,13 +90,13 @@ func TestSaveAccount(t *testing.T) { account := newAccountWithId("account_id", "testuser", "") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } // SaveAccount should trigger persist @@ -125,13 +126,13 @@ func TestStore(t *testing.T) { store := newStore(t) account := newAccountWithId("account_id", "testuser", "") - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } account.Groups["all"] = &Group{ ID: "all", @@ -546,19 +547,19 @@ func TestFileStore_SavePeerStatus(t *testing.T) { } // save status of non-existing peer - newStatus := PeerStatus{Connected: true, LastSeen: time.Now().UTC()} + newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()} err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus) assert.Error(t, err) // save new status of existing peer - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", ID: "testpeer", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, } err = store.SaveAccount(account) diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index f32f6347a..8d3d82661 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -17,6 +17,7 @@ import ( "github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" internalStatus "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/telemetry" ) @@ -196,7 +197,7 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi } } -func (s *GRPCServer) cancelPeerRoutines(peer *Peer) { +func (s *GRPCServer) cancelPeerRoutines(peer *nbpeer.Peer) { s.peersUpdateManager.CloseChannel(peer.ID) s.turnCredentialsManager.CancelRefresh(peer.ID) _ = s.accountManager.MarkPeerConnected(peer.Key, false) @@ -243,8 +244,8 @@ func mapError(err error) error { return status.Errorf(codes.Internal, "failed handling request") } -func extractPeerMeta(loginReq *proto.LoginRequest) PeerSystemMeta { - return PeerSystemMeta{ +func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta { + return nbpeer.PeerSystemMeta{ Hostname: loginReq.GetMeta().GetHostname(), GoOS: loginReq.GetMeta().GetGoOS(), Kernel: loginReq.GetMeta().GetKernel(), @@ -413,7 +414,7 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot } } -func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig { +func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig { netmask, _ := network.Net.Mask.Size() fqdn := peer.FQDN(dnsName) return &proto.PeerConfig{ @@ -423,7 +424,7 @@ func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfi } } -func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig { +func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig { remotePeers := []*proto.RemotePeerConfig{} for _, rPeer := range peers { fqdn := rPeer.FQDN(dnsName) @@ -437,7 +438,7 @@ func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig return remotePeers } -func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse { +func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse { wtConfig := toWiretrusteeConfig(config, turnCredentials) pConfig := toPeerConfig(peer, networkMap.Network, dnsName) @@ -477,7 +478,7 @@ func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Em } // sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization -func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error { +func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *nbpeer.Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error { // make secret time based TURN credentials optional var turnCredentials *TURNCredentials if s.config.TURNConfig.TimeBasedCredentials { diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 673cd90c5..1690bb75f 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -61,6 +61,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { Domain: "hotmail.com", Peers: map[string]*server.Peer{ peers[0].ID: peers[0], + peers[1].ID: peers[1], }, Users: map[string]*server.User{ "test_user": user, @@ -112,7 +113,7 @@ func TestGetPeers(t *testing.T) { Key: "key", SetupKey: "setupkey", IP: net.ParseIP("100.64.0.1"), - Status: &server.PeerStatus{Connected: true}, + Status: &server.PeerStatus{Connected: true, Approved: true}, Name: "PeerName", LoginExpirationEnabled: false, Meta: server.PeerSystemMeta{ diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index d1f4cd015..791dc5677 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -8,6 +8,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) const ( @@ -763,10 +764,10 @@ func createNSStore(t *testing.T) (Store, error) { func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { t.Helper() - peer1 := &Peer{ + peer1 := &nbpeer.Peer{ Key: nsGroupPeer1Key, Name: "test-host1@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host1@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -777,10 +778,10 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error UIVersion: "development", }, } - peer2 := &Peer{ + peer2 := &nbpeer.Peer{ Key: nsGroupPeer2Key, Name: "test-host2@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host2@netbird.io", GoOS: "linux", Kernel: "Linux", diff --git a/management/server/network.go b/management/server/network.go index c5b165cae..23ecb4c59 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -10,6 +10,7 @@ import ( "github.com/rs/xid" nbdns "github.com/netbirdio/netbird/dns" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" ) @@ -25,11 +26,11 @@ const ( ) type NetworkMap struct { - Peers []*Peer + Peers []*nbpeer.Peer Network *Network Routes []*route.Route DNSConfig nbdns.Config - OfflinePeers []*Peer + OfflinePeers []*nbpeer.Peer FirewallRules []*FirewallRule } diff --git a/management/server/peer.go b/management/server/peer.go index 6268ae794..97cada4f0 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -2,7 +2,6 @@ package server import ( "fmt" - "net" "strings" "time" @@ -10,6 +9,7 @@ import ( "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" log "github.com/sirupsen/logrus" @@ -17,40 +17,6 @@ import ( "github.com/netbirdio/netbird/management/proto" ) -// PeerSystemMeta is a metadata of a Peer machine system -type PeerSystemMeta struct { - Hostname string - GoOS string - Kernel string - Core string - Platform string - OS string - WtVersion string - UIVersion string -} - -func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { - return p.Hostname == other.Hostname && - p.GoOS == other.GoOS && - p.Kernel == other.Kernel && - p.Core == other.Core && - p.Platform == other.Platform && - p.OS == other.OS && - p.WtVersion == other.WtVersion && - p.UIVersion == other.UIVersion -} - -type PeerStatus struct { - // LastSeen is the last time peer was connected to the management service - LastSeen time.Time - // Connected indicates whether peer is connected to the management service or not - Connected bool - // LoginExpired - LoginExpired bool - // RequiresApproval indicates whether peer requires approval or not - RequiresApproval bool -} - // PeerSync used as a data object between the gRPC API and AccountManager on Sync request. type PeerSync struct { // WireGuardPubKey is a peers WireGuard public key @@ -64,147 +30,16 @@ type PeerLogin struct { // SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled) SSHKey string // Meta is the system information passed by peer, must be always present. - Meta PeerSystemMeta + Meta nbpeer.PeerSystemMeta // UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required. UserID string // SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required. SetupKey string } -// Peer represents a machine connected to the network. -// The Peer is a WireGuard peer identified by a public key -type Peer struct { - // ID is an internal ID of the peer - ID string `gorm:"primaryKey"` - // AccountID is a reference to Account that this object belongs - AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"` - // WireGuard public key - Key string `gorm:"index"` - // A setup key this peer was registered with - SetupKey string - // IP address of the Peer - IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"` - // Meta is a Peer system meta data - Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"` - // Name is peer's name (machine name) - Name string - // DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's - // domain to the peer label. e.g. peer-dns-label.netbird.cloud - DNSLabel string - // Status peer's management connection status - Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"` - // The user ID that registered the peer - UserID string - // SSHKey is a public SSH key of the peer - SSHKey string - // SSHEnabled indicates whether SSH server is enabled on the peer - SSHEnabled bool - // LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login. - // Works with LastLogin - LoginExpirationEnabled bool - // LastLogin the time when peer performed last login operation - LastLogin time.Time - // Indicate ephemeral peer attribute - Ephemeral bool -} - -// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user. -func (p *Peer) AddedWithSSOLogin() bool { - return p.UserID != "" -} - -// Copy copies Peer object -func (p *Peer) Copy() *Peer { - peerStatus := p.Status - if peerStatus != nil { - peerStatus = p.Status.Copy() - } - return &Peer{ - ID: p.ID, - AccountID: p.AccountID, - Key: p.Key, - SetupKey: p.SetupKey, - IP: p.IP, - Meta: p.Meta, - Name: p.Name, - DNSLabel: p.DNSLabel, - Status: peerStatus, - UserID: p.UserID, - SSHKey: p.SSHKey, - SSHEnabled: p.SSHEnabled, - LoginExpirationEnabled: p.LoginExpirationEnabled, - LastLogin: p.LastLogin, - Ephemeral: p.Ephemeral, - } -} - -// UpdateMetaIfNew updates peer's system metadata if new information is provided -// returns true if meta was updated, false otherwise -func (p *Peer) UpdateMetaIfNew(meta PeerSystemMeta) bool { - // Avoid overwriting UIVersion if the update was triggered sole by the CLI client - if meta.UIVersion == "" { - meta.UIVersion = p.Meta.UIVersion - } - - if p.Meta.isEqual(meta) { - return false - } - p.Meta = meta - return true -} - -// MarkLoginExpired marks peer's status expired or not -func (p *Peer) MarkLoginExpired(expired bool) { - newStatus := p.Status.Copy() - newStatus.LoginExpired = expired - if expired { - newStatus.Connected = false - } - p.Status = newStatus -} - -// LoginExpired indicates whether the peer's login has expired or not. -// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired. -// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired). -// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property. -// Login expiration can also be disabled/enabled globally on the Account level via Settings.PeerLoginExpirationEnabled. -// Only peers added by interactive SSO login can be expired. -func (p *Peer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) { - if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled { - return false, 0 - } - expiresAt := p.LastLogin.Add(expiresIn) - now := time.Now() - timeLeft := expiresAt.Sub(now) - return timeLeft <= 0, timeLeft -} - -// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain -func (p *Peer) FQDN(dnsDomain string) string { - if dnsDomain == "" { - return "" - } - return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain) -} - -// EventMeta returns activity event meta related to the peer -func (p *Peer) EventMeta(dnsDomain string) map[string]any { - return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP} -} - -// Copy PeerStatus -func (p *PeerStatus) Copy() *PeerStatus { - return &PeerStatus{ - LastSeen: p.LastSeen, - Connected: p.Connected, - LoginExpired: p.LoginExpired, - RequiresApproval: p.RequiresApproval, - } -} - // GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if // the current user is not an admin. -func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, error) { +func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) { account, err := am.Store.GetAccount(accountID) if err != nil { return nil, err @@ -215,8 +50,8 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er return nil, err } - peers := make([]*Peer, 0) - peersMap := make(map[string]*Peer) + peers := make([]*nbpeer.Peer, 0) + peersMap := make(map[string]*nbpeer.Peer) for _, peer := range account.Peers { if !user.IsAdmin() && user.Id != peer.UserID { // only display peers that belong to the current user if the current user is not an admin @@ -235,7 +70,7 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er } } - peers = make([]*Peer, 0, len(peersMap)) + peers = make([]*nbpeer.Peer, 0, len(peersMap)) for _, peer := range peersMap { peers = append(peers, peer) } @@ -294,7 +129,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected } // UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated. -func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Peer) (*Peer, error) { +func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -373,7 +208,7 @@ func (am *DefaultAccountManager) deletePeers(account *Account, peerIDs []string, // the first loop is needed to ensure all peers present under the account before modifying, otherwise // we might have some inconsistencies - peers := make([]*Peer, 0, len(peerIDs)) + peers := make([]*nbpeer.Peer, 0, len(peerIDs)) for _, peerID := range peerIDs { peer := account.GetPeer(peerID) @@ -465,7 +300,7 @@ func (am *DefaultAccountManager) GetPeerNetwork(peerID string) (*Network, error) // to it. We also add the User ID to the peer metadata to identify registrant. If no userID provided, then fail with status.PermissionDenied // Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused). // The peer property is just a placeholder for the Peer properties to pass further -func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) { +func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, error) { if setupKey == "" && userID == "" { // no auth method provided => reject access return nil, nil, status.Errorf(status.Unauthenticated, "no peer auth method provided, please use a setup key or interactive SSO login") @@ -554,7 +389,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* return nil, nil, err } - newPeer := &Peer{ + newPeer := &nbpeer.Peer{ ID: xid.New().String(), Key: peer.Key, SetupKey: upperKey, @@ -563,7 +398,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* Name: peer.Meta.Hostname, DNSLabel: newLabel, UserID: userID, - Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, SSHEnabled: false, SSHKey: peer.SSHKey, LastLogin: time.Now().UTC(), @@ -621,7 +456,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } // SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible -func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) { +func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) { account, err := am.Store.GetAccountByPeerPubKey(sync.WireGuardPubKey) if err != nil { if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { @@ -645,7 +480,7 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } - validatedPeers := integrations.ValidatePeers([]*Peer{peer}, account) + validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}, account) if len(validatedPeers) == 0 { return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") } @@ -663,14 +498,14 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er // LoginPeer logs in or registers a peer. // If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so. -func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) { +func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) { account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey) if err != nil { if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { // we couldn't find this peer by its public key which can mean that peer hasn't been registered yet. // Try registering it. - return am.AddPeer(login.SetupKey, login.UserID, &Peer{ + return am.AddPeer(login.SetupKey, login.UserID, &nbpeer.Peer{ Key: login.WireGuardPubKey, Meta: login.Meta, SSHKey: login.SSHKey, @@ -740,7 +575,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil } -func checkIfPeerOwnerIsBlocked(peer *Peer, account *Account) error { +func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error { if peer.AddedWithSSOLogin() { user, err := account.FindUser(peer.UserID) if err != nil { @@ -753,7 +588,7 @@ func checkIfPeerOwnerIsBlocked(peer *Peer, account *Account) error { return nil } -func checkAuth(loginUserID string, peer *Peer) error { +func checkAuth(loginUserID string, peer *nbpeer.Peer) error { if loginUserID == "" { // absence of a user ID indicates that JWT wasn't provided. return status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more") @@ -765,7 +600,7 @@ func checkAuth(loginUserID string, peer *Peer) error { return nil } -func peerLoginExpired(peer *Peer, account *Account) bool { +func peerLoginExpired(peer *nbpeer.Peer, account *Account) bool { expired, expiresIn := peer.LoginExpired(account.Settings.PeerLoginExpiration) expired = account.Settings.PeerLoginExpirationEnabled && expired if expired || peer.Status.LoginExpired { @@ -775,21 +610,12 @@ func peerLoginExpired(peer *Peer, account *Account) bool { return false } -func updatePeerLastLogin(peer *Peer, account *Account) { +func updatePeerLastLogin(peer *nbpeer.Peer, account *Account) { peer.UpdateLastLogin() account.UpdatePeer(peer) } -// UpdateLastLogin and set login expired false -func (p *Peer) UpdateLastLogin() *Peer { - p.LastLogin = time.Now().UTC() - newStatus := p.Status.Copy() - newStatus.LoginExpired = false - p.Status = newStatus - return p -} - -func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *Peer, account *Account, newSSHKey string) (*Peer, error) { +func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *nbpeer.Peer, account *Account, newSSHKey string) (*nbpeer.Peer, error) { if len(newSSHKey) == 0 { log.Debugf("no new SSH key provided for peer %s, skipping update", peer.ID) return peer, nil @@ -860,7 +686,7 @@ func (am *DefaultAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) } // GetPeer for a given accountID, peerID and userID error if not found. -func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Peer, error) { +func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -903,7 +729,7 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Pee return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peerID, accountID) } -func updatePeerMeta(peer *Peer, meta PeerSystemMeta, account *Account) (*Peer, bool) { +func updatePeerMeta(peer *nbpeer.Peer, meta nbpeer.PeerSystemMeta, account *Account) (*nbpeer.Peer, bool) { if peer.UpdateMetaIfNew(meta) { account.UpdatePeer(peer) return peer, true diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go new file mode 100644 index 000000000..a4e4cc3aa --- /dev/null +++ b/management/server/peer/peer.go @@ -0,0 +1,181 @@ +package peer + +import ( + "fmt" + "net" + "time" +) + +// Peer represents a machine connected to the network. +// The Peer is a WireGuard peer identified by a public key +type Peer struct { + // ID is an internal ID of the peer + ID string `gorm:"primaryKey"` + // AccountID is a reference to Account that this object belongs + AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"` + // WireGuard public key + Key string `gorm:"index"` + // A setup key this peer was registered with + SetupKey string + // IP address of the Peer + IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"` + // Meta is a Peer system meta data + Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"` + // Name is peer's name (machine name) + Name string + // DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's + // domain to the peer label. e.g. peer-dns-label.netbird.cloud + DNSLabel string + // Status peer's management connection status + Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"` + // The user ID that registered the peer + UserID string + // SSHKey is a public SSH key of the peer + SSHKey string + // SSHEnabled indicates whether SSH server is enabled on the peer + SSHEnabled bool + // LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login. + // Works with LastLogin + LoginExpirationEnabled bool + // LastLogin the time when peer performed last login operation + LastLogin time.Time + // Indicate ephemeral peer attribute + Ephemeral bool +} + +type PeerStatus struct { + // LastSeen is the last time peer was connected to the management service + LastSeen time.Time + // Connected indicates whether peer is connected to the management service or not + Connected bool + // LoginExpired + LoginExpired bool + // RequiresApproval indicates whether peer requires approval or not + RequiresApproval bool +} + +// PeerSystemMeta is a metadata of a Peer machine system +type PeerSystemMeta struct { + Hostname string + GoOS string + Kernel string + Core string + Platform string + OS string + WtVersion string + UIVersion string +} + +func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { + return p.Hostname == other.Hostname && + p.GoOS == other.GoOS && + p.Kernel == other.Kernel && + p.Core == other.Core && + p.Platform == other.Platform && + p.OS == other.OS && + p.WtVersion == other.WtVersion && + p.UIVersion == other.UIVersion +} + +// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user. +func (p *Peer) AddedWithSSOLogin() bool { + return p.UserID != "" +} + +// Copy copies Peer object +func (p *Peer) Copy() *Peer { + peerStatus := p.Status + if peerStatus != nil { + peerStatus = p.Status.Copy() + } + return &Peer{ + ID: p.ID, + AccountID: p.AccountID, + Key: p.Key, + SetupKey: p.SetupKey, + IP: p.IP, + Meta: p.Meta, + Name: p.Name, + DNSLabel: p.DNSLabel, + Status: peerStatus, + UserID: p.UserID, + SSHKey: p.SSHKey, + SSHEnabled: p.SSHEnabled, + LoginExpirationEnabled: p.LoginExpirationEnabled, + LastLogin: p.LastLogin, + Ephemeral: p.Ephemeral, + } +} + +// UpdateMetaIfNew updates peer's system metadata if new information is provided +// returns true if meta was updated, false otherwise +func (p *Peer) UpdateMetaIfNew(meta PeerSystemMeta) bool { + // Avoid overwriting UIVersion if the update was triggered sole by the CLI client + if meta.UIVersion == "" { + meta.UIVersion = p.Meta.UIVersion + } + + if p.Meta.isEqual(meta) { + return false + } + p.Meta = meta + return true +} + +// MarkLoginExpired marks peer's status expired or not +func (p *Peer) MarkLoginExpired(expired bool) { + newStatus := p.Status.Copy() + newStatus.LoginExpired = expired + if expired { + newStatus.Connected = false + } + p.Status = newStatus +} + +// LoginExpired indicates whether the peer's login has expired or not. +// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired. +// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired). +// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property. +// Login expiration can also be disabled/enabled globally on the Account level via Settings.PeerLoginExpirationEnabled. +// Only peers added by interactive SSO login can be expired. +func (p *Peer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) { + if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled { + return false, 0 + } + expiresAt := p.LastLogin.Add(expiresIn) + now := time.Now() + timeLeft := expiresAt.Sub(now) + return timeLeft <= 0, timeLeft +} + +// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain +func (p *Peer) FQDN(dnsDomain string) string { + if dnsDomain == "" { + return "" + } + return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain) +} + +// EventMeta returns activity event meta related to the peer +func (p *Peer) EventMeta(dnsDomain string) map[string]any { + return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP} +} + +// Copy PeerStatus +func (p *PeerStatus) Copy() *PeerStatus { + return &PeerStatus{ + LastSeen: p.LastSeen, + Connected: p.Connected, + LoginExpired: p.LoginExpired, + RequiresApproval: p.RequiresApproval, + } +} + +// UpdateLastLogin and set login expired false +func (p *Peer) UpdateLastLogin() *Peer { + p.LastLogin = time.Now().UTC() + newStatus := p.Status.Copy() + newStatus.LoginExpired = false + p.Status = newStatus + return p +} diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 9d5a8bfb9..ee84ea47d 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -8,6 +8,8 @@ import ( "github.com/rs/xid" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) func TestPeer_LoginExpired(t *testing.T) { @@ -52,7 +54,7 @@ func TestPeer_LoginExpired(t *testing.T) { for _, c := range tt { t.Run(c.name, func(t *testing.T) { - peer := &Peer{ + peer := &nbpeer.Peer{ LoginExpirationEnabled: c.expirationEnabled, LastLogin: c.lastLogin, UserID: userID, @@ -90,9 +92,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { return } - peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-1"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -104,9 +106,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { t.Fatal(err) return } - _, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ + _, _, err = manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -163,9 +165,9 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) { return } - peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-1"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -177,9 +179,9 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) { t.Fatal(err) return } - peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -339,9 +341,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) { return } - peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-1"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -353,9 +355,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) { t.Fatal(err) return } - _, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ + _, _, err = manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -409,9 +411,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) { return } - peer1, _, err := manager.AddPeer("", someUser, &Peer{ + peer1, _, err := manager.AddPeer("", someUser, &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -425,9 +427,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) { } // the second peer added with a setup key - peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { t.Fatal(err) diff --git a/management/server/policy.go b/management/server/policy.go index 24188f93c..dbfbca3ab 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -9,6 +9,7 @@ import ( "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -205,7 +206,7 @@ type FirewallRule struct { // getPeerConnectionResources for a given peer // // This function returns the list of peers and firewall rules that are applicable to a given peer. -func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*FirewallRule) { +func (a *Account) getPeerConnectionResources(peerID string) ([]*nbpeer.Peer, []*FirewallRule) { generateResources, getAccumulatedResources := a.connResourcesGenerator() for _, policy := range a.Policies { if !policy.Enabled { @@ -247,11 +248,11 @@ func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*Firewal // The generator function is used to generate the list of peers and firewall rules that are applicable to a given peer. // It safe to call the generator function multiple times for same peer and different rules no duplicates will be // generated. The accumulator function returns the result of all the generator calls. -func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), func() ([]*Peer, []*FirewallRule)) { +func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*nbpeer.Peer, int), func() ([]*nbpeer.Peer, []*FirewallRule)) { rulesExists := make(map[string]struct{}) peersExists := make(map[string]struct{}) rules := make([]*FirewallRule, 0) - peers := make([]*Peer, 0) + peers := make([]*nbpeer.Peer, 0) all, err := a.GetGroupAll() if err != nil { @@ -259,7 +260,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun all = &Group{} } - return func(rule *PolicyRule, groupPeers []*Peer, direction int) { + return func(rule *PolicyRule, groupPeers []*nbpeer.Peer, direction int) { isAll := (len(all.Peers) - 1) == len(groupPeers) for _, peer := range groupPeers { if peer == nil { @@ -299,7 +300,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun rules = append(rules, &pr) } } - }, func() ([]*Peer, []*FirewallRule) { + }, func() ([]*nbpeer.Peer, []*FirewallRule) { return peers, rules } } @@ -478,9 +479,9 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule { // getAllPeersFromGroups for given peer ID and list of groups // // Returns list of peers and boolean indicating if peer is in any of the groups -func getAllPeersFromGroups(account *Account, groups []string, peerID string) ([]*Peer, bool) { +func getAllPeersFromGroups(account *Account, groups []string, peerID string) ([]*nbpeer.Peer, bool) { peerInGroups := false - filteredPeers := make([]*Peer, 0, len(groups)) + filteredPeers := make([]*nbpeer.Peer, 0, len(groups)) for _, g := range groups { group, ok := account.Groups[g] if !ok { diff --git a/management/server/policy_test.go b/management/server/policy_test.go index 971bd27d9..3ed08f4e6 100644 --- a/management/server/policy_test.go +++ b/management/server/policy_test.go @@ -7,11 +7,13 @@ import ( "github.com/stretchr/testify/assert" "golang.org/x/exp/slices" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) func TestAccount_getPeersByPolicy(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peerA": { ID: "peerA", IP: net.ParseIP("100.65.14.88"), @@ -255,7 +257,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) { func TestAccount_getPeersByPolicyDirect(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peerA": { ID: "peerA", IP: net.ParseIP("100.65.14.88"), diff --git a/management/server/route_test.go b/management/server/route_test.go index 5112a5780..94f169a9b 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" ) @@ -1045,13 +1046,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer1 := &Peer{ + peer1 := &nbpeer.Peer{ IP: peer1IP, ID: peer1ID, Key: peer1Key, Name: "test-host1@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host1@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -1070,13 +1071,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer2 := &Peer{ + peer2 := &nbpeer.Peer{ IP: peer2IP, ID: peer2ID, Key: peer2Key, Name: "test-host2@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host2@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -1095,13 +1096,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer3 := &Peer{ + peer3 := &nbpeer.Peer{ IP: peer3IP, ID: peer3ID, Key: peer3Key, Name: "test-host3@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host3@netbird.io", GoOS: "darwin", Kernel: "Darwin", @@ -1120,13 +1121,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer4 := &Peer{ + peer4 := &nbpeer.Peer{ IP: peer4IP, ID: peer4ID, Key: peer4Key, Name: "test-host4@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host4@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -1145,13 +1146,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer5 := &Peer{ + peer5 := &nbpeer.Peer{ IP: peer5IP, ID: peer5ID, Key: peer5Key, Name: "test-host4@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host4@netbird.io", GoOS: "linux", Kernel: "Linux", diff --git a/management/server/sqlite_store.go b/management/server/sqlite_store.go index bbb13f8c6..abc3f575a 100644 --- a/management/server/sqlite_store.go +++ b/management/server/sqlite_store.go @@ -7,15 +7,17 @@ import ( "sync" "time" - nbdns "github.com/netbirdio/netbird/dns" - "github.com/netbirdio/netbird/management/server/status" - "github.com/netbirdio/netbird/management/server/telemetry" - "github.com/netbirdio/netbird/route" log "github.com/sirupsen/logrus" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" + + nbdns "github.com/netbirdio/netbird/dns" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/status" + "github.com/netbirdio/netbird/management/server/telemetry" + "github.com/netbirdio/netbird/route" ) // SqliteStore represents an account storage backed by a Sqlite DB persisted to disk @@ -58,7 +60,7 @@ func NewSqliteStore(dataDir string, metrics telemetry.AppMetrics) (*SqliteStore, sql.SetMaxOpenConns(conns) // TODO: make it configurable err = db.AutoMigrate( - &SetupKey{}, &Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{}, + &SetupKey{}, &nbpeer.Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{}, &Account{}, &Policy{}, &PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{}, &installation{}, ) @@ -219,8 +221,8 @@ func (s *SqliteStore) GetInstallationID() string { return installation.InstallationIDValue } -func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStatus) error { - var peer Peer +func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error { + var peer nbpeer.Peer result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerID) if result.Error != nil { @@ -347,7 +349,7 @@ func (s *SqliteStore) GetAccount(accountID string) (*Account, error) { } account.SetupKeysG = nil - account.Peers = make(map[string]*Peer, len(account.PeersG)) + account.Peers = make(map[string]*nbpeer.Peer, len(account.PeersG)) for _, peer := range account.PeersG { account.Peers[peer.ID] = peer.Copy() } @@ -405,7 +407,7 @@ func (s *SqliteStore) GetAccountByUser(userID string) (*Account, error) { } func (s *SqliteStore) GetAccountByPeerID(peerID string) (*Account, error) { - var peer Peer + var peer nbpeer.Peer result := s.db.Select("account_id").First(&peer, "id = ?", peerID) if result.Error != nil { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") @@ -419,7 +421,7 @@ func (s *SqliteStore) GetAccountByPeerID(peerID string) (*Account, error) { } func (s *SqliteStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) { - var peer Peer + var peer nbpeer.Peer result := s.db.Select("account_id").First(&peer, "key = ?", peerKey) if result.Error != nil { diff --git a/management/server/sqlite_store_test.go b/management/server/sqlite_store_test.go index 4a16e2525..88d330018 100644 --- a/management/server/sqlite_store_test.go +++ b/management/server/sqlite_store_test.go @@ -9,9 +9,11 @@ import ( "time" "github.com/google/uuid" - "github.com/netbirdio/netbird/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/util" ) func TestSqlite_NewStore(t *testing.T) { @@ -36,13 +38,13 @@ func TestSqlite_SaveAccount(t *testing.T) { account := newAccountWithId("account_id", "testuser", "") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } err := store.SaveAccount(account) @@ -51,13 +53,13 @@ func TestSqlite_SaveAccount(t *testing.T) { account2 := newAccountWithId("account_id2", "testuser2", "") setupKey = GenerateDefaultSetupKey() account2.SetupKeys[setupKey.Key] = setupKey - account2.Peers["testpeer2"] = &Peer{ + account2.Peers["testpeer2"] = &nbpeer.Peer{ Key: "peerkey2", SetupKey: "peerkeysetupkey2", IP: net.IP{127, 0, 0, 2}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name 2", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } err = store.SaveAccount(account2) @@ -109,19 +111,19 @@ func TestSqlite_SavePeerStatus(t *testing.T) { require.NoError(t, err) // save status of non-existing peer - newStatus := PeerStatus{Connected: true, LastSeen: time.Now().UTC()} + newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()} err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus) assert.Error(t, err) // save new status of existing peer - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", ID: "testpeer", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, } err = store.SaveAccount(account) @@ -216,13 +218,13 @@ func newAccount(store Store, id int) error { account := newAccountWithId(str, str+"-testuser", "example.com") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["p"+str] = &Peer{ + account.Peers["p"+str] = &nbpeer.Peer{ Key: "peerkey" + str, SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } return store.SaveAccount(account) diff --git a/management/server/store.go b/management/server/store.go index 66b239f96..2733a55f6 100644 --- a/management/server/store.go +++ b/management/server/store.go @@ -8,6 +8,7 @@ import ( log "github.com/sirupsen/logrus" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/telemetry" ) @@ -30,7 +31,7 @@ type Store interface { AcquireAccountLock(accountID string) func() // AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock AcquireGlobalLock() func() - SavePeerStatus(accountID, peerID string, status PeerStatus) error + SavePeerStatus(accountID, peerID string, status nbpeer.PeerStatus) error SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error // Close should close the store persisting all unsaved data. Close() error diff --git a/management/server/user.go b/management/server/user.go index b96bf743c..821559f92 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -11,6 +11,7 @@ import ( "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -980,7 +981,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( } // expireAndUpdatePeers expires all peers of the given user and updates them in the account -func (am *DefaultAccountManager) expireAndUpdatePeers(account *Account, peers []*Peer) error { +func (am *DefaultAccountManager) expireAndUpdatePeers(account *Account, peers []*nbpeer.Peer) error { var peerIDs []string for _, peer := range peers { if peer.Status.LoginExpired { From c2eaf8a1c0984d2a8beed5b322e6b6be2bc9eaf9 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Tue, 28 Nov 2023 14:23:38 +0100 Subject: [PATCH 34/59] Add account deletion endpoint (#1331) Adding support to account owners to delete an account This will remove all users from local, and if --user-delete-from-idp is set it will remove from the remote IDP --- management/server/account.go | 52 +++++++++++++ management/server/account_test.go | 25 ++++++ management/server/file_store.go | 35 +++++++++ management/server/file_store_test.go | 54 +++++++++++++ management/server/http/accounts_handler.go | 24 ++++++ management/server/http/api/openapi.yml | 26 +++++++ management/server/http/handler.go | 2 + management/server/http/users_handler.go | 2 +- management/server/mock_server/account_mock.go | 9 +++ management/server/sqlite_store.go | 42 ++++++++-- management/server/sqlite_store_test.go | 77 ++++++++++++++++++- management/server/store.go | 1 + 12 files changed, 342 insertions(+), 7 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 09071d103..cbdb247db 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -64,6 +64,7 @@ type AccountManager interface { GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, *User, error) GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error) + DeleteAccount(accountID, userID string) error MarkPATUsed(tokenID string) error GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) ListUsers(accountID string) ([]*User, error) @@ -1004,6 +1005,57 @@ func (am *DefaultAccountManager) warmupIDPCache() error { 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 // userID doesn't have an account associated with it, one account is created func (am *DefaultAccountManager) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) { diff --git a/management/server/account_test.go b/management/server/account_test.go index ad0ccfbce..3343acbde 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -746,6 +746,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) { manager, err := createManager(t) if err != nil { diff --git a/management/server/file_store.go b/management/server/file_store.go index 73c52927e..97fdc9a92 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -351,6 +351,41 @@ func (s *FileStore) SaveAccount(account *Account) error { 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 func (s *FileStore) DeleteHashedPAT2TokenIDIndex(hashedToken string) error { s.mux.Lock() diff --git a/management/server/file_store_test.go b/management/server/file_store_test.go index 206873d20..f5baf9858 100644 --- a/management/server/file_store_test.go +++ b/management/server/file_store_test.go @@ -121,6 +121,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) { store := newStore(t) diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index a5d7a9501..e5e0dff8d 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -98,6 +98,30 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) 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 { return &api.Account{ Id: account.Id, diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 64e97426a..0ed826148 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -1074,6 +1074,32 @@ paths: '500': "$ref": "#/components/responses/internal_error" /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: summary: Update an Account description: Update information about an account diff --git a/management/server/http/handler.go b/management/server/http/handler.go index c589512e5..8c77d27dc 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -7,6 +7,7 @@ import ( "github.com/rs/cors" "github.com/netbirdio/management-integrations/integrations" + s "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/http/middleware" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -105,6 +106,7 @@ func APIHandler(accountManager s.AccountManager, jwtValidator jwtclaims.JWTValid func (apiHandler *apiHandler) addAccountsEndpoint() { accountsHandler := NewAccountsHandler(apiHandler.AccountManager, apiHandler.AuthCfg) 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") } diff --git a/management/server/http/users_handler.go b/management/server/http/users_handler.go index e474ac19a..5d92b65e5 100644 --- a/management/server/http/users_handler.go +++ b/management/server/http/users_handler.go @@ -94,7 +94,7 @@ func (h *UsersHandler) UpdateUser(w http.ResponseWriter, r *http.Request) { 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) { if r.Method != http.MethodDelete { util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w) diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index f6b2e1641..84b23a4f2 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -68,6 +68,7 @@ type MockAccountManager struct { ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error) CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error) GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) + DeleteAccountFunc func(accountID, userID string) error GetDNSDomainFunc func() string StoreEventFunc func(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) 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") } +// 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 func (am *MockAccountManager) MarkPATUsed(pat string) error { if am.MarkPATUsedFunc != nil { diff --git a/management/server/sqlite_store.go b/management/server/sqlite_store.go index bbb13f8c6..0cd0abe4a 100644 --- a/management/server/sqlite_store.go +++ b/management/server/sqlite_store.go @@ -7,15 +7,16 @@ import ( "sync" "time" - nbdns "github.com/netbirdio/netbird/dns" - "github.com/netbirdio/netbird/management/server/status" - "github.com/netbirdio/netbird/management/server/telemetry" - "github.com/netbirdio/netbird/route" log "github.com/sirupsen/logrus" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" + + nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/status" + "github.com/netbirdio/netbird/management/server/telemetry" + "github.com/netbirdio/netbird/route" ) // SqliteStore represents an account storage backed by a Sqlite DB persisted to disk @@ -202,6 +203,37 @@ func (s *SqliteStore) SaveAccount(account *Account) error { 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 { installation := installation{InstallationIDValue: ID} installation.ID = uint(s.installationPK) @@ -336,7 +368,7 @@ func (s *SqliteStore) GetAccount(accountID string) (*Account, error) { var rules []*PolicyRule err := s.db.Model(&PolicyRule{}).Find(&rules, "policy_id = ?", policy.ID).Error 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 } diff --git a/management/server/sqlite_store_test.go b/management/server/sqlite_store_test.go index 4a16e2525..eef469f40 100644 --- a/management/server/sqlite_store_test.go +++ b/management/server/sqlite_store_test.go @@ -9,9 +9,10 @@ import ( "time" "github.com/google/uuid" - "github.com/netbirdio/netbird/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/util" ) func TestSqlite_NewStore(t *testing.T) { @@ -98,6 +99,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) { if runtime.GOOS == "windows" { t.Skip("The SQLite store is not properly supported by Windows yet") diff --git a/management/server/store.go b/management/server/store.go index 66b239f96..25511539a 100644 --- a/management/server/store.go +++ b/management/server/store.go @@ -14,6 +14,7 @@ import ( type Store interface { GetAllAccounts() []*Account GetAccount(accountID string) (*Account, error) + DeleteAccount(account *Account) error GetAccountByUser(userID string) (*Account, error) GetAccountByPeerPubKey(peerKey string) (*Account, error) GetAccountByPeerID(peerID string) (*Account, error) From bab420ca77981af5606550e447b6635fcc5d9db6 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 14:34:57 +0100 Subject: [PATCH 35/59] extract account into separate package --- management/server/account.go | 15 ++------------- management/server/account/account.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 management/server/account/account.go diff --git a/management/server/account.go b/management/server/account.go index 1563570fa..12198d726 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -24,6 +24,7 @@ import ( "github.com/netbirdio/netbird/base62" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/account" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -162,7 +163,7 @@ type Settings struct { JWTGroupsClaimName string // Extra is a dictionary of Account settings - Extra *ExtraSettings + Extra *account.ExtraSettings } // Copy copies the Settings struct @@ -180,18 +181,6 @@ func (s *Settings) Copy() *Settings { return settings } -type ExtraSettings struct { - // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator - PeerApprovalEnabled bool -} - -// Copy copies the ExtraSettings struct -func (e *ExtraSettings) Copy() *ExtraSettings { - return &ExtraSettings{ - PeerApprovalEnabled: e.PeerApprovalEnabled, - } -} - // Account represents a unique account of the system type Account struct { // we have to name column to aid as it collides with Network.Id when work with associations diff --git a/management/server/account/account.go b/management/server/account/account.go new file mode 100644 index 000000000..b8b71a6de --- /dev/null +++ b/management/server/account/account.go @@ -0,0 +1,13 @@ +package account + +type ExtraSettings struct { + // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator + PeerApprovalEnabled bool +} + +// Copy copies the ExtraSettings struct +func (e *ExtraSettings) Copy() *ExtraSettings { + return &ExtraSettings{ + PeerApprovalEnabled: e.PeerApprovalEnabled, + } +} From ad1f18a52a1eaece4b7651de2c081fbe3fb6893a Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 14:55:20 +0100 Subject: [PATCH 36/59] replace with updated integrations --- go.mod | 2 +- go.sum | 4 ++++ management/server/account.go | 6 +++--- management/server/peer.go | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 44d7cd6ec..cb8310dfb 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 diff --git a/go.sum b/go.sum index 4038f9b5f..5dcc3a776 100644 --- a/go.sum +++ b/go.sum @@ -509,6 +509,10 @@ github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220- github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 h1:JVImKBfZC2tC88GcJS0Hi6sdFL+FkhCcmbpU8lRAWss= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134203-547db8103a27 h1:cxyRuXtsr20HTz2YAcxpc+I1xVLLuIAxv8HpCs8Q5v4= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134203-547db8103a27/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06 h1:hEgJwiJTT/b0s3RNkXFpe2hTFu5rZJfHxd8UyuyRz/0= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= diff --git a/management/server/account.go b/management/server/account.go index 12198d726..e4af8e8f6 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -360,14 +360,14 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { Network: a.Network.Copy(), } } - validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}, a) + validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}) if len(validatedPeers) == 0 { return &NetworkMap{ Network: a.Network.Copy(), } } aclPeers, firewallRules := a.getPeerConnectionResources(peerID) - aclPeers = integrations.ValidatePeers(aclPeers, a) + aclPeers = integrations.ValidatePeers(aclPeers) // exclude expired peers var peersToConnect []*nbpeer.Peer var expiredPeers []*nbpeer.Peer @@ -894,7 +894,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } - err = integrations.ValidateExtraSettings(newSettings.Extra, account, userID, am) + err = integrations.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore) if err != nil { return nil, err } diff --git a/management/server/peer.go b/management/server/peer.go index 97cada4f0..b42615d12 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -143,7 +143,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } - update, err = integrations.ValidatePeersUpdateRequest(update, peer, am) + update, err = integrations.ValidatePeersUpdateRequest(update, peer, userID, am.eventStore, am.GetDNSDomain()) if err != nil { return nil, err } @@ -480,7 +480,7 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } - validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}, account) + validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}) if len(validatedPeers) == 0 { return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") } From c829ad930c609cb0562638993e28c8a650007ed1 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 15:09:04 +0100 Subject: [PATCH 37/59] use separate package for signatures --- go.mod | 3 ++- go.sum | 4 ++++ management/server/account.go | 8 ++++---- management/server/peer.go | 6 +++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index cb8310dfb..197bd8c2a 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 @@ -120,6 +120,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mdlayher/genetlink v1.1.0 // indirect github.com/mdlayher/netlink v1.7.1 // indirect + github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect diff --git a/go.sum b/go.sum index 5dcc3a776..8d9b2a923 100644 --- a/go.sum +++ b/go.sum @@ -495,6 +495,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= +github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97 h1:bZWuD9M+xfslW+Abn8LJJ/S3gYQEwh+vQ4Sr9M8gcU4= +github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944 h1:n7o2/NCZzn0+73LGdl/VMh7DOTdWZ98le2woeZ4HlB0= github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a h1:6aipBr80s0GPKO9Wl+f5TUOSwebQ91uX2thk9tElyqc= @@ -513,6 +515,8 @@ github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134203- github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134203-547db8103a27/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06 h1:hEgJwiJTT/b0s3RNkXFpe2hTFu5rZJfHxd8UyuyRz/0= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= diff --git a/management/server/account.go b/management/server/account.go index e4af8e8f6..1dd128abd 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -17,7 +17,7 @@ import ( "github.com/eko/gocache/v3/cache" cacheStore "github.com/eko/gocache/v3/store" - "github.com/netbirdio/management-integrations/integrations" + "github.com/netbirdio/management-integrations/additions" gocache "github.com/patrickmn/go-cache" "github.com/rs/xid" log "github.com/sirupsen/logrus" @@ -360,14 +360,14 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { Network: a.Network.Copy(), } } - validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}) + validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer}) if len(validatedPeers) == 0 { return &NetworkMap{ Network: a.Network.Copy(), } } aclPeers, firewallRules := a.getPeerConnectionResources(peerID) - aclPeers = integrations.ValidatePeers(aclPeers) + aclPeers = additions.ValidatePeers(aclPeers) // exclude expired peers var peersToConnect []*nbpeer.Peer var expiredPeers []*nbpeer.Peer @@ -894,7 +894,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } - err = integrations.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore) + err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore) if err != nil { return nil, err } diff --git a/management/server/peer.go b/management/server/peer.go index b42615d12..38a583a1e 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/netbirdio/management-integrations/integrations" + "github.com/netbirdio/management-integrations/additions" "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" @@ -143,7 +143,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } - update, err = integrations.ValidatePeersUpdateRequest(update, peer, userID, am.eventStore, am.GetDNSDomain()) + update, err = additions.ValidatePeersUpdateRequest(update, peer, userID, am.eventStore, am.GetDNSDomain()) if err != nil { return nil, err } @@ -480,7 +480,7 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } - validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}) + validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer}) if len(validatedPeers) == 0 { return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") } From efd05ca0236cb4fa1cadcacaf90aefa8e94df032 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 15:15:51 +0100 Subject: [PATCH 38/59] fix api references --- management/server/http/accounts_handler.go | 3 ++- management/server/http/groups_handler_test.go | 3 ++- management/server/http/peers_handler.go | 17 +++++++------- management/server/http/peers_handler_test.go | 23 ++++++++++--------- management/server/http/routes_handler_test.go | 7 +++--- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index 8c356bb7a..ef7e2cdd2 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/mux" "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/account" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -78,7 +79,7 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) } if req.Settings.Extra != nil { - settings.Extra = &server.ExtraSettings{PeerApprovalEnabled: *req.Settings.Extra.PeerApprovalEnabled} + settings.Extra = &account.ExtraSettings{PeerApprovalEnabled: *req.Settings.Extra.PeerApprovalEnabled} } if req.Settings.JwtGroupsEnabled != nil { diff --git a/management/server/http/groups_handler_test.go b/management/server/http/groups_handler_test.go index 5f40be7a9..5b47b1208 100644 --- a/management/server/http/groups_handler_test.go +++ b/management/server/http/groups_handler_test.go @@ -19,10 +19,11 @@ import ( "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/mock_server" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) -var TestPeers = map[string]*server.Peer{ +var TestPeers = map[string]*nbpeer.Peer{ "A": {Key: "A", ID: "peer-A-ID", IP: net.ParseIP("100.100.100.100")}, "B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")}, } diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index c143a8076..460458683 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -11,6 +11,7 @@ import ( "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -31,7 +32,7 @@ func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg) *Pee } } -func (h *PeersHandler) checkPeerStatus(peer *server.Peer) (*server.Peer, error) { +func (h *PeersHandler) checkPeerStatus(peer *nbpeer.Peer) (*nbpeer.Peer, error) { peerToReturn := peer.Copy() if peer.Status.Connected { statuses, err := h.accountManager.GetAllConnectedPeers() @@ -79,11 +80,11 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe return } - update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, + update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, LoginExpirationEnabled: req.LoginExpirationEnabled} if req.ApprovalRequired != nil { - update.Status = &server.PeerStatus{RequiresApproval: *req.ApprovalRequired} + update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired} } peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) @@ -234,7 +235,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin return groupsInfo } -func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer { +func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer { return &api.Peer{ Id: peer.ID, Name: peer.Name, @@ -253,11 +254,11 @@ func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsD LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeers: accessiblePeer, - Approved: &peer.Status.Approved, + ApprovalRequired: &peer.Status.RequiresApproval, } } -func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch { +func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch { return &api.PeerBatch{ Id: peer.ID, Name: peer.Name, @@ -276,11 +277,11 @@ func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dn LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeersCount: accessiblePeersCount, - Approved: peer.Status.Approved, + ApprovalRequired: &peer.Status.RequiresApproval, } } -func fqdn(peer *server.Peer, dnsDomain string) string { +func fqdn(peer *nbpeer.Peer, dnsDomain string) string { fqdn := peer.FQDN(dnsDomain) if fqdn == "" { return peer.DNSLabel diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 1690bb75f..d13db447b 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/mux" "github.com/netbirdio/netbird/management/server/http/api" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -25,11 +26,11 @@ import ( const testPeerID = "test_peer" const noUpdateChannelTestPeerID = "no-update-channel" -func initTestMetaData(peers ...*server.Peer) *PeersHandler { +func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { return &PeersHandler{ accountManager: &mock_server.MockAccountManager{ - UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) { - var p *server.Peer + UpdatePeerFunc: func(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) { + var p *nbpeer.Peer for _, peer := range peers { if update.ID == peer.ID { p = peer.Copy() @@ -41,8 +42,8 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { p.Name = update.Name return p, nil }, - GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) { - var p *server.Peer + GetPeerFunc: func(accountID, peerID, userID string) (*nbpeer.Peer, error) { + var p *nbpeer.Peer for _, peer := range peers { if peerID == peer.ID { p = peer.Copy() @@ -51,7 +52,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { } return p, nil }, - GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) { + GetPeersFunc: func(accountID, userID string) ([]*nbpeer.Peer, error) { return peers, nil }, GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) { @@ -59,7 +60,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { return &server.Account{ Id: claims.AccountId, Domain: "hotmail.com", - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ peers[0].ID: peers[0], peers[1].ID: peers[1], }, @@ -108,15 +109,15 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { // Use the metadata generated by initTestMetaData() to check for values func TestGetPeers(t *testing.T) { - peer := &server.Peer{ + peer := &nbpeer.Peer{ ID: testPeerID, Key: "key", SetupKey: "setupkey", IP: net.ParseIP("100.64.0.1"), - Status: &server.PeerStatus{Connected: true, Approved: true}, + Status: &nbpeer.PeerStatus{Connected: true}, Name: "PeerName", LoginExpirationEnabled: false, - Meta: server.PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "hostname", GoOS: "GoOS", Kernel: "kernel", @@ -145,7 +146,7 @@ func TestGetPeers(t *testing.T) { requestPath string requestBody io.Reader expectedArray bool - expectedPeer *server.Peer + expectedPeer *nbpeer.Peer }{ { name: "GetPeersMetaData", diff --git a/management/server/http/routes_handler_test.go b/management/server/http/routes_handler_test.go index 0bb4587e4..c02292f2a 100644 --- a/management/server/http/routes_handler_test.go +++ b/management/server/http/routes_handler_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/netbirdio/netbird/management/server/http/api" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" @@ -55,12 +56,12 @@ var baseExistingRoute = &route.Route{ var testingAccount = &server.Account{ Id: testAccountID, Domain: "hotmail.com", - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ existingPeerID: { Key: existingPeerKey, IP: netip.MustParseAddr(existingPeerIP1).AsSlice(), ID: existingPeerID, - Meta: server.PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ GoOS: "linux", }, }, @@ -68,7 +69,7 @@ var testingAccount = &server.Account{ Key: nonLinuxExistingPeerID, IP: netip.MustParseAddr(existingPeerIP2).AsSlice(), ID: nonLinuxExistingPeerID, - Meta: server.PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ GoOS: "darwin", }, }, From b6211ad0202ac998b7add702bd89147492791001 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Wed, 29 Nov 2023 09:33:05 +0100 Subject: [PATCH 39/59] Fix group membership for peers API response (#1337) --- management/server/http/peers_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 3d0d735ec..527e18f9d 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -215,7 +215,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin } groupsChecked[group.ID] = struct{}{} for _, pk := range group.Peers { - if pk != peerID { + if pk == peerID { info := api.GroupMinimum{ Id: group.ID, Name: group.Name, From 2129b23fe7630e3cc23b22416ccd322f67333f47 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 29 Nov 2023 14:56:06 +0100 Subject: [PATCH 40/59] allow sync for and return empty map --- management/server/peer.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/management/server/peer.go b/management/server/peer.go index 38a583a1e..439756eb5 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -480,11 +480,6 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } - validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer}) - if len(validatedPeers) == 0 { - return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") - } - err = checkIfPeerOwnerIsBlocked(peer, account) if err != nil { return nil, nil, err From 7a46a63a14f0d65e17d990a985d2171fbae718ce Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Wed, 29 Nov 2023 17:01:27 +0300 Subject: [PATCH 41/59] Fix the inability to set hostname with the flag in daemon mode (#1339) Pass the hostname set in the flag into the protocol message when running in daemon mode. --- client/cmd/login.go | 3 +- client/cmd/up.go | 3 +- client/proto/daemon.pb.go | 264 ++++++++++++++++++++------------------ client/proto/daemon.proto | 3 + client/server/server.go | 11 ++ 5 files changed, 160 insertions(+), 124 deletions(-) diff --git a/client/cmd/login.go b/client/cmd/login.go index b64355649..ac79199e2 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -85,6 +85,7 @@ var loginCmd = &cobra.Command{ PreSharedKey: preSharedKey, ManagementUrl: managementURL, IsLinuxDesktopClient: isLinuxRunningDesktop(), + Hostname: hostName, } var loginErr error @@ -114,7 +115,7 @@ var loginCmd = &cobra.Command{ if loginResp.NeedsSSOLogin { 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 { return fmt.Errorf("waiting sso login failed with: %v", err) } diff --git a/client/cmd/up.go b/client/cmd/up.go index 80ed04b57..dd4c7290e 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -149,6 +149,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error { CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0, CustomDNSAddress: customDNSAddressConverted, IsLinuxDesktopClient: isLinuxRunningDesktop(), + Hostname: hostName, } var loginErr error @@ -179,7 +180,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error { 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 { return fmt.Errorf("waiting sso login failed with: %v", err) } diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index 4dc989420..03eb3c49b 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -43,6 +43,7 @@ type LoginRequest struct { CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,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"` + Hostname string `protobuf:"bytes,9,opt,name=hostname,proto3" json:"hostname,omitempty"` } func (x *LoginRequest) Reset() { @@ -133,6 +134,13 @@ func (x *LoginRequest) GetIsLinuxDesktopClient() bool { return false } +func (x *LoginRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + type LoginResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -210,6 +218,7 @@ type WaitSSOLoginRequest struct { unknownFields protoimpl.UnknownFields 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() { @@ -251,6 +260,13 @@ func (x *WaitSSOLoginRequest) GetUserCode() string { return "" } +func (x *WaitSSOLoginRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + type WaitSSOLoginResponse struct { state protoimpl.MessageState 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, 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, - 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, 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, @@ -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, 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, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, - 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, - 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, - 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31, - 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, - 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, - 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, - 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, - 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, - 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, - 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, - 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, - 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, - 0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, - 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, - 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, - 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 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, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, - 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65, + 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, + 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x61, + 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, + 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, + 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, + 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, + 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, + 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, + 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, + 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, + 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65, + 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, + 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, + 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, + 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, - 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, - 0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, - 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, - 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, - 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, - 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, - 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, - 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, - 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, - 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, - 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, - 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, - 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, - 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 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, + 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 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, 0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c, + 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, + 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, + 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, + 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, + 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, + 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, + 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, + 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, + 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, + 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, + 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, + 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 ( diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index 8bed1ec9d..c2983c943 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -52,6 +52,8 @@ message LoginRequest { bytes customDNSAddress = 7; bool isLinuxDesktopClient = 8; + + string hostname = 9; } message LoginResponse { @@ -63,6 +65,7 @@ message LoginResponse { message WaitSSOLoginRequest { string userCode = 1; + string hostname = 2; } message WaitSSOLoginResponse {} diff --git a/client/server/server.go b/client/server/server.go index faac22273..b9c7b0a5e 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -7,6 +7,7 @@ import ( "time" "github.com/netbirdio/netbird/client/internal/auth" + "github.com/netbirdio/netbird/client/system" log "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" @@ -181,6 +182,11 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro s.latestConfigInput.CustomDNSAddress = []byte{} } + if msg.Hostname != "" { + // nolint + ctx = context.WithValue(ctx, system.DeviceNameCtxKey, msg.Hostname) + } + s.mutex.Unlock() inputConfig.PreSharedKey = &msg.PreSharedKey @@ -275,6 +281,11 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin ctx = metadata.NewOutgoingContext(ctx, md) } + if msg.Hostname != "" { + // nolint + ctx = context.WithValue(ctx, system.DeviceNameCtxKey, msg.Hostname) + } + s.actCancel = cancel s.mutex.Unlock() From ba96e102b4d045da798eb423767c943547ed79d3 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 29 Nov 2023 15:16:11 +0100 Subject: [PATCH 42/59] settings nil check --- management/server/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/server/peer.go b/management/server/peer.go index 439756eb5..00e97b044 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -406,7 +406,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P Ephemeral: ephemeral, } - if account.Settings.Extra.PeerApprovalEnabled { + if account.Settings.Extra != nil && account.Settings.Extra.PeerApprovalEnabled { newPeer.Status.RequiresApproval = true } From 8e74fb1fa82b68d62998040239121b3993525023 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 29 Nov 2023 15:57:56 +0100 Subject: [PATCH 43/59] add account id to validating peer update --- go.mod | 2 +- go.sum | 2 ++ management/server/peer.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 197bd8c2a..2be225702 100644 --- a/go.mod +++ b/go.mod @@ -120,7 +120,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mdlayher/genetlink v1.1.0 // indirect github.com/mdlayher/netlink v1.7.1 // indirect - github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97 // indirect + github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect diff --git a/go.sum b/go.sum index 8d9b2a923..bbe39de55 100644 --- a/go.sum +++ b/go.sum @@ -497,6 +497,8 @@ github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97 h1:bZWuD9M+xfslW+Abn8LJJ/S3gYQEwh+vQ4Sr9M8gcU4= github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 h1:fNGkwtLHz0U4bP3bPVSwbtfL4I0ZVNYNCpmGwl6TETE= +github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944 h1:n7o2/NCZzn0+73LGdl/VMh7DOTdWZ98le2woeZ4HlB0= github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a h1:6aipBr80s0GPKO9Wl+f5TUOSwebQ91uX2thk9tElyqc= diff --git a/management/server/peer.go b/management/server/peer.go index 00e97b044..8fafb7b71 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -143,7 +143,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } - update, err = additions.ValidatePeersUpdateRequest(update, peer, userID, am.eventStore, am.GetDNSDomain()) + update, err = additions.ValidatePeersUpdateRequest(update, peer, userID, accountID, am.eventStore, am.GetDNSDomain()) if err != nil { return nil, err } From 96f866fb6802c06192bd23b2fe4197f73ba31672 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Wed, 29 Nov 2023 16:46:35 +0100 Subject: [PATCH 44/59] add missing imports after refactor --- go.mod | 2 +- go.sum | 20 ------------ management/server/metrics/selfhosted_test.go | 9 +++--- management/server/mock_server/account_mock.go | 31 ++++++++++--------- 4 files changed, 22 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 2be225702..f3e939852 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 + github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -120,7 +121,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mdlayher/genetlink v1.1.0 // indirect github.com/mdlayher/netlink v1.7.1 // indirect - github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect diff --git a/go.sum b/go.sum index bbe39de55..9407ba7b2 100644 --- a/go.sum +++ b/go.sum @@ -495,28 +495,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= -github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97 h1:bZWuD9M+xfslW+Abn8LJJ/S3gYQEwh+vQ4Sr9M8gcU4= -github.com/netbirdio/management-integrations/additions v0.0.0-20231128140355-566178608e97/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 h1:fNGkwtLHz0U4bP3bPVSwbtfL4I0ZVNYNCpmGwl6TETE= github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944 h1:n7o2/NCZzn0+73LGdl/VMh7DOTdWZ98le2woeZ4HlB0= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231124150713-21460cd61944/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a h1:6aipBr80s0GPKO9Wl+f5TUOSwebQ91uX2thk9tElyqc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127164900-a09d11271e0a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127165733-0d0650c84683 h1:jJMO8KL2u3ok5VtGgZtFpuVK0GBEXXIb84idlmqGe68= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127165733-0d0650c84683/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127170523-a11fee39970a h1:DIe9xdl6RcxeZFu5Pr3OPC8SHM6yadF212W3LJlzfhQ= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127170523-a11fee39970a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a h1:iMEPP7MC3/7DTs/BNMshsBoviG3yWSTRbIzXKdrUwHw= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 h1:PTQ2SSijkoN8Qkctq9oLzEdzCLLv7WoD2dqScmpb15o= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 h1:JVImKBfZC2tC88GcJS0Hi6sdFL+FkhCcmbpU8lRAWss= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134203-547db8103a27 h1:cxyRuXtsr20HTz2YAcxpc+I1xVLLuIAxv8HpCs8Q5v4= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134203-547db8103a27/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06 h1:hEgJwiJTT/b0s3RNkXFpe2hTFu5rZJfHxd8UyuyRz/0= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128134945-092a9f4a2d06/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= diff --git a/management/server/metrics/selfhosted_test.go b/management/server/metrics/selfhosted_test.go index 7717ff409..a577f8309 100644 --- a/management/server/metrics/selfhosted_test.go +++ b/management/server/metrics/selfhosted_test.go @@ -5,6 +5,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" ) @@ -37,12 +38,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account { NameServerGroups: map[string]*nbdns.NameServerGroup{ "1": {}, }, - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ "1": { ID: "1", UserID: "test", SSHEnabled: true, - Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, + Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, }, }, Policies: []*server.Policy{ @@ -101,12 +102,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account { NameServerGroups: map[string]*nbdns.NameServerGroup{ "1": {}, }, - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ "1": { ID: "1", UserID: "test", SSHEnabled: true, - Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, + Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, }, }, Policies: []*server.Policy{ diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 84b23a4f2..b200af0c3 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -10,6 +10,7 @@ import ( "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" ) @@ -21,12 +22,12 @@ type MockAccountManager struct { GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error) GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error) ListUsersFunc func(accountID string) ([]*server.User, error) - GetPeersFunc func(accountID, userID string) ([]*server.Peer, error) + GetPeersFunc func(accountID, userID string) ([]*nbpeer.Peer, error) MarkPeerConnectedFunc func(peerKey string, connected bool) error DeletePeerFunc func(accountID, peerKey, userID string) error GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error) GetPeerNetworkFunc func(peerKey string) (*server.Network, error) - AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, *server.NetworkMap, error) + AddPeerFunc func(setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *server.NetworkMap, error) GetGroupFunc func(accountID, groupID string) (*server.Group, error) SaveGroupFunc func(accountID, userID string, group *server.Group) error DeleteGroupFunc func(accountID, userId, groupID string) error @@ -44,9 +45,9 @@ type MockAccountManager struct { GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error) GetAccountFromPATFunc func(pat string) (*server.Account, *server.User, *server.PersonalAccessToken, error) MarkPATUsedFunc func(pat string) error - UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error + UpdatePeerMetaFunc func(peerID string, meta nbpeer.PeerSystemMeta) error UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error - UpdatePeerFunc func(accountID, userID string, peer *server.Peer) (*server.Peer, error) + UpdatePeerFunc func(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error) SaveRouteFunc func(accountID, userID string, route *route.Route) error @@ -74,10 +75,10 @@ type MockAccountManager struct { GetEventsFunc func(accountID, userID string) ([]*activity.Event, error) GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error) SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *server.DNSSettings) error - GetPeerFunc func(accountID, peerID, userID string) (*server.Peer, error) + GetPeerFunc func(accountID, peerID, userID string) (*nbpeer.Peer, error) UpdateAccountSettingsFunc func(accountID, userID string, newSettings *server.Settings) (*server.Account, error) - LoginPeerFunc func(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) - SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) + LoginPeerFunc func(login server.PeerLogin) (*nbpeer.Peer, *server.NetworkMap, error) + SyncPeerFunc func(sync server.PeerSync) (*nbpeer.Peer, *server.NetworkMap, error) InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error GetAllConnectedPeersFunc func() (map[string]struct{}, error) GetExternalCacheManagerFunc func() server.ExternalCacheManager @@ -226,8 +227,8 @@ func (am *MockAccountManager) GetPeerNetwork(peerKey string) (*server.Network, e func (am *MockAccountManager) AddPeer( setupKey string, userId string, - peer *server.Peer, -) (*server.Peer, *server.NetworkMap, error) { + peer *nbpeer.Peer, +) (*nbpeer.Peer, *server.NetworkMap, error) { if am.AddPeerFunc != nil { return am.AddPeerFunc(setupKey, userId, peer) } @@ -347,7 +348,7 @@ func (am *MockAccountManager) ListPolicies(accountID, userID string) ([]*server. } // UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface -func (am *MockAccountManager) UpdatePeerMeta(peerID string, meta server.PeerSystemMeta) error { +func (am *MockAccountManager) UpdatePeerMeta(peerID string, meta nbpeer.PeerSystemMeta) error { if am.UpdatePeerMetaFunc != nil { return am.UpdatePeerMetaFunc(peerID, meta) } @@ -378,7 +379,7 @@ func (am *MockAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) err } // UpdatePeer mocks UpdatePeerFunc function of the account manager -func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server.Peer) (*server.Peer, error) { +func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) { if am.UpdatePeerFunc != nil { return am.UpdatePeerFunc(accountID, userID, peer) } @@ -542,7 +543,7 @@ func (am *MockAccountManager) GetAccountFromToken(claims jwtclaims.Authorization } // GetPeers mocks GetPeers of the AccountManager interface -func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*server.Peer, error) { +func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) { if am.GetAccountFromTokenFunc != nil { return am.GetPeersFunc(accountID, userID) } @@ -582,7 +583,7 @@ func (am *MockAccountManager) SaveDNSSettings(accountID string, userID string, d } // GetPeer mocks GetPeer of the AccountManager interface -func (am *MockAccountManager) GetPeer(accountID, peerID, userID string) (*server.Peer, error) { +func (am *MockAccountManager) GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error) { if am.GetPeerFunc != nil { return am.GetPeerFunc(accountID, peerID, userID) } @@ -598,7 +599,7 @@ func (am *MockAccountManager) UpdateAccountSettings(accountID, userID string, ne } // LoginPeer mocks LoginPeer of the AccountManager interface -func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) { +func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*nbpeer.Peer, *server.NetworkMap, error) { if am.LoginPeerFunc != nil { return am.LoginPeerFunc(login) } @@ -606,7 +607,7 @@ func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*server.Peer, * } // SyncPeer mocks SyncPeer of the AccountManager interface -func (am *MockAccountManager) SyncPeer(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) { +func (am *MockAccountManager) SyncPeer(sync server.PeerSync) (*nbpeer.Peer, *server.NetworkMap, error) { if am.SyncPeerFunc != nil { return am.SyncPeerFunc(sync) } From 47c44d4b875b178691cec7f3d168cea32e8dc493 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Thu, 30 Nov 2023 11:08:51 +0100 Subject: [PATCH 45/59] fix imports in sqlite store test --- management/server/sqlite_store_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/management/server/sqlite_store_test.go b/management/server/sqlite_store_test.go index cb2aa93b5..e493368fa 100644 --- a/management/server/sqlite_store_test.go +++ b/management/server/sqlite_store_test.go @@ -117,13 +117,13 @@ func TestSqlite_DeleteAccount(t *testing.T) { account := newAccountWithId("account_id", testUserID, "") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } account.Users[testUserID] = user From 4bf574037fe73d0fa9d1a925d081f6aba1fba8c4 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Thu, 30 Nov 2023 11:51:35 +0100 Subject: [PATCH 46/59] fix sql store --- management/server/account.go | 2 +- management/server/sqlite_store.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index b95191b1f..2ac00410c 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -164,7 +164,7 @@ type Settings struct { JWTGroupsClaimName string // Extra is a dictionary of Account settings - Extra *account.ExtraSettings + Extra *account.ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"` } // Copy copies the Settings struct diff --git a/management/server/sqlite_store.go b/management/server/sqlite_store.go index 532597527..1bc2db3f1 100644 --- a/management/server/sqlite_store.go +++ b/management/server/sqlite_store.go @@ -14,6 +14,7 @@ import ( "gorm.io/gorm/logger" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/account" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/telemetry" @@ -62,7 +63,7 @@ func NewSqliteStore(dataDir string, metrics telemetry.AppMetrics) (*SqliteStore, err = db.AutoMigrate( &SetupKey{}, &nbpeer.Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{}, &Account{}, &Policy{}, &PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{}, - &installation{}, + &installation{}, &account.ExtraSettings{}, ) if err != nil { return nil, err From b8c46e2654400c99a0c6f2da0be2ce478d4bc1c4 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 1 Dec 2023 14:26:42 +0100 Subject: [PATCH 47/59] Fix IPv6 format for DNS address received from android (#1350) this adds the address in the expected format in Go [ipv6]:port --- client/internal/dns/server.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 9bb9a76a9..f984f02ec 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -488,7 +488,18 @@ func (s *DefaultServer) addHostRootZone() { handler := newUpstreamResolver(s.ctx) handler.upstreamServers = make([]string, len(s.hostsDnsList)) for n, ua := range s.hostsDnsList { - handler.upstreamServers[n] = fmt.Sprintf("%s:53", ua) + a, err := netip.ParseAddr(ua) + if err != nil { + log.Errorf("invalid upstream IP address: %s, error: %s", ua, err) + continue + } + + ipString := ua + if !a.Is4() { + ipString = fmt.Sprintf("[%s]", ua) + } + + handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString) } handler.deactivate = func() {} handler.reactivate = func() {} From d7efea74b69a74d3c1bcfdeb157bee20e17c4634 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 1 Dec 2023 17:24:57 +0100 Subject: [PATCH 48/59] add owner role support (#1340) This PR adds support to Owner roles. The owner role has a similar access level as the admin, but it has the power to delete the account. Besides that, the role has the following constraints: - The role can only be transferred. So, only a user with the owner role can transfer the owner role to a new user - It can't be assigned to users being invited - It can't be assigned to service users --- management/server/account.go | 6 +- management/server/account_test.go | 14 +- management/server/activity/codes.go | 3 + management/server/dns.go | 8 +- management/server/group.go | 2 +- management/server/group_test.go | 2 +- management/server/http/accounts_handler.go | 2 +- .../server/http/middleware/access_control.go | 4 +- management/server/idp/mock.go | 77 +++++ management/server/peer.go | 4 +- management/server/policy.go | 8 +- management/server/route.go | 8 +- management/server/setupkey.go | 4 +- management/server/user.go | 92 ++++-- management/server/user_test.go | 274 ++++++++++++++---- 15 files changed, 397 insertions(+), 111 deletions(-) create mode 100644 management/server/idp/mock.go diff --git a/management/server/account.go b/management/server/account.go index cbdb247db..3de68412e 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -877,7 +877,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } - if !user.IsAdmin() { + if !user.HasAdminPower() { return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account") } @@ -1019,7 +1019,7 @@ func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error { return err } - if !user.IsAdmin() { + if !user.HasAdminPower() { return status.Errorf(status.PermissionDenied, "user is not allowed to delete account") } @@ -1703,7 +1703,7 @@ func newAccountWithId(accountID, userID, domain string) *Account { routes := make(map[string]*route.Route) setupKeys := map[string]*SetupKey{} nameServersGroups := make(map[string]*nbdns.NameServerGroup) - users[userID] = NewAdminUser(userID) + users[userID] = NewOwnerUser(userID) dnsSettings := DNSSettings{ DisabledManagementGroups: make([]string, 0), } diff --git a/management/server/account_test.go b/management/server/account_test.go index 3343acbde..cfd2d0ca9 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -306,7 +306,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) { inputInitUserParams: defaultInitAccount, testingFunc: require.NotEqual, expectedMSG: "account IDs shouldn't match", - expectedUserRole: UserRoleAdmin, + expectedUserRole: UserRoleOwner, expectedDomainCategory: "", expectedDomain: publicDomain, expectedPrimaryDomainStatus: false, @@ -328,7 +328,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) { inputInitUserParams: initUnknown, testingFunc: require.NotEqual, expectedMSG: "account IDs shouldn't match", - expectedUserRole: UserRoleAdmin, + expectedUserRole: UserRoleOwner, expectedDomain: unknownDomain, expectedDomainCategory: "", expectedPrimaryDomainStatus: false, @@ -346,7 +346,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) { inputInitUserParams: defaultInitAccount, testingFunc: require.NotEqual, expectedMSG: "account IDs shouldn't match", - expectedUserRole: UserRoleAdmin, + expectedUserRole: UserRoleOwner, expectedDomain: privateDomain, expectedDomainCategory: PrivateCategory, expectedPrimaryDomainStatus: true, @@ -387,7 +387,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) { inputInitUserParams: defaultInitAccount, testingFunc: require.Equal, expectedMSG: "account IDs should match", - expectedUserRole: UserRoleAdmin, + expectedUserRole: UserRoleOwner, expectedDomain: defaultInitAccount.Domain, expectedDomainCategory: PrivateCategory, expectedPrimaryDomainStatus: true, @@ -406,7 +406,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) { inputInitUserParams: defaultInitAccount, testingFunc: require.Equal, expectedMSG: "account IDs should match", - expectedUserRole: UserRoleAdmin, + expectedUserRole: UserRoleOwner, expectedDomain: defaultInitAccount.Domain, expectedDomainCategory: PrivateCategory, expectedPrimaryDomainStatus: true, @@ -424,7 +424,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) { inputInitUserParams: defaultInitAccount, testingFunc: require.NotEqual, expectedMSG: "account IDs shouldn't match", - expectedUserRole: UserRoleAdmin, + expectedUserRole: UserRoleOwner, expectedDomain: "", expectedDomainCategory: "", expectedPrimaryDomainStatus: false, @@ -1183,7 +1183,7 @@ func TestGetUsersFromAccount(t *testing.T) { t.Fatal(err) } - users := map[string]*User{"1": {Id: "1", Role: "admin"}, "2": {Id: "2", Role: "user"}, "3": {Id: "3", Role: "user"}} + users := map[string]*User{"1": {Id: "1", Role: UserRoleOwner}, "2": {Id: "2", Role: "user"}, "3": {Id: "3", Role: "user"}} accountId := "test_account_id" account, err := createAccount(manager, accountId, users["1"].Id, "") diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 7bad9f627..811481048 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -120,6 +120,8 @@ const ( IntegrationUpdated // IntegrationDeleted indicates that the user deleted an integration IntegrationDeleted + // TransferredOwnerRole indicates that the user transferred the owner role of the account + TransferredOwnerRole ) var activityMap = map[Activity]Code{ @@ -178,6 +180,7 @@ var activityMap = map[Activity]Code{ IntegrationCreated: {"Integration created", "integration.create"}, IntegrationUpdated: {"Integration updated", "integration.update"}, IntegrationDeleted: {"Integration deleted", "integration.delete"}, + TransferredOwnerRole: {"Transferred owner role", "transferred.owner.role"}, } // StringCode returns a string code of the activity diff --git a/management/server/dns.go b/management/server/dns.go index 4f7f95aaf..3a9aef192 100644 --- a/management/server/dns.go +++ b/management/server/dns.go @@ -47,8 +47,8 @@ func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string) return nil, err } - if !user.IsAdmin() { - return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view DNS settings") + if !user.HasAdminPower() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power are allowed to view DNS settings") } dnsSettings := account.DNSSettings.Copy() return &dnsSettings, nil @@ -69,8 +69,8 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string return err } - if !user.IsAdmin() { - return status.Errorf(status.PermissionDenied, "only admins are allowed to update DNS settings") + if !user.HasAdminPower() { + return status.Errorf(status.PermissionDenied, "only users with admin power are allowed to update DNS settings") } if dnsSettingsToSave == nil { diff --git a/management/server/group.go b/management/server/group.go index 07f61425e..cf7320c29 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -170,7 +170,7 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) return status.Errorf(status.NotFound, "user not found") } if executingUser.Role != UserRoleAdmin || !executingUser.IsServiceUser { - return status.Errorf(status.PermissionDenied, "only admins service user can delete integration group") + return status.Errorf(status.PermissionDenied, "only service users with admin power can delete integration group") } } diff --git a/management/server/group_test.go b/management/server/group_test.go index 97a1b75f5..e2051a656 100644 --- a/management/server/group_test.go +++ b/management/server/group_test.go @@ -57,7 +57,7 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) { { "integration", "grp-for-integration", - "only admins service user can delete integration group", + "only service users with admin power can delete integration group", }, } diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index e5e0dff8d..362c4b2a8 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -40,7 +40,7 @@ func (h *AccountsHandler) GetAllAccounts(w http.ResponseWriter, r *http.Request) return } - if !user.IsAdmin() { + if !user.HasAdminPower() { util.WriteError(status.Errorf(status.PermissionDenied, "the user has no permission to access account data"), w) return } diff --git a/management/server/http/middleware/access_control.go b/management/server/http/middleware/access_control.go index 31b5a2a9d..2f9374fc1 100644 --- a/management/server/http/middleware/access_control.go +++ b/management/server/http/middleware/access_control.go @@ -53,7 +53,7 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler { return } - if !user.IsAdmin() { + if !user.HasAdminPower() { switch r.Method { case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut: @@ -63,7 +63,7 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler { return } - util.WriteError(status.Errorf(status.PermissionDenied, "only admin can perform this operation"), w) + util.WriteError(status.Errorf(status.PermissionDenied, "only users with admin power can perform this operation"), w) return } } diff --git a/management/server/idp/mock.go b/management/server/idp/mock.go new file mode 100644 index 000000000..7605466e7 --- /dev/null +++ b/management/server/idp/mock.go @@ -0,0 +1,77 @@ +package idp + +// MockIDP is a mock implementation of the IDP interface +type MockIDP struct { + UpdateUserAppMetadataFunc func(userId string, appMetadata AppMetadata) error + GetUserDataByIDFunc func(userId string, appMetadata AppMetadata) (*UserData, error) + GetAccountFunc func(accountId string) ([]*UserData, error) + GetAllAccountsFunc func() (map[string][]*UserData, error) + CreateUserFunc func(email, name, accountID, invitedByEmail string) (*UserData, error) + GetUserByEmailFunc func(email string) ([]*UserData, error) + InviteUserByIDFunc func(userID string) error + DeleteUserFunc func(userID string) error +} + +// UpdateUserAppMetadata is a mock implementation of the IDP interface UpdateUserAppMetadata method +func (m *MockIDP) UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error { + if m.UpdateUserAppMetadataFunc != nil { + return m.UpdateUserAppMetadataFunc(userId, appMetadata) + } + return nil +} + +// GetUserDataByID is a mock implementation of the IDP interface GetUserDataByID method +func (m *MockIDP) GetUserDataByID(userId string, appMetadata AppMetadata) (*UserData, error) { + if m.GetUserDataByIDFunc != nil { + return m.GetUserDataByIDFunc(userId, appMetadata) + } + return nil, nil +} + +// GetAccount is a mock implementation of the IDP interface GetAccount method +func (m *MockIDP) GetAccount(accountId string) ([]*UserData, error) { + if m.GetAccountFunc != nil { + return m.GetAccountFunc(accountId) + } + return nil, nil +} + +// GetAllAccounts is a mock implementation of the IDP interface GetAllAccounts method +func (m *MockIDP) GetAllAccounts() (map[string][]*UserData, error) { + if m.GetAllAccountsFunc != nil { + return m.GetAllAccountsFunc() + } + return nil, nil +} + +// CreateUser is a mock implementation of the IDP interface CreateUser method +func (m *MockIDP) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) { + if m.CreateUserFunc != nil { + return m.CreateUserFunc(email, name, accountID, invitedByEmail) + } + return nil, nil +} + +// GetUserByEmail is a mock implementation of the IDP interface GetUserByEmail method +func (m *MockIDP) GetUserByEmail(email string) ([]*UserData, error) { + if m.GetUserByEmailFunc != nil { + return m.GetUserByEmailFunc(email) + } + return nil, nil +} + +// InviteUserByID is a mock implementation of the IDP interface InviteUserByID method +func (m *MockIDP) InviteUserByID(userID string) error { + if m.InviteUserByIDFunc != nil { + return m.InviteUserByIDFunc(userID) + } + return nil +} + +// DeleteUser is a mock implementation of the IDP interface DeleteUser method +func (m *MockIDP) DeleteUser(userID string) error { + if m.DeleteUserFunc != nil { + return m.DeleteUserFunc(userID) + } + return nil +} diff --git a/management/server/peer.go b/management/server/peer.go index 27912f1f4..67076921b 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -214,7 +214,7 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er peers := make([]*Peer, 0) peersMap := make(map[string]*Peer) for _, peer := range account.Peers { - if !user.IsAdmin() && user.Id != peer.UserID { + if !user.HasAdminPower() && user.Id != peer.UserID { // only display peers that belong to the current user if the current user is not an admin continue } @@ -862,7 +862,7 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Pee } // if admin or user owns this peer, return peer - if user.IsAdmin() || peer.UserID == userID { + if user.HasAdminPower() || peer.UserID == userID { return peer, nil } diff --git a/management/server/policy.go b/management/server/policy.go index fc222cb40..428d28d9c 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -319,8 +319,8 @@ func (am *DefaultAccountManager) GetPolicy(accountID, policyID, userID string) ( return nil, err } - if !user.IsAdmin() { - return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view policies") + if !user.HasAdminPower() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power are allowed to view policies") } for _, policy := range account.Policies { @@ -402,8 +402,8 @@ func (am *DefaultAccountManager) ListPolicies(accountID, userID string) ([]*Poli return nil, err } - if !user.IsAdmin() { - return nil, status.Errorf(status.PermissionDenied, "Only Administrators can view policies") + if !user.HasAdminPower() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view policies") } return account.Policies, nil diff --git a/management/server/route.go b/management/server/route.go index 14b5bef3f..5f7976fe4 100644 --- a/management/server/route.go +++ b/management/server/route.go @@ -27,8 +27,8 @@ func (am *DefaultAccountManager) GetRoute(accountID, routeID, userID string) (*r return nil, err } - if !user.IsAdmin() { - return nil, status.Errorf(status.PermissionDenied, "Only administrators can view Network Routes") + if !user.HasAdminPower() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view Network Routes") } wantedRoute, found := account.Routes[routeID] @@ -296,8 +296,8 @@ func (am *DefaultAccountManager) ListRoutes(accountID, userID string) ([]*route. return nil, err } - if !user.IsAdmin() { - return nil, status.Errorf(status.PermissionDenied, "Only administrators can view Network Routes") + if !user.HasAdminPower() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view Network Routes") } routes := make([]*route.Route, 0, len(account.Routes)) diff --git a/management/server/setupkey.go b/management/server/setupkey.go index d347fb181..b557b07c8 100644 --- a/management/server/setupkey.go +++ b/management/server/setupkey.go @@ -342,7 +342,7 @@ func (am *DefaultAccountManager) ListSetupKeys(accountID, userID string) ([]*Set keys := make([]*SetupKey, 0, len(account.SetupKeys)) for _, key := range account.SetupKeys { var k *SetupKey - if !user.IsAdmin() { + if !user.HasAdminPower() { k = key.HiddenCopy(999) } else { k = key.Copy() @@ -384,7 +384,7 @@ func (am *DefaultAccountManager) GetSetupKey(accountID, userID, keyID string) (* foundKey.UpdatedAt = foundKey.CreatedAt } - if !user.IsAdmin() { + if !user.HasAdminPower() { foundKey = foundKey.HiddenCopy(999) } diff --git a/management/server/user.go b/management/server/user.go index b759bba42..4bd46f617 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -15,6 +15,7 @@ import ( ) const ( + UserRoleOwner UserRole = "owner" UserRoleAdmin UserRole = "admin" UserRoleUser UserRole = "user" UserRoleUnknown UserRole = "unknown" @@ -30,6 +31,8 @@ const ( // StrRoleToUserRole returns UserRole for a given strRole or UserRoleUnknown if the specified role is unknown func StrRoleToUserRole(strRole string) UserRole { switch strings.ToLower(strRole) { + case "owner": + return UserRoleOwner case "admin": return UserRoleAdmin case "user": @@ -97,9 +100,9 @@ func (u *User) LastDashboardLoginChanged(LastLogin time.Time) bool { return LastLogin.After(u.LastLogin) && !u.LastLogin.IsZero() } -// IsAdmin returns true if the user is an admin, false otherwise -func (u *User) IsAdmin() bool { - return u.Role == UserRoleAdmin +// HasAdminPower returns true if the user has admin or owner roles, false otherwise +func (u *User) HasAdminPower() bool { + return u.Role == UserRoleAdmin || u.Role == UserRoleOwner } // ToUserInfo converts a User object to a UserInfo object. @@ -193,6 +196,11 @@ func NewAdminUser(id string) *User { return NewUser(id, UserRoleAdmin, false, false, "", []string{}, UserIssuedAPI) } +// NewOwnerUser creates a new user with role UserRoleOwner +func NewOwnerUser(id string) *User { + return NewUser(id, UserRoleOwner, false, false, "", []string{}, UserIssuedAPI) +} + // createServiceUser creates a new service user under the given account. func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUserID string, role UserRole, serviceUserName string, nonDeletable bool, autoGroups []string) (*UserInfo, error) { unlock := am.Store.AcquireAccountLock(accountID) @@ -207,8 +215,12 @@ func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUs if executingUser == nil { return nil, status.Errorf(status.NotFound, "user not found") } - if executingUser.Role != UserRoleAdmin { - return nil, status.Errorf(status.PermissionDenied, "only admins can create service users") + if !executingUser.HasAdminPower() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power can create service users") + } + + if role == UserRoleOwner { + return nil, status.Errorf(status.InvalidArgument, "can't create a service user with owner role") } newUserID := uuid.New().String() @@ -258,11 +270,15 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite return nil, fmt.Errorf("provided user update is nil") } + invitedRole := StrRoleToUserRole(invite.Role) + 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") + case invitedRole == UserRoleOwner: + return nil, status.Errorf(status.InvalidArgument, "can't invite a user with owner role") default: } @@ -311,10 +327,9 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite return nil, err } - role := StrRoleToUserRole(invite.Role) newUser := &User{ Id: idpUser.ID, - Role: role, + Role: invitedRole, AutoGroups: invite.AutoGroups, Issued: invite.Issued, IntegrationReference: invite.IntegrationReference, @@ -416,8 +431,8 @@ func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, t if executingUser == nil { return status.Errorf(status.NotFound, "user not found") } - if executingUser.Role != UserRoleAdmin { - return status.Errorf(status.PermissionDenied, "only admins can delete users") + if !executingUser.HasAdminPower() { + return status.Errorf(status.PermissionDenied, "only users with admin power can delete users") } targetUser := account.Users[targetUserID] @@ -425,9 +440,13 @@ func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, t return status.Errorf(status.NotFound, "target user not found") } + if targetUser.Role == UserRoleOwner { + return status.Errorf(status.PermissionDenied, "unable to delete a user with owner role") + } + // disable deleting integration user if the initiator is not admin service user if targetUser.Issued == UserIssuedIntegration && !executingUser.IsServiceUser { - return status.Errorf(status.PermissionDenied, "only admin service user can delete this user") + return status.Errorf(status.PermissionDenied, "only integration service user can delete this user") } // handle service user first and exit, no need to fetch extra data from IDP, etc @@ -566,7 +585,7 @@ func (am *DefaultAccountManager) CreatePAT(accountID string, initiatorUserID str return nil, status.Errorf(status.NotFound, "user not found") } - if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) { + if !(initiatorUserID == targetUserID || (executingUser.HasAdminPower() && targetUser.IsServiceUser)) { return nil, status.Errorf(status.PermissionDenied, "no permission to create PAT for this user") } @@ -608,7 +627,7 @@ func (am *DefaultAccountManager) DeletePAT(accountID string, initiatorUserID str return status.Errorf(status.NotFound, "user not found") } - if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) { + if !(initiatorUserID == targetUserID || (executingUser.HasAdminPower() && targetUser.IsServiceUser)) { return status.Errorf(status.PermissionDenied, "no permission to delete PAT for this user") } @@ -658,7 +677,7 @@ func (am *DefaultAccountManager) GetPAT(accountID string, initiatorUserID string return nil, status.Errorf(status.NotFound, "user not found") } - if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) { + if !(initiatorUserID == targetUserID || (executingUser.HasAdminPower() && targetUser.IsServiceUser)) { return nil, status.Errorf(status.PermissionDenied, "no permission to get PAT for this userser") } @@ -690,7 +709,7 @@ func (am *DefaultAccountManager) GetAllPATs(accountID string, initiatorUserID st return nil, status.Errorf(status.NotFound, "user not found") } - if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) { + if !(initiatorUserID == targetUserID || (executingUser.HasAdminPower() && targetUser.IsServiceUser)) { return nil, status.Errorf(status.PermissionDenied, "no permission to get PAT for this user") } @@ -727,8 +746,8 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string return nil, err } - if !initiatorUser.IsAdmin() || initiatorUser.IsBlocked() { - return nil, status.Errorf(status.PermissionDenied, "only admins are authorized to perform user update operations") + if !initiatorUser.HasAdminPower() || initiatorUser.IsBlocked() { + return nil, status.Errorf(status.PermissionDenied, "only users with admin power are authorized to perform user update operations") } oldUser := account.Users[update.Id] @@ -740,14 +759,38 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string oldUser = update } - if initiatorUser.IsAdmin() && initiatorUserID == update.Id && oldUser.Blocked != update.Blocked { + if initiatorUser.HasAdminPower() && initiatorUserID == update.Id && oldUser.Blocked != update.Blocked { return nil, status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves") } - if initiatorUser.IsAdmin() && initiatorUserID == update.Id && update.Role != UserRoleAdmin { + if initiatorUser.HasAdminPower() && initiatorUserID == update.Id && update.Role != initiatorUser.Role { return nil, status.Errorf(status.PermissionDenied, "admins can't change their role") } + if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.Role != oldUser.Role { + return nil, status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user") + } + + if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() { + return nil, status.Errorf(status.PermissionDenied, "unable to block owner user") + } + + if initiatorUser.Role == UserRoleAdmin && update.Role == UserRoleOwner && update.Role != oldUser.Role { + return nil, status.Errorf(status.PermissionDenied, "only owners can add owner role to other users") + } + + if oldUser.IsServiceUser && update.Role == UserRoleOwner { + return nil, status.Errorf(status.PermissionDenied, "can't update a service user with owner role") + } + + transferedOwnerRole := false + if initiatorUser.Role == UserRoleOwner && initiatorUserID != update.Id && update.Role == UserRoleOwner { + newInitiatorUser := initiatorUser.Copy() + newInitiatorUser.Role = UserRoleAdmin + account.Users[initiatorUserID] = newInitiatorUser + transferedOwnerRole = true + } + // only auto groups, revoked status, and integration reference can be updated for now newUser := oldUser.Copy() newUser.Role = update.Role @@ -806,9 +849,12 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string } } - // store activity logs - if oldUser.Role != newUser.Role { + switch { + case transferedOwnerRole: + am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.TransferredOwnerRole, nil) + case oldUser.Role != newUser.Role: am.StoreEvent(initiatorUserID, oldUser.Id, accountID, activity.UserRoleUpdated, map[string]any{"role": newUser.Role}) + default: } if update.AutoGroups != nil { @@ -882,7 +928,7 @@ func (am *DefaultAccountManager) GetOrCreateAccountByUser(userID, domain string) userObj := account.Users[userID] - if account.Domain != lowerDomain && userObj.Role == UserRoleAdmin { + if account.Domain != lowerDomain && userObj.Role == UserRoleOwner { account.Domain = lowerDomain err = am.Store.SaveAccount(account) if err != nil { @@ -940,7 +986,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( // in case of self-hosted, or IDP doesn't return anything, we will return the locally stored userInfo if len(queriedUsers) == 0 { for _, accountUser := range account.Users { - if !user.IsAdmin() && user.Id != accountUser.Id { + if !user.HasAdminPower() && user.Id != accountUser.Id { // if user is not an admin then show only current user and do not show other users continue } @@ -954,7 +1000,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( } for _, localUser := range account.Users { - if !user.IsAdmin() && user.Id != localUser.Id { + if !user.HasAdminPower() && user.Id != localUser.Id { // if user is not an admin then show only current user and do not show other users continue } diff --git a/management/server/user_test.go b/management/server/user_test.go index e628981b0..d445ade62 100644 --- a/management/server/user_test.go +++ b/management/server/user_test.go @@ -348,6 +348,11 @@ func TestUser_CreateServiceUser(t *testing.T) { assert.Zero(t, user.Email) assert.True(t, user.IsServiceUser) assert.Equal(t, "active", user.Status) + + _, err = am.createServiceUser(mockAccountID, mockUserID, UserRoleOwner, mockServiceUserName, false, nil) + if err == nil { + t.Fatal("should return error when creating service user with owner role") + } } func TestUser_CreateUser_ServiceUser(t *testing.T) { @@ -412,6 +417,75 @@ func TestUser_CreateUser_RegularUser(t *testing.T) { assert.Errorf(t, err, "Not configured IDP will throw error but right path used") } +func TestUser_InviteNewUser(t *testing.T) { + store := newStore(t) + account := newAccountWithId(mockAccountID, mockUserID, "") + + err := store.SaveAccount(account) + if err != nil { + t.Fatalf("Error when saving account: %s", err) + } + + am := DefaultAccountManager{ + Store: store, + eventStore: &activity.InMemoryEventStore{}, + cacheLoading: map[string]chan struct{}{}, + } + + goCacheClient := gocache.New(CacheExpirationMax, 30*time.Minute) + goCacheStore := cacheStore.NewGoCache(goCacheClient) + am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](goCacheStore)) + + mockData := []*idp.UserData{ + { + Email: "user@test.com", + Name: "user", + ID: mockUserID, + }, + } + + idpMock := idp.MockIDP{ + CreateUserFunc: func(email, name, accountID, invitedByEmail string) (*idp.UserData, error) { + newData := &idp.UserData{ + Email: email, + Name: name, + ID: "id", + } + + mockData = append(mockData, newData) + + return newData, nil + }, + GetAccountFunc: func(accountId string) ([]*idp.UserData, error) { + return mockData, nil + }, + } + + am.idpManager = &idpMock + + // test if new invite with regular role works + _, err = am.inviteNewUser(mockAccountID, mockUserID, &UserInfo{ + Name: mockServiceUserName, + Role: mockRole, + Email: "test@teste.com", + IsServiceUser: false, + AutoGroups: []string{"group1", "group2"}, + }) + + assert.NoErrorf(t, err, "Invite user should not throw error") + + // test if new invite with owner role fails + _, err = am.inviteNewUser(mockAccountID, mockUserID, &UserInfo{ + Name: mockServiceUserName, + Role: string(UserRoleOwner), + Email: "test2@teste.com", + IsServiceUser: false, + AutoGroups: []string{"group1", "group2"}, + }) + + assert.Errorf(t, err, "Invite user with owner role should throw error") +} + func TestUser_DeleteUser_ServiceUser(t *testing.T) { tests := []struct { name string @@ -514,6 +588,14 @@ func TestUser_DeleteUser_regularUser(t *testing.T) { Issued: UserIssuedIntegration, } + targetId = "user5" + account.Users[targetId] = &User{ + Id: targetId, + IsServiceUser: false, + Issued: UserIssuedAPI, + Role: UserRoleOwner, + } + err := store.SaveAccount(account) if err != nil { t.Fatalf("Error when saving account: %s", err) @@ -546,6 +628,12 @@ func TestUser_DeleteUser_regularUser(t *testing.T) { assertErrFunc: assert.Error, assertErrMessage: "only admin service user can delete this user", }, + { + name: "Delete user with owner role should return permission denied ", + userID: "user5", + assertErrFunc: assert.Error, + assertErrMessage: "unable to delete a user with owner role", + }, } for _, testCase := range testCases { @@ -581,7 +669,7 @@ func TestDefaultAccountManager_GetUser(t *testing.T) { } assert.Equal(t, mockUserID, user.Id) - assert.True(t, user.IsAdmin()) + assert.True(t, user.HasAdminPower()) assert.False(t, user.IsBlocked()) } @@ -609,7 +697,7 @@ func TestDefaultAccountManager_ListUsers(t *testing.T) { admins := 0 regular := 0 for _, user := range users { - if user.IsAdmin() { + if user.HasAdminPower() { admins++ continue } @@ -677,10 +765,10 @@ func TestDefaultAccountManager_ExternalCache(t *testing.T) { func TestUser_IsAdmin(t *testing.T) { user := NewAdminUser(mockUserID) - assert.True(t, user.IsAdmin()) + assert.True(t, user.HasAdminPower()) user = NewRegularUser(mockUserID) - assert.False(t, user.IsAdmin()) + assert.False(t, user.HasAdminPower()) } func TestUser_GetUsersFromAccount_ForAdmin(t *testing.T) { @@ -746,26 +834,39 @@ func TestDefaultAccountManager_SaveUser(t *testing.T) { } regularUserID := "regularUser" + serviceUserID := "serviceUser" + adminUserID := "adminUser" + ownerUserID := "ownerUser" tt := []struct { - name string - adminInitiator bool - update *User - expectedErr bool + name string + initiatorID string + update *User + expectedErr bool }{ { - name: "Should_Fail_To_Update_Admin_Role", - expectedErr: true, - adminInitiator: true, + name: "Should_Fail_To_Update_Admin_Role", + expectedErr: true, + initiatorID: adminUserID, update: &User{ - Id: userID, + Id: adminUserID, Role: UserRoleUser, Blocked: false, }, }, { - name: "Should_Fail_When_Admin_Blocks_Themselves", - expectedErr: true, - adminInitiator: true, + name: "Should_Fail_When_Admin_Blocks_Themselves", + expectedErr: true, + initiatorID: adminUserID, + update: &User{ + Id: adminUserID, + Role: UserRoleAdmin, + Blocked: true, + }, + }, + { + name: "Should_Fail_To_Update_Non_Existing_User", + expectedErr: true, + initiatorID: adminUserID, update: &User{ Id: userID, Role: UserRoleAdmin, @@ -773,66 +874,125 @@ func TestDefaultAccountManager_SaveUser(t *testing.T) { }, }, { - name: "Should_Fail_To_Update_Non_Existing_User", - expectedErr: true, - adminInitiator: true, + name: "Should_Fail_To_Update_When_Initiator_Is_Not_An_Admin", + expectedErr: true, + initiatorID: regularUserID, update: &User{ - Id: userID, + Id: adminUserID, Role: UserRoleAdmin, Blocked: true, }, }, { - name: "Should_Fail_To_Update_When_Initiator_Is_Not_An_Admin", - expectedErr: true, - adminInitiator: false, - update: &User{ - Id: userID, - Role: UserRoleAdmin, - Blocked: true, - }, - }, - { - name: "Should_Update_User", - expectedErr: false, - adminInitiator: true, + name: "Should_Update_User", + expectedErr: false, + initiatorID: adminUserID, update: &User{ Id: regularUserID, Role: UserRoleAdmin, Blocked: true, }, }, + { + name: "Should_Transfer_Owner_Role_To_User", + expectedErr: false, + initiatorID: ownerUserID, + update: &User{ + Id: adminUserID, + Role: UserRoleAdmin, + Blocked: false, + }, + }, + { + name: "Should_Fail_To_Transfer_Owner_Role_To_Service_User", + expectedErr: true, + initiatorID: ownerUserID, + update: &User{ + Id: serviceUserID, + Role: UserRoleOwner, + Blocked: false, + }, + }, + { + name: "Should_Fail_To_Update_Owner_User_Role_By_Admin", + expectedErr: true, + initiatorID: adminUserID, + update: &User{ + Id: ownerUserID, + Role: UserRoleAdmin, + Blocked: false, + }, + }, + { + name: "Should_Fail_To_Update_Owner_User_Role_By_User", + expectedErr: true, + initiatorID: regularUserID, + update: &User{ + Id: ownerUserID, + Role: UserRoleAdmin, + Blocked: false, + }, + }, + { + name: "Should_Fail_To_Update_Owner_User_Role_By_Service_User", + expectedErr: true, + initiatorID: serviceUserID, + update: &User{ + Id: ownerUserID, + Role: UserRoleAdmin, + Blocked: false, + }, + }, + { + name: "Should_Fail_To_Update_Owner_Role_By_Admin", + expectedErr: true, + initiatorID: adminUserID, + update: &User{ + Id: regularUserID, + Role: UserRoleOwner, + Blocked: false, + }, + }, + { + name: "Should_Fail_To_Block_Owner_Role_By_Admin", + expectedErr: true, + initiatorID: adminUserID, + update: &User{ + Id: ownerUserID, + Role: UserRoleOwner, + Blocked: true, + }, + }, } for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { - // create an account and an admin user - account, err := manager.GetOrCreateAccountByUser(userID, "netbird.io") - if err != nil { - t.Fatal(err) - } + // create an account and an admin user + account, err := manager.GetOrCreateAccountByUser(ownerUserID, "netbird.io") + if err != nil { + t.Fatal(err) + } - // create a regular user - account.Users[regularUserID] = NewRegularUser(regularUserID) - err = manager.Store.SaveAccount(account) - if err != nil { - t.Fatal(err) - } + // create other users + account.Users[regularUserID] = NewRegularUser(regularUserID) + account.Users[adminUserID] = NewAdminUser(adminUserID) + account.Users[serviceUserID] = &User{IsServiceUser: true, Id: serviceUserID, Role: UserRoleAdmin, ServiceUserName: "service"} + err = manager.Store.SaveAccount(account) + if err != nil { + t.Fatal(err) + } - initiatorID := userID - if !tc.adminInitiator { - initiatorID = regularUserID - } + updated, err := manager.SaveUser(account.Id, tc.initiatorID, tc.update) + if tc.expectedErr { + require.Errorf(t, err, "expecting SaveUser to throw an error") + } else { + require.NoError(t, err, "expecting SaveUser not to throw an error") + assert.NotNil(t, updated) - updated, err := manager.SaveUser(account.Id, initiatorID, tc.update) - if tc.expectedErr { - require.Errorf(t, err, "expecting SaveUser to throw an error") - } else { - require.NoError(t, err, "expecting SaveUser not to throw an error") - assert.NotNil(t, updated) - - assert.Equal(t, string(tc.update.Role), updated.Role) - assert.Equal(t, tc.update.IsBlocked(), updated.IsBlocked) - } + assert.Equal(t, string(tc.update.Role), updated.Role) + assert.Equal(t, tc.update.IsBlocked(), updated.IsBlocked) + } + }) } } From b9fc0085421e37276f754ee0487380eb6903a9ae Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 4 Dec 2023 12:49:36 +0100 Subject: [PATCH 49/59] extract peer preparation --- go.mod | 2 +- go.sum | 4 ++++ management/server/peer.go | 19 +++++++++---------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index f3e939852..1d83be3b3 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 + github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 9407ba7b2..4474f9b4f 100644 --- a/go.sum +++ b/go.sum @@ -497,6 +497,10 @@ github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 h1:fNGkwtLHz0U4bP3bPVSwbtfL4I0ZVNYNCpmGwl6TETE= github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204103019-ac6dd5a9c91c h1:LerqmkLL6OG+1n+wzxgXOx6kJZETUJVAvU5fZokUn9Q= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204103019-ac6dd5a9c91c/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f h1:yDNCzVwhgV05+D1sFmXCj9HEr2sob/MoWaGQLy3mKyY= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= diff --git a/management/server/peer.go b/management/server/peer.go index 630a01548..c025d7fd6 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -406,16 +406,14 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P Ephemeral: ephemeral, } - if account.Settings.Extra != nil && account.Settings.Extra.PeerApprovalEnabled { - newPeer.Status.RequiresApproval = true - } + peerToAdd := additions.PreparePeer(newPeer, account.Settings) // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { return nil, nil, err } - group.Peers = append(group.Peers, newPeer.ID) + group.Peers = append(group.Peers, peerToAdd.ID) var groupsToAdd []string if addedByUser { @@ -433,26 +431,27 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P if len(groupsToAdd) > 0 { for _, s := range groupsToAdd { if g, ok := account.Groups[s]; ok && g.Name != "All" { - g.Peers = append(g.Peers, newPeer.ID) + g.Peers = append(g.Peers, peerToAdd.ID) } } } - account.Peers[newPeer.ID] = newPeer + account.Peers[peerToAdd.ID] = newPeer account.Network.IncSerial() err = am.Store.SaveAccount(account) if err != nil { return nil, nil, err } - opEvent.TargetID = newPeer.ID - opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) + opEvent.TargetID = peerToAdd.ID + opEvent.Meta = peerToAdd.EventMeta(am.GetDNSDomain()) + am.StoreEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) am.updateAccountPeers(account) - networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain) - return newPeer, networkMap, nil + networkMap := account.GetPeerNetworkMap(peerToAdd.ID, am.dnsDomain) + return peerToAdd, networkMap, nil } // SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible From 1cd5a66575695324b86e0cda6804e573f789a851 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 4 Dec 2023 13:00:13 +0100 Subject: [PATCH 50/59] adding setup key name to the event meta for adding peers by setup key --- management/server/peer.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/management/server/peer.go b/management/server/peer.go index 67076921b..356353db6 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -525,6 +525,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* opEvent.InitiatorID = sk.Id opEvent.Activity = activity.PeerAddedWithSetupKey ephemeral = sk.Ephemeral + opEvent.Meta["setup_key_name"] = sk.Name } else { opEvent.InitiatorID = userID opEvent.Activity = activity.PeerAddedByUser @@ -598,7 +599,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } opEvent.TargetID = newPeer.ID - opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) + peerMeta := newPeer.EventMeta(am.GetDNSDomain()) + for k, v := range peerMeta { + opEvent.Meta[k] = v + } + am.StoreEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) am.updateAccountPeers(account) From e37a337164cd4fae3498e1b9c15a420f9553529a Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Mon, 4 Dec 2023 13:34:06 +0100 Subject: [PATCH 51/59] Add gosec linter (#1342) This PR adds `gosec` linter with the following checks disabled: - G102: Bind to all interfaces - G107: Url provided to HTTP request as taint input - G112: Potential slowloris attack - G114: Use of net/http serve function that has no support for setting timeouts - G204: Audit use of command execution - G401: Detect the usage of DES, RC4, MD5 or SHA1 - G402: Look for bad TLS connection settings - G404: Insecure random number source (rand) - G501: Import blocklist: crypto/md5 - G505: Import blocklist: crypto/sha1 We have complaints related to the checks above. They have to be addressed separately. --- .golangci.yaml | 52 ++++++++++++++++--- client/firewall/uspfilter/uspfilter.go | 6 ++- .../internal/routemanager/systemops_linux.go | 5 +- client/ui/client_ui.go | 2 +- iface/tun_linux.go | 3 +- management/server/http/policies_handler.go | 6 ++- management/server/metrics/selfhosted.go | 12 +++-- 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index d847e63c3..757a60a39 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -12,6 +12,44 @@ linters-settings: # Default: false check-type-assertions: false + gosec: + includes: + - G101 # Look for hard coded credentials + #- G102 # Bind to all interfaces + - G103 # Audit the use of unsafe block + - G104 # Audit errors not checked + - G106 # Audit the use of ssh.InsecureIgnoreHostKey + #- G107 # Url provided to HTTP request as taint input + - G108 # Profiling endpoint automatically exposed on /debug/pprof + - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 + - G110 # Potential DoS vulnerability via decompression bomb + - G111 # Potential directory traversal + #- G112 # Potential slowloris attack + - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) + #- G114 # Use of net/http serve function that has no support for setting timeouts + - G201 # SQL query construction using format string + - G202 # SQL query construction using string concatenation + - G203 # Use of unescaped data in HTML templates + #- G204 # Audit use of command execution + - G301 # Poor file permissions used when creating a directory + - G302 # Poor file permissions used with chmod + - G303 # Creating tempfile using a predictable path + - G304 # File path provided as taint input + - G305 # File traversal when extracting zip/tar archive + - G306 # Poor file permissions used when writing to a new file + - G307 # Poor file permissions used when creating a file with os.Create + #- G401 # Detect the usage of DES, RC4, MD5 or SHA1 + #- G402 # Look for bad TLS connection settings + - G403 # Ensure minimum RSA key length of 2048 bits + #- G404 # Insecure random number source (rand) + #- G501 # Import blocklist: crypto/md5 + - G502 # Import blocklist: crypto/des + - G503 # Import blocklist: crypto/rc4 + - G504 # Import blocklist: net/http/cgi + #- G505 # Import blocklist: crypto/sha1 + - G601 # Implicit memory aliasing of items from a range statement + - G602 # Slice access out of bounds + gocritic: disabled-checks: - commentFormatting @@ -49,6 +87,7 @@ linters: - durationcheck # durationcheck checks for two durations multiplied together - forbidigo # forbidigo forbids identifiers - gocritic # provides diagnostics that check for bugs, performance and style issues + - gosec # inspects source code for security problems - mirror # mirror reports wrong mirror patterns of bytes/strings usage - misspell # misspess finds commonly misspelled English words in comments - nilerr # finds the code that returns nil even if it checks that the error is not nil @@ -65,19 +104,20 @@ issues: exclude-rules: # allow fmt - - path: management/cmd/root.go + - path: management/cmd/root\.go linters: forbidigo - - path: signal/cmd/root.go + - path: signal/cmd/root\.go linters: forbidigo - - path: sharedsock/filter.go + - path: sharedsock/filter\.go linters: - unused - - path: client/firewall/iptables/rule.go + - path: client/firewall/iptables/rule\.go linters: - unused - - path: test.go + - path: test\.go linters: - mirror - - path: mock.go + - gosec + - path: mock\.go linters: - nilnil diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/uspfilter.go index 6fd11e652..7119e791c 100644 --- a/client/firewall/uspfilter/uspfilter.go +++ b/client/firewall/uspfilter/uspfilter.go @@ -355,14 +355,16 @@ func (m *Manager) RemovePacketHook(hookID string) error { for _, arr := range m.incomingRules { for _, r := range arr { if r.id == hookID { - return m.DeleteRule(&r) + rule := r + return m.DeleteRule(&rule) } } } for _, arr := range m.outgoingRules { for _, r := range arr { if r.id == hookID { - return m.DeleteRule(&r) + rule := r + return m.DeleteRule(&rule) } } } diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index b5b4f5696..0562826a5 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -107,7 +107,8 @@ loop: break loop case syscall.RTM_NEWROUTE: rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) - attrs, err := syscall.ParseNetlinkRouteAttr(&m) + msg := m + attrs, err := syscall.ParseNetlinkRouteAttr(&msg) if err != nil { return nil, err } @@ -146,5 +147,5 @@ func enableIPForwarding() error { return nil } - return os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644) + return os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644) //nolint:gosec } diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go index da230e214..1dc0bb374 100644 --- a/client/ui/client_ui.go +++ b/client/ui/client_ui.go @@ -634,5 +634,5 @@ func checkPIDFile() error { } } - return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) + return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) //nolint:gosec } diff --git a/iface/tun_linux.go b/iface/tun_linux.go index 93c03436e..1a3537394 100644 --- a/iface/tun_linux.go +++ b/iface/tun_linux.go @@ -99,7 +99,8 @@ func (c *tunDevice) assignAddr() error { } if len(list) > 0 { for _, a := range list { - err = netlink.AddrDel(link, &a) + addr := a + err = netlink.AddrDel(link, &addr) if err != nil { return err } diff --git a/management/server/http/policies_handler.go b/management/server/http/policies_handler.go index f8c876a41..c7b69897a 100644 --- a/management/server/http/policies_handler.go +++ b/management/server/http/policies_handler.go @@ -290,11 +290,13 @@ func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Polic Enabled: policy.Enabled, } for _, r := range policy.Rules { + rID := r.ID + rDescription := r.Description rule := api.PolicyRule{ - Id: &r.ID, + Id: &rID, Name: r.Name, Enabled: r.Enabled, - Description: &r.Description, + Description: &rDescription, Bidirectional: r.Bidirectional, Protocol: api.PolicyRuleProtocol(r.Protocol), Action: api.PolicyRuleAction(r.Action), diff --git a/management/server/metrics/selfhosted.go b/management/server/metrics/selfhosted.go index 90d69b47b..9a8c65b8e 100644 --- a/management/server/metrics/selfhosted.go +++ b/management/server/metrics/selfhosted.go @@ -390,14 +390,18 @@ func getMinMaxVersion(inputList []string) (string, string) { } } } - switch len(versions) { + + targetIndex := 1 + l := len(versions) + + switch l { case 0: return "", "" - case 1: - v := versions[0].String() + case targetIndex: + v := versions[targetIndex-1].String() return v, v default: sort.Sort(version.Collection(versions)) - return versions[0].String(), versions[len(versions)-1].String() + return versions[targetIndex-1].String(), versions[l-1].String() } } From 92adf57fea25258726f75d0b510659def823db52 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 4 Dec 2023 13:49:46 +0100 Subject: [PATCH 52/59] fix map assignment --- management/server/peer.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/management/server/peer.go b/management/server/peer.go index 356353db6..dcfe26410 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -510,6 +510,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } var ephemeral bool + setupKeyName := "" if !addedByUser { // validate the setup key if adding with a key sk, err := account.FindSetupKey(upperKey) @@ -525,7 +526,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* opEvent.InitiatorID = sk.Id opEvent.Activity = activity.PeerAddedWithSetupKey ephemeral = sk.Ephemeral - opEvent.Meta["setup_key_name"] = sk.Name + setupKeyName = sk.Name } else { opEvent.InitiatorID = userID opEvent.Activity = activity.PeerAddedByUser @@ -599,9 +600,9 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } opEvent.TargetID = newPeer.ID - peerMeta := newPeer.EventMeta(am.GetDNSDomain()) - for k, v := range peerMeta { - opEvent.Meta[k] = v + opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) + if !addedByUser { + opEvent.Meta["setup_key_name"] = setupKeyName } am.StoreEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) From 89249b414f136c5a7eeb400d97570d5a2d9390b9 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 4 Dec 2023 14:53:38 +0100 Subject: [PATCH 53/59] move peer validation into getPeerconnectionResources --- management/server/account.go | 1 - management/server/policy.go | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/management/server/account.go b/management/server/account.go index 14fd241a1..baaea2005 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -368,7 +368,6 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { } } aclPeers, firewallRules := a.getPeerConnectionResources(peerID) - aclPeers = additions.ValidatePeers(aclPeers) // exclude expired peers var peersToConnect []*nbpeer.Peer var expiredPeers []*nbpeer.Peer diff --git a/management/server/policy.go b/management/server/policy.go index 37718a3e0..0eb2fb538 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/netbirdio/management-integrations/additions" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/proto" @@ -220,6 +221,8 @@ func (a *Account) getPeerConnectionResources(peerID string) ([]*nbpeer.Peer, []* sourcePeers, peerInSources := getAllPeersFromGroups(a, rule.Sources, peerID) destinationPeers, peerInDestinations := getAllPeersFromGroups(a, rule.Destinations, peerID) + sourcePeers = additions.ValidatePeers(sourcePeers) + destinationPeers = additions.ValidatePeers(destinationPeers) if rule.Bidirectional { if peerInSources { From dc8f55f23e925061847d59236f191763434b5d9a Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 4 Dec 2023 16:26:34 +0100 Subject: [PATCH 54/59] remove dependency cycle from prepare peer --- go.mod | 2 +- go.sum | 4 ++++ management/server/peer.go | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 1d83be3b3..12b7d9a95 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f + github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 4474f9b4f..b0fdfecfd 100644 --- a/go.sum +++ b/go.sum @@ -501,6 +501,10 @@ github.com/netbirdio/management-integrations/additions v0.0.0-20231204103019-ac6 github.com/netbirdio/management-integrations/additions v0.0.0-20231204103019-ac6dd5a9c91c/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f h1:yDNCzVwhgV05+D1sFmXCj9HEr2sob/MoWaGQLy3mKyY= github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204151811-a5cf1000bdd4 h1:ApjFM/CU3My4lLL1HVb0j7q4+jmXdbYFVT8RJNLQjyc= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204151811-a5cf1000bdd4/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 h1:TEaiKhkxwIFA2AmIj/7x9X/6zlXjV68Vs6Wclew1oTs= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= diff --git a/management/server/peer.go b/management/server/peer.go index c025d7fd6..de7ce263e 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -406,14 +406,16 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P Ephemeral: ephemeral, } - peerToAdd := additions.PreparePeer(newPeer, account.Settings) + if account.Settings.Extra != nil { + newPeer = additions.PreparePeer(newPeer, account.Settings.Extra) + } // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { return nil, nil, err } - group.Peers = append(group.Peers, peerToAdd.ID) + group.Peers = append(group.Peers, newPeer.ID) var groupsToAdd []string if addedByUser { @@ -431,27 +433,27 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P if len(groupsToAdd) > 0 { for _, s := range groupsToAdd { if g, ok := account.Groups[s]; ok && g.Name != "All" { - g.Peers = append(g.Peers, peerToAdd.ID) + g.Peers = append(g.Peers, newPeer.ID) } } } - account.Peers[peerToAdd.ID] = newPeer + account.Peers[newPeer.ID] = newPeer account.Network.IncSerial() err = am.Store.SaveAccount(account) if err != nil { return nil, nil, err } - opEvent.TargetID = peerToAdd.ID - opEvent.Meta = peerToAdd.EventMeta(am.GetDNSDomain()) + opEvent.TargetID = newPeer.ID + opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) am.StoreEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) am.updateAccountPeers(account) - networkMap := account.GetPeerNetworkMap(peerToAdd.ID, am.dnsDomain) - return peerToAdd, networkMap, nil + networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain) + return newPeer, networkMap, nil } // SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible From 7a18dea7667e8d5b36c0c6162ed1c29861c30263 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 4 Dec 2023 17:35:49 +0100 Subject: [PATCH 55/59] go mod tidy --- go.sum | 8 -------- 1 file changed, 8 deletions(-) diff --git a/go.sum b/go.sum index b0fdfecfd..27f48b31e 100644 --- a/go.sum +++ b/go.sum @@ -495,14 +495,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= -github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2 h1:fNGkwtLHz0U4bP3bPVSwbtfL4I0ZVNYNCpmGwl6TETE= -github.com/netbirdio/management-integrations/additions v0.0.0-20231129145353-f46b1dd842f2/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204103019-ac6dd5a9c91c h1:LerqmkLL6OG+1n+wzxgXOx6kJZETUJVAvU5fZokUn9Q= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204103019-ac6dd5a9c91c/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f h1:yDNCzVwhgV05+D1sFmXCj9HEr2sob/MoWaGQLy3mKyY= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204103351-c9d8082fc22f/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204151811-a5cf1000bdd4 h1:ApjFM/CU3My4lLL1HVb0j7q4+jmXdbYFVT8RJNLQjyc= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204151811-a5cf1000bdd4/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 h1:TEaiKhkxwIFA2AmIj/7x9X/6zlXjV68Vs6Wclew1oTs= github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= From 0d2db4b172a0c4de9306b696deb337feabf10edc Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Mon, 4 Dec 2023 19:02:16 +0100 Subject: [PATCH 56/59] update API doc --- management/server/http/api/openapi.yml | 2 +- management/server/http/api/types.gen.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index cf48fe4f8..2b8d614f6 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -75,7 +75,7 @@ components: type: object properties: peer_approval_enabled: - description: Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + description: (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. type: boolean example: true AccountRequest: diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 5298e887b..8e41f5672 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -144,7 +144,7 @@ type Account struct { // AccountExtraSettings defines model for AccountExtraSettings. type AccountExtraSettings struct { - // PeerApprovalEnabled Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + // PeerApprovalEnabled (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. PeerApprovalEnabled *bool `json:"peer_approval_enabled,omitempty"` } From d1f13025d19beabfc4513c217fb9cde4f157f7a6 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 5 Dec 2023 12:39:15 +0100 Subject: [PATCH 57/59] switch back to use netbird main --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 12b7d9a95..018d7ee01 100644 --- a/go.mod +++ b/go.mod @@ -51,8 +51,8 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 + github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 diff --git a/go.sum b/go.sum index 27f48b31e..d4f4c28f7 100644 --- a/go.sum +++ b/go.sum @@ -497,8 +497,12 @@ github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 h1:TEaiKhkxwIFA2AmIj/7x9X/6zlXjV68Vs6Wclew1oTs= github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695 h1:c/Rvyy/mqbFoKo6FS8ihQ3/3y+TAl0qDEH0pO2tXayM= +github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695 h1:9HRnqSosRuKyOZgVN/hJW3DG2zVyt5AARmiQlSuDPIc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= From f822a58326d9b80146b01f24ab679d26fdf87581 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 5 Dec 2023 12:54:01 +0100 Subject: [PATCH 58/59] go mod tidy --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index d4f4c28f7..9ad425dfd 100644 --- a/go.sum +++ b/go.sum @@ -495,12 +495,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 h1:TEaiKhkxwIFA2AmIj/7x9X/6zlXjV68Vs6Wclew1oTs= -github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695 h1:c/Rvyy/mqbFoKo6FS8ihQ3/3y+TAl0qDEH0pO2tXayM= github.com/netbirdio/management-integrations/additions v0.0.0-20231205113053-c462587ae695/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695 h1:9HRnqSosRuKyOZgVN/hJW3DG2zVyt5AARmiQlSuDPIc= github.com/netbirdio/management-integrations/integrations v0.0.0-20231205113053-c462587ae695/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= From 27ed88f918fe99a600880652ca44d5546d69cbb2 Mon Sep 17 00:00:00 2001 From: Yury Gargay Date: Tue, 5 Dec 2023 14:17:56 +0100 Subject: [PATCH 59/59] Implement lightweight method to check is peer has update channel (#1351) Instead of GetAllConnectedPeers that need to traverse the whole connections map in order to find one channel there. --- management/server/account.go | 6 +++ management/server/http/peers_handler.go | 7 +-- management/server/http/peers_handler_test.go | 6 +-- management/server/mock_server/account_mock.go | 9 ++++ .../server/telemetry/updatechannel_metrics.go | 52 +++++-------------- management/server/updatechannel.go | 18 +++++++ 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index baaea2005..4c13c8535 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -114,6 +114,7 @@ type AccountManager interface { LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API GetAllConnectedPeers() (map[string]struct{}, error) + HasConnectedChannel(peerID string) bool GetExternalCacheManager() ExternalCacheManager } @@ -1676,6 +1677,11 @@ func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, er return am.peersUpdateManager.GetAllConnectedPeers(), nil } +// HasConnectedChannel returns true if peers has channel in update manager, otherwise false +func (am *DefaultAccountManager) HasConnectedChannel(peerID string) bool { + return am.peersUpdateManager.HasChannel(peerID) +} + var invalidDomainRegexp = regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) func isDomainValid(domain string) bool { diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index c586bd6c8..734785e30 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -35,14 +35,9 @@ func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg) *Pee func (h *PeersHandler) checkPeerStatus(peer *nbpeer.Peer) (*nbpeer.Peer, error) { peerToReturn := peer.Copy() if peer.Status.Connected { - statuses, err := h.accountManager.GetAllConnectedPeers() - if err != nil { - return peerToReturn, err - } - // Although we have online status in store we do not yet have an updated channel so have to show it as disconnected // This may happen after server restart when not all peers are yet connected - if _, connected := statuses[peerToReturn.ID]; !connected { + if !h.accountManager.HasConnectedChannel(peer.ID) { peerToReturn.Status.Connected = false } } diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index d13db447b..27978c487 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -81,8 +81,7 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { }, }, user, nil }, - - GetAllConnectedPeersFunc: func() (map[string]struct{}, error) { + HasConnectedChannelFunc: func(peerID string) bool { statuses := make(map[string]struct{}) for _, peer := range peers { if peer.ID == noUpdateChannelTestPeerID { @@ -90,7 +89,8 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { } statuses[peer.ID] = struct{}{} } - return statuses, nil + _, ok := statuses[peerID] + return ok }, }, claimsExtractor: jwtclaims.NewClaimsExtractor( diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index b200af0c3..a349b35a9 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -81,6 +81,7 @@ type MockAccountManager struct { SyncPeerFunc func(sync server.PeerSync) (*nbpeer.Peer, *server.NetworkMap, error) InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error GetAllConnectedPeersFunc func() (map[string]struct{}, error) + HasConnectedChannelFunc func(peerID string) bool GetExternalCacheManagerFunc func() server.ExternalCacheManager } @@ -622,6 +623,14 @@ func (am *MockAccountManager) GetAllConnectedPeers() (map[string]struct{}, error return nil, status.Errorf(codes.Unimplemented, "method GetAllConnectedPeers is not implemented") } +// HasconnectedChannel mocks HasConnectedChannel of the AccountManager interface +func (am *MockAccountManager) HasConnectedChannel(peerID string) bool { + if am.HasConnectedChannelFunc != nil { + return am.HasConnectedChannelFunc(peerID) + } + return false +} + // StoreEvent mocks StoreEvent of the AccountManager interface func (am *MockAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) { if am.StoreEventFunc != nil { diff --git a/management/server/telemetry/updatechannel_metrics.go b/management/server/telemetry/updatechannel_metrics.go index ede1671fc..7abe34354 100644 --- a/management/server/telemetry/updatechannel_metrics.go +++ b/management/server/telemetry/updatechannel_metrics.go @@ -11,48 +11,29 @@ import ( // UpdateChannelMetrics represents all metrics related to the UpdateChannel type UpdateChannelMetrics struct { - createChannelDurationMs syncint64.Histogram createChannelDurationMicro syncint64.Histogram - closeChannelDurationMs syncint64.Histogram closeChannelDurationMicro syncint64.Histogram - closeChannelsDurationMs syncint64.Histogram closeChannelsDurationMicro syncint64.Histogram closeChannels syncint64.Histogram - sendUpdateDurationMs syncint64.Histogram sendUpdateDurationMicro syncint64.Histogram - getAllConnectedPeersDurationMs syncint64.Histogram getAllConnectedPeersDurationMicro syncint64.Histogram getAllConnectedPeers syncint64.Histogram + hasChannelDurationMicro syncint64.Histogram ctx context.Context } // NewUpdateChannelMetrics creates an instance of UpdateChannel func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateChannelMetrics, error) { - createChannelDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.create.duration.ms") - if err != nil { - return nil, err - } - createChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.create.duration.micro") if err != nil { return nil, err } - closeChannelDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.close.one.duration.ms") - if err != nil { - return nil, err - } - closeChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.close.one.duration.micro") if err != nil { return nil, err } - closeChannelsDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.duration.ms") - if err != nil { - return nil, err - } - closeChannelsDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.duration.micro") if err != nil { return nil, err @@ -63,21 +44,11 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh return nil, err } - sendUpdateDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.send.duration.ms") - if err != nil { - return nil, err - } - sendUpdateDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.send.duration.micro") if err != nil { return nil, err } - getAllConnectedPeersDurationMs, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.duration.ms") - if err != nil { - return nil, err - } - getAllConnectedPeersDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.duration.micro") if err != nil { return nil, err @@ -88,19 +59,20 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh return nil, err } + hasChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.haschannel.duration.micro") + if err != nil { + return nil, err + } + return &UpdateChannelMetrics{ - createChannelDurationMs: createChannelDurationMs, createChannelDurationMicro: createChannelDurationMicro, - closeChannelDurationMs: closeChannelDurationMs, closeChannelDurationMicro: closeChannelDurationMicro, - closeChannelsDurationMs: closeChannelsDurationMs, closeChannelsDurationMicro: closeChannelsDurationMicro, closeChannels: closeChannels, - sendUpdateDurationMs: sendUpdateDurationMs, sendUpdateDurationMicro: sendUpdateDurationMicro, - getAllConnectedPeersDurationMs: getAllConnectedPeersDurationMs, getAllConnectedPeersDurationMicro: getAllConnectedPeersDurationMicro, getAllConnectedPeers: getAllConnectedPeers, + hasChannelDurationMicro: hasChannelDurationMicro, ctx: ctx, }, nil } @@ -108,19 +80,16 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh // CountCreateChannelDuration counts the duration of the CreateChannel method, // closed indicates if existing channel was closed before creation of a new one func (metrics *UpdateChannelMetrics) CountCreateChannelDuration(duration time.Duration, closed bool) { - metrics.createChannelDurationMs.Record(metrics.ctx, duration.Milliseconds(), attribute.Bool("closed", closed)) metrics.createChannelDurationMicro.Record(metrics.ctx, duration.Microseconds(), attribute.Bool("closed", closed)) } // CountCloseChannelDuration counts the duration of the CloseChannel method func (metrics *UpdateChannelMetrics) CountCloseChannelDuration(duration time.Duration) { - metrics.closeChannelDurationMs.Record(metrics.ctx, duration.Milliseconds()) metrics.closeChannelDurationMicro.Record(metrics.ctx, duration.Microseconds()) } // CountCloseChannelsDuration counts the duration of the CloseChannels method and the number of channels have been closed func (metrics *UpdateChannelMetrics) CountCloseChannelsDuration(duration time.Duration, channels int) { - metrics.closeChannelsDurationMs.Record(metrics.ctx, duration.Milliseconds()) metrics.closeChannelsDurationMicro.Record(metrics.ctx, duration.Microseconds()) metrics.closeChannels.Record(metrics.ctx, int64(channels)) } @@ -129,13 +98,16 @@ func (metrics *UpdateChannelMetrics) CountCloseChannelsDuration(duration time.Du // found indicates if peer had channel, dropped indicates if the message was dropped due channel buffer overload func (metrics *UpdateChannelMetrics) CountSendUpdateDuration(duration time.Duration, found, dropped bool) { attrs := []attribute.KeyValue{attribute.Bool("found", found), attribute.Bool("dropped", dropped)} - metrics.sendUpdateDurationMs.Record(metrics.ctx, duration.Milliseconds(), attrs...) metrics.sendUpdateDurationMicro.Record(metrics.ctx, duration.Microseconds(), attrs...) } // CountGetAllConnectedPeersDuration counts the duration of the GetAllConnectedPeers method and the number of peers have been returned func (metrics *UpdateChannelMetrics) CountGetAllConnectedPeersDuration(duration time.Duration, peers int) { - metrics.getAllConnectedPeersDurationMs.Record(metrics.ctx, duration.Milliseconds()) metrics.getAllConnectedPeersDurationMicro.Record(metrics.ctx, duration.Microseconds()) metrics.getAllConnectedPeers.Record(metrics.ctx, int64(peers)) } + +// CountHasChannelDuration counts the duration of the HasChannel method +func (metrics *UpdateChannelMetrics) CountHasChannelDuration(duration time.Duration) { + metrics.hasChannelDurationMicro.Record(metrics.ctx, duration.Microseconds()) +} diff --git a/management/server/updatechannel.go b/management/server/updatechannel.go index 0f14497fd..f760c5a75 100644 --- a/management/server/updatechannel.go +++ b/management/server/updatechannel.go @@ -151,3 +151,21 @@ func (p *PeersUpdateManager) GetAllConnectedPeers() map[string]struct{} { return m } + +// HasChannel returns true if peers has channel in update manager, otherwise false +func (p *PeersUpdateManager) HasChannel(peerID string) bool { + start := time.Now() + + p.channelsMux.Lock() + + defer func() { + p.channelsMux.Unlock() + if p.metrics != nil { + p.metrics.UpdateChannelMetrics().CountHasChannelDuration(time.Since(start)) + } + }() + + _, ok := p.peerChannels[peerID] + + return ok +}