mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 20:39:08 +02:00
The dashboard needs to know which peers have opted out of remote jobs so it can reflect that in the UI, the same way it surfaces the SSH server flag. Report RemoteJobsAllowed as peer system-info: the client sets it on the reported flags (like ServerSSHAllowed), the proto Flags message carries it, and management decodes it onto the peer meta and exposes it on the peers API as remote_jobs_allowed. Kept out of the components/network-map path: unlike ServerSSHAllowed it does not participate in firewall-rule calculation, so it only rides the reporting flags, not ComponentPeer.
725 lines
23 KiB
Go
725 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(netMap, account.Peers, 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 rehydrates the calculated map's component peers into the
|
|
// account's full peer objects, which carry the location/status/meta fields
|
|
// the API response needs.
|
|
func toAccessiblePeers(netMap *types.NetworkMap, accountPeers map[string]*nbpeer.Peer, dnsDomain string) []api.AccessiblePeer {
|
|
accessiblePeers := make([]api.AccessiblePeer, 0, len(netMap.Peers)+len(netMap.OfflinePeers))
|
|
add := func(peers []*types.ComponentPeer) {
|
|
for _, p := range peers {
|
|
if peer := accountPeers[p.ID]; peer != nil {
|
|
accessiblePeers = append(accessiblePeers, peerToAccessiblePeer(peer, dnsDomain))
|
|
}
|
|
}
|
|
}
|
|
add(netMap.Peers)
|
|
add(netMap.OfflinePeers)
|
|
|
|
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
|
|
}
|