From 170e842422a53e3f218f21bc59c2942c84e73c0e Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Thu, 12 Sep 2024 16:19:27 +0300 Subject: [PATCH] [management] Add accessible peers endpoint (#2579) * move accessible peer to separate endpoint in api doc Signed-off-by: bcmmbaga * add endpoint to get accessible peers Signed-off-by: bcmmbaga * Update management/server/http/api/openapi.yml Co-authored-by: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com> * Update management/server/http/api/openapi.yml Co-authored-by: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com> * Update management/server/http/peers_handler.go Co-authored-by: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com> --------- Signed-off-by: bcmmbaga Co-authored-by: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com> --- management/server/http/api/openapi.yml | 75 ++++++++++++++++---- management/server/http/api/types.gen.go | 93 +++++-------------------- management/server/http/handler.go | 1 + management/server/http/peers_handler.go | 81 +++++++++++++-------- 4 files changed, 133 insertions(+), 117 deletions(-) diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index d32ec6167..156310a9b 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -251,7 +251,7 @@ components: - name - ssh_enabled - login_expiration_enabled - PeerBase: + Peer: allOf: - $ref: '#/components/schemas/PeerMinimum' - type: object @@ -378,25 +378,40 @@ components: description: User ID of the user that enrolled this peer type: string example: google-oauth2|277474792786460067937 + os: + description: Peer's operating system and version + type: string + example: linux + country_code: + $ref: '#/components/schemas/CountryCode' + city_name: + $ref: '#/components/schemas/CityName' + geoname_id: + description: Unique identifier from the GeoNames database for a specific geographical location. + type: integer + example: 2643743 + connected: + description: Peer to Management connection status + type: boolean + example: true + last_seen: + description: Last time peer connected to Netbird's management service + type: string + format: date-time + example: "2023-05-05T10:05:26.420578Z" 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 + - os + - country_code + - city_name + - geoname_id + - connected + - last_seen PeerBatch: allOf: - - $ref: '#/components/schemas/PeerBase' + - $ref: '#/components/schemas/Peer' - type: object properties: accessible_peers_count: @@ -1806,6 +1821,38 @@ paths: "$ref": "#/components/responses/forbidden" '500': "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}/accessible-peers: + get: + summary: List accessible Peers + description: Returns a list of peers that the specified peer can connect to within the network. + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + responses: + '200': + description: A JSON Array of Accessible Peers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AccessiblePeer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" /api/setup-keys: get: summary: List all Setup Keys diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index a575ff54b..b219d38fd 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -152,18 +152,36 @@ const ( // AccessiblePeer defines model for AccessiblePeer. type AccessiblePeer struct { + // CityName Commonly used English name of the city + CityName CityName `json:"city_name"` + + // Connected Peer to Management connection status + Connected bool `json:"connected"` + + // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country + CountryCode CountryCode `json:"country_code"` + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud DnsLabel string `json:"dns_label"` + // GeonameId Unique identifier from the GeoNames database for a specific geographical location. + GeonameId int `json:"geoname_id"` + // Id Peer ID Id string `json:"id"` // Ip Peer's IP address Ip string `json:"ip"` + // LastSeen Last time peer connected to Netbird's management service + LastSeen time.Time `json:"last_seen"` + // Name Peer's hostname Name string `json:"name"` + // Os Peer's operating system and version + Os string `json:"os"` + // UserId User ID of the user that enrolled this peer UserId string `json:"user_id"` } @@ -490,81 +508,6 @@ type OSVersionCheck struct { // Peer defines model for Peer. 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"` - - // CityName Commonly used English name of the city - CityName CityName `json:"city_name"` - - // Connected Peer to Management connection status - Connected bool `json:"connected"` - - // ConnectionIp Peer's public connection IP address - ConnectionIp string `json:"connection_ip"` - - // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country - CountryCode CountryCode `json:"country_code"` - - // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud - DnsLabel string `json:"dns_label"` - - // GeonameId Unique identifier from the GeoNames database for a specific geographical location. - GeonameId int `json:"geoname_id"` - - // 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"` - - // KernelVersion Peer's operating system kernel version - KernelVersion string `json:"kernel_version"` - - // 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"` - - // SerialNumber System serial number - SerialNumber string `json:"serial_number"` - - // 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"` - - // UserId User ID of the user that enrolled this peer - UserId string `json:"user_id"` - - // Version Peer's daemon or cli version - Version string `json:"version"` -} - -// PeerBase defines model for PeerBase. -type PeerBase struct { // ApprovalRequired (Cloud only) Indicates whether peer needs approval ApprovalRequired bool `json:"approval_required"` diff --git a/management/server/http/handler.go b/management/server/http/handler.go index 366efa9b7..ef94f22b9 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -115,6 +115,7 @@ func (apiHandler *apiHandler) addPeersEndpoint() { apiHandler.Router.HandleFunc("/peers", peersHandler.GetAllPeers).Methods("GET", "OPTIONS") apiHandler.Router.HandleFunc("/peers/{peerId}", peersHandler.HandlePeer). Methods("GET", "PUT", "DELETE", "OPTIONS") + apiHandler.Router.HandleFunc("/peers/{peerId}/accessible-peers", peersHandler.GetAccessiblePeers).Methods("GET", "OPTIONS") } func (apiHandler *apiHandler) addUsersEndpoint() { diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 913d424d1..1487bbc39 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -71,12 +71,8 @@ func (h *PeersHandler) getPeer(ctx context.Context, account *server.Account, pee return } - customZone := account.GetPeersCustomZone(ctx, h.accountManager.GetDNSDomain()) - netMap := account.GetPeerNetworkMap(ctx, peerID, customZone, validPeers, nil) - accessiblePeers := toAccessiblePeers(netMap, dnsDomain) - _, valid := validPeers[peer.ID] - util.WriteJSONObject(ctx, w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers, valid)) + util.WriteJSONObject(ctx, w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, valid)) } func (h *PeersHandler) updatePeer(ctx context.Context, account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) { @@ -117,13 +113,9 @@ func (h *PeersHandler) updatePeer(ctx context.Context, account *server.Account, return } - customZone := account.GetPeersCustomZone(ctx, h.accountManager.GetDNSDomain()) - netMap := account.GetPeerNetworkMap(ctx, peerID, customZone, validPeers, nil) - accessiblePeers := toAccessiblePeers(netMap, dnsDomain) - _, valid := validPeers[peer.ID] - util.WriteJSONObject(r.Context(), w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers, valid)) + util.WriteJSONObject(r.Context(), w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, valid)) } func (h *PeersHandler) deletePeer(ctx context.Context, accountID, userID string, peerID string, w http.ResponseWriter) { @@ -220,32 +212,66 @@ func (h *PeersHandler) setApprovalRequiredFlag(respBody []*api.PeerBatch, approv } } +// GetAccessiblePeers returns a list of all peers that the specified peer can connect to within the network. +func (h *PeersHandler) GetAccessiblePeers(w http.ResponseWriter, r *http.Request) { + claims := h.claimsExtractor.FromRequestContext(r) + account, _, err := h.accountManager.GetAccountFromToken(r.Context(), claims) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + vars := mux.Vars(r) + peerID := vars["peerId"] + if len(peerID) == 0 { + util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid peer ID"), w) + return + } + + dnsDomain := h.accountManager.GetDNSDomain() + + validPeers, err := h.accountManager.GetValidatedPeers(account) + if err != nil { + log.WithContext(r.Context()).Errorf("failed to list approved peers: %v", err) + util.WriteError(r.Context(), fmt.Errorf("internal error"), w) + return + } + + customZone := account.GetPeersCustomZone(r.Context(), h.accountManager.GetDNSDomain()) + netMap := account.GetPeerNetworkMap(r.Context(), peerID, customZone, validPeers, nil) + + util.WriteJSONObject(r.Context(), w, toAccessiblePeers(netMap, dnsDomain)) +} + 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) + accessiblePeers = append(accessiblePeers, peerToAccessiblePeer(p, dnsDomain)) } 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) + accessiblePeers = append(accessiblePeers, peerToAccessiblePeer(p, dnsDomain)) } + return accessiblePeers } +func peerToAccessiblePeer(peer *nbpeer.Peer, dnsDomain string) api.AccessiblePeer { + return api.AccessiblePeer{ + CityName: peer.Location.CityName, + Connected: peer.Status.Connected, + CountryCode: peer.Location.CountryCode, + DnsLabel: fqdn(peer, dnsDomain), + GeonameId: int(peer.Location.GeoNameID), + Id: peer.ID, + Ip: peer.IP.String(), + LastSeen: peer.Status.LastSeen, + Name: peer.Name, + Os: peer.Meta.OS, + UserId: peer.UserID, + } +} + func toGroupsInfo(groups map[string]*nbgroup.Group, peerID string) []api.GroupMinimum { var groupsInfo []api.GroupMinimum groupsChecked := make(map[string]struct{}) @@ -270,7 +296,7 @@ func toGroupsInfo(groups map[string]*nbgroup.Group, peerID string) []api.GroupMi return groupsInfo } -func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer, approved bool) *api.Peer { +func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, approved bool) *api.Peer { osVersion := peer.Meta.OSVersion if osVersion == "" { osVersion = peer.Meta.Core @@ -296,7 +322,6 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, - AccessiblePeers: accessiblePeer, ApprovalRequired: !approved, CountryCode: peer.Location.CountryCode, CityName: peer.Location.CityName,