mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-13 10:19:07 +02:00
This introduces a disabled-by-default allow-remote-jobs setting that controls whether the management server may run jobs (such as debug bundles) on a peer. The flag propagates end to end: through client configuration, the daemon SetConfig and Login requests, authentication, and system info, up to management, where it is stored on the peer and exposed on the peers API as remote_jobs_allowed. The client refuses any management-requested job unless the peer has opted in. Because enabling remote jobs crosses the user-to-root boundary, turning it on requires privilege, mirroring the SSH-server gate. Administrators can enforce the setting through MDM policy on both macOS and Windows, and MDM can also override the debug-bundle upload URL. The change ships policy documentation and generated profile templates, and adds configuration, conflict, and enforcement tests covering the opt-in, privilege, and MDM paths.
727 lines
23 KiB
Go
727 lines
23 KiB
Go
package peers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/netip"
|
|
|
|
"github.com/gorilla/mux"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/dns"
|
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
|
"github.com/netbirdio/netbird/management/server/account"
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
|
"github.com/netbirdio/netbird/management/server/groups"
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
"github.com/netbirdio/netbird/management/server/permissions"
|
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
|
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
"github.com/netbirdio/netbird/shared/management/http/api"
|
|
"github.com/netbirdio/netbird/shared/management/http/util"
|
|
"github.com/netbirdio/netbird/shared/management/status"
|
|
)
|
|
|
|
// Handler is a handler that returns peers of the account
|
|
type Handler struct {
|
|
accountManager account.Manager
|
|
permissionsManager permissions.Manager
|
|
networkMapController network_map.Controller
|
|
}
|
|
|
|
func AddEndpoints(accountManager account.Manager, router *mux.Router, networkMapController network_map.Controller, permissionsManager permissions.Manager) {
|
|
peersHandler := NewHandler(accountManager, networkMapController, permissionsManager)
|
|
router.HandleFunc("/peers", peersHandler.GetAllPeers).Methods("GET", "OPTIONS")
|
|
router.HandleFunc("/peers/{peerId}", peersHandler.HandlePeer).
|
|
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
|
router.HandleFunc("/peers/{peerId}/accessible-peers", peersHandler.GetAccessiblePeers).Methods("GET", "OPTIONS")
|
|
router.HandleFunc("/peers/{peerId}/temporary-access", peersHandler.CreateTemporaryAccess).Methods("POST", "OPTIONS")
|
|
router.HandleFunc("/peers/{peerId}/jobs", peersHandler.ListJobs).Methods("GET", "OPTIONS")
|
|
router.HandleFunc("/peers/{peerId}/jobs", peersHandler.CreateJob).Methods("POST", "OPTIONS")
|
|
router.HandleFunc("/peers/{peerId}/jobs/{jobId}", peersHandler.GetJob).Methods("GET", "OPTIONS")
|
|
}
|
|
|
|
// NewHandler creates a new peers Handler
|
|
func NewHandler(accountManager account.Manager, networkMapController network_map.Controller, permissionsManager permissions.Manager) *Handler {
|
|
return &Handler{
|
|
accountManager: accountManager,
|
|
networkMapController: networkMapController,
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) CreateJob(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(ctx)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
peerID := vars["peerId"]
|
|
|
|
req := &api.JobRequest{}
|
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
|
return
|
|
}
|
|
|
|
job, err := types.NewJob(userAuth.UserId, userAuth.AccountId, peerID, req)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
if err := h.accountManager.CreatePeerJob(ctx, userAuth.AccountId, peerID, userAuth.UserId, job); err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
resp, err := toSingleJobResponse(job)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
util.WriteJSONObject(ctx, w, resp)
|
|
}
|
|
|
|
func (h *Handler) ListJobs(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(ctx)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
peerID := vars["peerId"]
|
|
|
|
jobs, err := h.accountManager.GetAllPeerJobs(ctx, userAuth.AccountId, userAuth.UserId, peerID)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
respBody := make([]*api.JobResponse, 0, len(jobs))
|
|
for _, job := range jobs {
|
|
resp, err := toSingleJobResponse(job)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
respBody = append(respBody, resp)
|
|
}
|
|
|
|
util.WriteJSONObject(ctx, w, respBody)
|
|
}
|
|
|
|
func (h *Handler) GetJob(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(ctx)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
peerID := vars["peerId"]
|
|
jobID := vars["jobId"]
|
|
|
|
job, err := h.accountManager.GetPeerJobByID(ctx, userAuth.AccountId, userAuth.UserId, peerID, jobID)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
resp, err := toSingleJobResponse(job)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
util.WriteJSONObject(ctx, w, resp)
|
|
}
|
|
|
|
func (h *Handler) getPeer(ctx context.Context, accountID, peerID, userID string, w http.ResponseWriter) {
|
|
peer, err := h.accountManager.GetPeer(ctx, accountID, peerID, userID)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if peer.ProxyMeta.Embedded {
|
|
util.WriteError(ctx, status.Errorf(status.InvalidArgument, "not allowed to read peer"), w)
|
|
return
|
|
}
|
|
|
|
settings, err := h.accountManager.GetAccountSettings(ctx, accountID, activity.SystemInitiator)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
dnsDomain := h.networkMapController.GetDNSDomain(settings)
|
|
|
|
grps, _ := h.accountManager.GetPeerGroups(ctx, accountID, peerID)
|
|
grpsInfoMap := groups.ToGroupsInfoMap(grps, 0)
|
|
|
|
validPeers, invalidPeers, err := h.accountManager.GetValidatedPeers(ctx, accountID)
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("failed to list approved peers: %v", err)
|
|
util.WriteError(ctx, fmt.Errorf("internal error"), w)
|
|
return
|
|
}
|
|
|
|
_, valid := validPeers[peer.ID]
|
|
reason := invalidPeers[peer.ID]
|
|
|
|
util.WriteJSONObject(ctx, w, toSinglePeerResponse(peer, grpsInfoMap[peerID], dnsDomain, valid, reason))
|
|
}
|
|
|
|
func (h *Handler) updatePeer(ctx context.Context, accountID, userID, peerID string, w http.ResponseWriter, r *http.Request) {
|
|
req := &api.PeerRequest{}
|
|
err := json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
|
return
|
|
}
|
|
|
|
update := &nbpeer.Peer{
|
|
ID: peerID,
|
|
SSHEnabled: req.SshEnabled,
|
|
Name: req.Name,
|
|
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
|
|
|
InactivityExpirationEnabled: req.InactivityExpirationEnabled,
|
|
}
|
|
|
|
if req.ApprovalRequired != nil {
|
|
// todo: looks like that we reset all status property, is it right?
|
|
update.Status = &nbpeer.PeerStatus{
|
|
RequiresApproval: *req.ApprovalRequired,
|
|
}
|
|
}
|
|
|
|
if req.Ip != nil {
|
|
addr, err := netip.ParseAddr(*req.Ip)
|
|
if err != nil {
|
|
util.WriteError(ctx, status.Errorf(status.InvalidArgument, "invalid IP address %s: %v", *req.Ip, err), w)
|
|
return
|
|
}
|
|
|
|
if err = h.accountManager.UpdatePeerIP(ctx, accountID, userID, peerID, addr); err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.Ipv6 != nil {
|
|
v6Addr, err := parseIPv6(req.Ipv6)
|
|
if err != nil {
|
|
util.WriteError(ctx, status.Errorf(status.InvalidArgument, "%v", err), w)
|
|
return
|
|
}
|
|
if err = h.accountManager.UpdatePeerIPv6(ctx, accountID, userID, peerID, v6Addr); err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
peer, err := h.accountManager.UpdatePeer(ctx, accountID, userID, update)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
settings, err := h.accountManager.GetAccountSettings(ctx, accountID, activity.SystemInitiator)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
dnsDomain := h.networkMapController.GetDNSDomain(settings)
|
|
|
|
peerGroups, err := h.accountManager.GetPeerGroups(ctx, accountID, peer.ID)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
grpsInfoMap := groups.ToGroupsInfoMap(peerGroups, 0)
|
|
|
|
validPeers, invalidPeers, err := h.accountManager.GetValidatedPeers(ctx, accountID)
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("failed to get validated peers: %v", err)
|
|
util.WriteError(ctx, fmt.Errorf("internal error"), w)
|
|
return
|
|
}
|
|
|
|
_, valid := validPeers[peer.ID]
|
|
reason := invalidPeers[peer.ID]
|
|
|
|
util.WriteJSONObject(r.Context(), w, toSinglePeerResponse(peer, grpsInfoMap[peerID], dnsDomain, valid, reason))
|
|
}
|
|
|
|
func (h *Handler) deletePeer(ctx context.Context, accountID, userID string, peerID string, w http.ResponseWriter) {
|
|
err := h.accountManager.DeletePeer(ctx, accountID, peerID, userID)
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("failed to delete peer: %v", err)
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
util.WriteJSONObject(ctx, w, util.EmptyObject{})
|
|
}
|
|
|
|
// HandlePeer handles all peer requests for GET, PUT and DELETE operations
|
|
func (h *Handler) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
vars := mux.Vars(r)
|
|
peerID := vars["peerId"]
|
|
if len(peerID) == 0 {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid peer ID"), w)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodDelete:
|
|
h.deletePeer(r.Context(), accountID, userID, peerID, w)
|
|
return
|
|
case http.MethodGet:
|
|
h.getPeer(r.Context(), accountID, peerID, userID, w)
|
|
return
|
|
case http.MethodPut:
|
|
h.updatePeer(r.Context(), accountID, userID, peerID, w, r)
|
|
return
|
|
default:
|
|
util.WriteError(r.Context(), status.Errorf(status.NotFound, "unknown METHOD"), w)
|
|
}
|
|
}
|
|
|
|
// GetAllPeers returns a list of all peers associated with a provided account
|
|
func (h *Handler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
nameFilter := r.URL.Query().Get("name")
|
|
ipFilter := r.URL.Query().Get("ip")
|
|
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
|
|
peers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, nameFilter, ipFilter)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
settings, err := h.accountManager.GetAccountSettings(r.Context(), accountID, activity.SystemInitiator)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
dnsDomain := h.networkMapController.GetDNSDomain(settings)
|
|
|
|
grps, _ := h.accountManager.GetAllGroups(r.Context(), accountID, userID)
|
|
|
|
grpsInfoMap := groups.ToGroupsInfoMap(grps, len(peers))
|
|
respBody := make([]*api.PeerBatch, 0, len(peers))
|
|
for _, peer := range peers {
|
|
if peer.ProxyMeta.Embedded {
|
|
continue
|
|
}
|
|
respBody = append(respBody, toPeerListItemResponse(peer, grpsInfoMap[peer.ID], dnsDomain, 0))
|
|
}
|
|
|
|
validPeersMap, invalidPeersMap, err := h.accountManager.GetValidatedPeers(r.Context(), accountID)
|
|
if err != nil {
|
|
log.WithContext(r.Context()).Errorf("failed to get validated peers: %v", err)
|
|
util.WriteError(r.Context(), fmt.Errorf("internal error"), w)
|
|
return
|
|
}
|
|
h.setApprovalRequiredFlag(respBody, validPeersMap, invalidPeersMap)
|
|
|
|
util.WriteJSONObject(r.Context(), w, respBody)
|
|
}
|
|
|
|
func (h *Handler) setApprovalRequiredFlag(respBody []*api.PeerBatch, validPeersMap map[string]struct{}, invalidPeersMap map[string]string) {
|
|
for _, peer := range respBody {
|
|
_, ok := validPeersMap[peer.Id]
|
|
if !ok {
|
|
peer.ApprovalRequired = true
|
|
|
|
reason := invalidPeersMap[peer.Id]
|
|
peer.DisapprovalReason = &reason
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseIPv6(s *string) (netip.Addr, error) {
|
|
if s == nil {
|
|
return netip.Addr{}, fmt.Errorf("IPv6 address is nil")
|
|
}
|
|
addr, err := netip.ParseAddr(*s)
|
|
if err != nil {
|
|
return netip.Addr{}, fmt.Errorf("invalid IPv6 address %s: %w", *s, err)
|
|
}
|
|
addr = addr.Unmap()
|
|
if !addr.Is6() {
|
|
return netip.Addr{}, fmt.Errorf("address %s is not IPv6", *s)
|
|
}
|
|
return addr, nil
|
|
}
|
|
|
|
// GetAccessiblePeers returns a list of all peers that the specified peer can connect to within the network.
|
|
func (h *Handler) GetAccessiblePeers(w http.ResponseWriter, r *http.Request) {
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
|
|
vars := mux.Vars(r)
|
|
peerID := vars["peerId"]
|
|
if len(peerID) == 0 {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid peer ID"), w)
|
|
return
|
|
}
|
|
|
|
user, err := h.accountManager.GetUserByID(r.Context(), userID)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
allowed, ctx, err := h.permissionsManager.ValidateUserPermissions(r.Context(), accountID, userID, modules.Peers, operations.Read)
|
|
if err != nil {
|
|
util.WriteError(ctx, status.NewPermissionValidationError(err), w)
|
|
return
|
|
}
|
|
|
|
account, err := h.accountManager.GetAccountByID(ctx, accountID, activity.SystemInitiator)
|
|
if err != nil {
|
|
util.WriteError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if !allowed && !userAuth.IsChild {
|
|
if account.Settings.RegularUsersViewBlocked {
|
|
util.WriteJSONObject(ctx, w, []api.AccessiblePeer{})
|
|
return
|
|
}
|
|
|
|
peer, ok := account.Peers[peerID]
|
|
if !ok {
|
|
util.WriteError(ctx, status.Errorf(status.NotFound, "peer not found"), w)
|
|
return
|
|
}
|
|
|
|
if peer.UserID != user.Id {
|
|
util.WriteJSONObject(ctx, w, []api.AccessiblePeer{})
|
|
return
|
|
}
|
|
}
|
|
|
|
validPeers, _, err := h.accountManager.GetValidatedPeers(ctx, accountID)
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("failed to list approved peers: %v", err)
|
|
util.WriteError(ctx, fmt.Errorf("internal error"), w)
|
|
return
|
|
}
|
|
|
|
dnsDomain := h.networkMapController.GetDNSDomain(account.Settings)
|
|
|
|
netMap := account.GetPeerNetworkMapFromComponents(ctx, peerID, dns.CustomZone{}, nil, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil, account.GetActiveGroupUsers())
|
|
|
|
util.WriteJSONObject(ctx, w, toAccessiblePeers(account.Peers, netMap, dnsDomain))
|
|
}
|
|
|
|
func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request) {
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
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
|
|
}
|
|
|
|
var req api.PeerTemporaryAccessRequest
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
|
return
|
|
}
|
|
|
|
newPeer := &nbpeer.Peer{}
|
|
newPeer.FromAPITemporaryAccessRequest(&req)
|
|
|
|
targetPeer, err := h.accountManager.GetPeer(r.Context(), userAuth.AccountId, peerID, userAuth.UserId)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
peer, _, _, _, err := h.accountManager.AddPeer(r.Context(), userAuth.AccountId, "", userAuth.UserId, newPeer, true)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
for _, rule := range req.Rules {
|
|
protocol, portRange, err := types.ParseRuleString(rule)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
policy := &types.Policy{
|
|
AccountID: userAuth.AccountId,
|
|
Description: "Temporary access policy for peer " + peer.Name,
|
|
Name: "Temporary access policy for peer " + peer.Name,
|
|
Enabled: true,
|
|
Rules: []*types.PolicyRule{{
|
|
Name: "Temporary access rule",
|
|
Description: "Temporary access rule",
|
|
Enabled: true,
|
|
Action: types.PolicyTrafficActionAccept,
|
|
SourceResource: types.Resource{
|
|
Type: types.ResourceTypePeer,
|
|
ID: peer.ID,
|
|
},
|
|
DestinationResource: types.Resource{
|
|
Type: types.ResourceTypePeer,
|
|
ID: targetPeer.ID,
|
|
},
|
|
Bidirectional: false,
|
|
Protocol: protocol,
|
|
PortRanges: []types.RulePortRange{portRange},
|
|
}},
|
|
}
|
|
if protocol == types.PolicyRuleProtocolNetbirdSSH {
|
|
policy.Rules[0].AuthorizedUser = userAuth.UserId
|
|
}
|
|
|
|
_, err = h.accountManager.SavePolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, true)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
resp := &api.PeerTemporaryAccessResponse{
|
|
Id: peer.ID,
|
|
Name: peer.Name,
|
|
Rules: req.Rules,
|
|
}
|
|
|
|
util.WriteJSONObject(r.Context(), w, resp)
|
|
}
|
|
|
|
// toAccessiblePeers resolves the twin peers in netMap back to the full account
|
|
// peers (by ID) so the API response keeps Status/Name/OS/GeoNameID, which the
|
|
// slim netmap twins intentionally don't carry.
|
|
func toAccessiblePeers(accountPeers map[string]*nbpeer.Peer, netMap *types.NetworkMap, dnsDomain string) []api.AccessiblePeer {
|
|
accessiblePeers := make([]api.AccessiblePeer, 0, len(netMap.Peers)+len(netMap.OfflinePeers))
|
|
appendByID := func(id string) {
|
|
if p, ok := accountPeers[id]; ok && p != nil {
|
|
accessiblePeers = append(accessiblePeers, peerToAccessiblePeer(p, dnsDomain))
|
|
}
|
|
}
|
|
for _, p := range netMap.Peers {
|
|
appendByID(p.ID)
|
|
}
|
|
for _, p := range netMap.OfflinePeers {
|
|
appendByID(p.ID)
|
|
}
|
|
|
|
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(),
|
|
Ipv6: peerIPv6String(peer),
|
|
LastSeen: peer.Status.LastSeen,
|
|
Name: peer.Name,
|
|
Os: peer.Meta.OS,
|
|
UserId: peer.UserID,
|
|
}
|
|
}
|
|
|
|
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, approved bool, reason string) *api.Peer {
|
|
osVersion := peer.Meta.OSVersion
|
|
if osVersion == "" {
|
|
osVersion = peer.Meta.Core
|
|
}
|
|
|
|
apiPeer := &api.Peer{
|
|
CreatedAt: peer.CreatedAt,
|
|
Id: peer.ID,
|
|
Name: peer.Name,
|
|
Ip: peer.IP.String(),
|
|
Ipv6: peerIPv6String(peer),
|
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
|
Connected: peer.Status.Connected,
|
|
LastSeen: peer.Status.LastSeen,
|
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
|
KernelVersion: peer.Meta.KernelVersion,
|
|
GeonameId: int(peer.Location.GeoNameID),
|
|
Version: peer.Meta.WtVersion,
|
|
Groups: groupsInfo,
|
|
SshEnabled: peer.SSHEnabled,
|
|
Hostname: peer.Meta.Hostname,
|
|
UserId: peer.UserID,
|
|
UiVersion: peer.Meta.UIVersion,
|
|
DnsLabel: fqdn(peer, dnsDomain),
|
|
ExtraDnsLabels: fqdnList(peer.ExtraDNSLabels, dnsDomain),
|
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
|
LastLogin: peer.GetLastLogin(),
|
|
LoginExpired: peer.Status.LoginExpired,
|
|
ApprovalRequired: !approved,
|
|
CountryCode: peer.Location.CountryCode,
|
|
CityName: peer.Location.CityName,
|
|
SerialNumber: peer.Meta.SystemSerialNumber,
|
|
InactivityExpirationEnabled: peer.InactivityExpirationEnabled,
|
|
Ephemeral: peer.Ephemeral,
|
|
LocalFlags: &api.PeerLocalFlags{
|
|
BlockInbound: &peer.Meta.Flags.BlockInbound,
|
|
BlockLanAccess: &peer.Meta.Flags.BlockLANAccess,
|
|
DisableClientRoutes: &peer.Meta.Flags.DisableClientRoutes,
|
|
DisableDns: &peer.Meta.Flags.DisableDNS,
|
|
DisableFirewall: &peer.Meta.Flags.DisableFirewall,
|
|
DisableServerRoutes: &peer.Meta.Flags.DisableServerRoutes,
|
|
LazyConnectionEnabled: &peer.Meta.Flags.LazyConnectionEnabled,
|
|
RosenpassEnabled: &peer.Meta.Flags.RosenpassEnabled,
|
|
RosenpassPermissive: &peer.Meta.Flags.RosenpassPermissive,
|
|
ServerSshAllowed: &peer.Meta.Flags.ServerSSHAllowed,
|
|
RemoteJobsAllowed: &peer.Meta.Flags.RemoteJobsAllowed,
|
|
},
|
|
}
|
|
|
|
if !approved {
|
|
apiPeer.DisapprovalReason = &reason
|
|
}
|
|
|
|
return apiPeer
|
|
}
|
|
|
|
func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch {
|
|
osVersion := peer.Meta.OSVersion
|
|
if osVersion == "" {
|
|
osVersion = peer.Meta.Core
|
|
}
|
|
return &api.PeerBatch{
|
|
CreatedAt: peer.CreatedAt,
|
|
Id: peer.ID,
|
|
Name: peer.Name,
|
|
Ip: peer.IP.String(),
|
|
Ipv6: peerIPv6String(peer),
|
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
|
Connected: peer.Status.Connected,
|
|
LastSeen: peer.Status.LastSeen,
|
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
|
KernelVersion: peer.Meta.KernelVersion,
|
|
GeonameId: int(peer.Location.GeoNameID),
|
|
Version: peer.Meta.WtVersion,
|
|
Groups: groupsInfo,
|
|
SshEnabled: peer.SSHEnabled,
|
|
Hostname: peer.Meta.Hostname,
|
|
UserId: peer.UserID,
|
|
UiVersion: peer.Meta.UIVersion,
|
|
DnsLabel: fqdn(peer, dnsDomain),
|
|
ExtraDnsLabels: fqdnList(peer.ExtraDNSLabels, dnsDomain),
|
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
|
LastLogin: peer.GetLastLogin(),
|
|
LoginExpired: peer.Status.LoginExpired,
|
|
AccessiblePeersCount: accessiblePeersCount,
|
|
CountryCode: peer.Location.CountryCode,
|
|
CityName: peer.Location.CityName,
|
|
SerialNumber: peer.Meta.SystemSerialNumber,
|
|
InactivityExpirationEnabled: peer.InactivityExpirationEnabled,
|
|
Ephemeral: peer.Ephemeral,
|
|
LocalFlags: &api.PeerLocalFlags{
|
|
BlockInbound: &peer.Meta.Flags.BlockInbound,
|
|
BlockLanAccess: &peer.Meta.Flags.BlockLANAccess,
|
|
DisableClientRoutes: &peer.Meta.Flags.DisableClientRoutes,
|
|
DisableDns: &peer.Meta.Flags.DisableDNS,
|
|
DisableFirewall: &peer.Meta.Flags.DisableFirewall,
|
|
DisableServerRoutes: &peer.Meta.Flags.DisableServerRoutes,
|
|
LazyConnectionEnabled: &peer.Meta.Flags.LazyConnectionEnabled,
|
|
RosenpassEnabled: &peer.Meta.Flags.RosenpassEnabled,
|
|
RosenpassPermissive: &peer.Meta.Flags.RosenpassPermissive,
|
|
ServerSshAllowed: &peer.Meta.Flags.ServerSSHAllowed,
|
|
RemoteJobsAllowed: &peer.Meta.Flags.RemoteJobsAllowed,
|
|
},
|
|
}
|
|
}
|
|
|
|
func toSingleJobResponse(job *types.Job) (*api.JobResponse, error) {
|
|
workload, err := job.BuildWorkloadResponse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var failed *string
|
|
if job.FailedReason != "" {
|
|
failed = &job.FailedReason
|
|
}
|
|
|
|
return &api.JobResponse{
|
|
Id: job.ID,
|
|
CreatedAt: job.CreatedAt,
|
|
CompletedAt: job.CompletedAt,
|
|
TriggeredBy: job.TriggeredBy,
|
|
Status: api.JobResponseStatus(job.Status),
|
|
FailedReason: failed,
|
|
Workload: *workload,
|
|
}, nil
|
|
}
|
|
|
|
func fqdn(peer *nbpeer.Peer, dnsDomain string) string {
|
|
fqdn := peer.FQDN(dnsDomain)
|
|
if fqdn == "" {
|
|
return peer.DNSLabel
|
|
} else {
|
|
return fqdn
|
|
}
|
|
}
|
|
func fqdnList(extraLabels []string, dnsDomain string) []string {
|
|
fqdnList := make([]string, 0, len(extraLabels))
|
|
for _, label := range extraLabels {
|
|
fqdn := fmt.Sprintf("%s.%s", label, dnsDomain)
|
|
fqdnList = append(fqdnList, fqdn)
|
|
}
|
|
return fqdnList
|
|
}
|
|
|
|
func peerIPv6String(peer *nbpeer.Peer) *string {
|
|
if !peer.IPv6.IsValid() {
|
|
return nil
|
|
}
|
|
s := peer.IPv6.String()
|
|
return &s
|
|
}
|