mirror of
https://github.com/netbirdio/netbird.git
synced 2025-04-16 15:38:42 +02:00
Extend peer http endpoint (#94)
* feature: add peer GET and DELETE API methods * refactor: extract peer business logic to a separate file * refactor: extract peer business logic to a separate file * feature: add peer update HTTP endpoint * chore: fill peer new fields * merge with main * refactor: HTTP methods according to standards * chore: setup keys POST endpoint without ID
This commit is contained in:
parent
6869b48905
commit
95845c88fe
@ -6,7 +6,6 @@ import (
|
|||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -25,17 +24,6 @@ type Account struct {
|
|||||||
Peers map[string]*Peer
|
Peers map[string]*Peer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer represents a machine connected to the network.
|
|
||||||
// The Peer is a Wireguard peer identified by a public key
|
|
||||||
type Peer struct {
|
|
||||||
// Wireguard public key
|
|
||||||
Key string
|
|
||||||
// A setup key this peer was registered with
|
|
||||||
SetupKey string
|
|
||||||
// IP address of the Peer
|
|
||||||
IP net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewManager creates a new AccountManager with a provided Store
|
// NewManager creates a new AccountManager with a provided Store
|
||||||
func NewManager(store Store) *AccountManager {
|
func NewManager(store Store) *AccountManager {
|
||||||
return &AccountManager{
|
return &AccountManager{
|
||||||
@ -44,40 +32,6 @@ func NewManager(store Store) *AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeer returns a peer from a Store
|
|
||||||
func (manager *AccountManager) GetPeer(peerKey string) (*Peer, error) {
|
|
||||||
manager.mux.Lock()
|
|
||||||
defer manager.mux.Unlock()
|
|
||||||
|
|
||||||
peer, err := manager.Store.GetPeer(peerKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return peer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeersForAPeer returns a list of peers available for a given peer (key)
|
|
||||||
// Effectively all the peers of the original peer's account except for the peer itself
|
|
||||||
func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) {
|
|
||||||
manager.mux.Lock()
|
|
||||||
defer manager.mux.Unlock()
|
|
||||||
|
|
||||||
account, err := manager.Store.GetPeerAccount(peerKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "Invalid peer key %s", peerKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res []*Peer
|
|
||||||
for _, peer := range account.Peers {
|
|
||||||
if peer.Key != peerKey {
|
|
||||||
res = append(res, peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
||||||
func (manager *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
|
func (manager *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
|
||||||
manager.mux.Lock()
|
manager.mux.Lock()
|
||||||
@ -228,65 +182,6 @@ func (manager *AccountManager) createAccount(accountId string) (*Account, error)
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPeer adds a new peer to the Store.
|
|
||||||
// Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated
|
|
||||||
// will be returned, meaning the key is invalid
|
|
||||||
// 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).
|
|
||||||
// If the specified setupKey is empty then a new Account will be created //todo remove this part
|
|
||||||
func (manager *AccountManager) AddPeer(setupKey string, peerKey string) (*Peer, error) {
|
|
||||||
manager.mux.Lock()
|
|
||||||
defer manager.mux.Unlock()
|
|
||||||
|
|
||||||
upperKey := strings.ToUpper(setupKey)
|
|
||||||
|
|
||||||
var account *Account
|
|
||||||
var err error
|
|
||||||
var sk *SetupKey
|
|
||||||
if len(upperKey) == 0 {
|
|
||||||
// Empty setup key, create a new account for it.
|
|
||||||
account, sk = newAccount()
|
|
||||||
} else {
|
|
||||||
account, err = manager.Store.GetAccountBySetupKey(upperKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
sk = getAccountSetupKeyByKey(account, upperKey)
|
|
||||||
if sk == nil {
|
|
||||||
// shouldn't happen actually
|
|
||||||
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sk.IsValid() {
|
|
||||||
return nil, status.Errorf(codes.FailedPrecondition, "setup key was expired or overused %s", upperKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var takenIps []net.IP
|
|
||||||
for _, peer := range account.Peers {
|
|
||||||
takenIps = append(takenIps, peer.IP)
|
|
||||||
}
|
|
||||||
|
|
||||||
network := account.Network
|
|
||||||
nextIp, _ := AllocatePeerIP(network.Net, takenIps)
|
|
||||||
|
|
||||||
newPeer := &Peer{
|
|
||||||
Key: peerKey,
|
|
||||||
SetupKey: sk.Key,
|
|
||||||
IP: nextIp,
|
|
||||||
}
|
|
||||||
|
|
||||||
account.Peers[newPeer.Key] = newPeer
|
|
||||||
account.SetupKeys[sk.Key] = sk.IncrementUsage()
|
|
||||||
err = manager.Store.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed adding peer")
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPeer, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
|
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
|
||||||
func newAccountWithId(accountId string) (*Account, *SetupKey) {
|
func newAccountWithId(accountId string) (*Account, *SetupKey) {
|
||||||
|
|
||||||
|
@ -83,6 +83,51 @@ func (s *FileStore) persist(file string) error {
|
|||||||
return util.WriteJson(file, s)
|
return util.WriteJson(file, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SavePeer saves updated peer
|
||||||
|
func (s *FileStore) SavePeer(accountId string, peer *Peer) error {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := s.GetAccount(accountId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Peers[peer.Key] = peer
|
||||||
|
err = s.persist(s.storeFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePeer deletes peer from the Store
|
||||||
|
func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := s.GetAccount(accountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer := account.Peers[peerKey]
|
||||||
|
if peer == nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "peer not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(account.Peers, peerKey)
|
||||||
|
delete(s.PeerKeyId2AccountId, peerKey)
|
||||||
|
|
||||||
|
err = s.persist(s.storeFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetPeer returns a peer from a Store
|
// GetPeer returns a peer from a Store
|
||||||
func (s *FileStore) GetPeer(peerKey string) (*Peer, error) {
|
func (s *FileStore) GetPeer(peerKey string) (*Peer, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
|
@ -2,24 +2,30 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/wiretrustee/wiretrustee/management/server"
|
"github.com/wiretrustee/wiretrustee/management/server"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Peers is a handler that returns peers of the account
|
//Peers is a handler that returns peers of the account
|
||||||
type Peers struct {
|
type Peers struct {
|
||||||
accountManager *server.AccountManager
|
accountManager *server.AccountManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerResponse is a response sent to the client
|
//PeerResponse is a response sent to the client
|
||||||
type PeerResponse struct {
|
type PeerResponse struct {
|
||||||
Name string
|
Name string
|
||||||
IP string
|
IP string
|
||||||
Connected bool
|
Connected bool
|
||||||
LastSeen time.Time
|
LastSeen time.Time
|
||||||
Os string
|
OS string
|
||||||
|
}
|
||||||
|
|
||||||
|
//PeerRequest is a request sent by the client
|
||||||
|
type PeerRequest struct {
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPeers(accountManager *server.AccountManager) *Peers {
|
func NewPeers(accountManager *server.AccountManager) *Peers {
|
||||||
@ -28,6 +34,63 @@ func NewPeers(accountManager *server.AccountManager) *Peers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Peers) updatePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := &PeerRequest{}
|
||||||
|
peerIp := peer.IP
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peer, err = h.accountManager.RenamePeer(accountId, peer.Key, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed updating peer %s under account %s %v", peerIp, accountId, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONObject(w, toPeerResponse(peer))
|
||||||
|
}
|
||||||
|
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed deleteing peer %s, %v", peer.IP, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
accountId := extractAccountIdFromRequestContext(r)
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
peerId := vars["id"] //effectively peer IP address
|
||||||
|
if len(peerId) == 0 {
|
||||||
|
http.Error(w, "invalid peer Id", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(accountId, peerId)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "peer not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodDelete:
|
||||||
|
h.deletePeer(accountId, peer, w, r)
|
||||||
|
return
|
||||||
|
case http.MethodPut:
|
||||||
|
h.updatePeer(accountId, peer, w, r)
|
||||||
|
return
|
||||||
|
case http.MethodGet:
|
||||||
|
writeJSONObject(w, toPeerResponse(peer))
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "", http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
@ -39,27 +102,24 @@ func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
respBody := []*PeerResponse{}
|
respBody := []*PeerResponse{}
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
respBody = append(respBody, &PeerResponse{
|
respBody = append(respBody, toPeerResponse(peer))
|
||||||
Name: peer.Key,
|
|
||||||
IP: peer.IP.String(),
|
|
||||||
LastSeen: time.Now(),
|
|
||||||
Connected: false,
|
|
||||||
Os: "Ubuntu 21.04 (Hirsute Hippo)",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(respBody)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed encoding account peers %s: %v", accountId, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
writeJSONObject(w, respBody)
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPeerResponse(peer *server.Peer) *PeerResponse {
|
||||||
|
return &PeerResponse{
|
||||||
|
Name: peer.Name,
|
||||||
|
IP: peer.IP.String(),
|
||||||
|
Connected: peer.Connected,
|
||||||
|
LastSeen: peer.LastSeen,
|
||||||
|
OS: peer.OS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,8 +43,52 @@ func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) CreateKey(w http.ResponseWriter, r *http.Request) {
|
func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
||||||
accountId := extractAccountIdFromRequestContext(r)
|
req := &SetupKeyRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var key *server.SetupKey
|
||||||
|
if req.Revoked {
|
||||||
|
//handle only if being revoked, don't allow to enable key again for now
|
||||||
|
key, err = h.accountManager.RevokeSetupKey(accountId, keyId)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "failed revoking key", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(req.Name) != 0 {
|
||||||
|
key, err = h.accountManager.RenameSetupKey(accountId, keyId, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "failed renaming key", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key != nil {
|
||||||
|
writeSuccess(w, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := h.accountManager.GetAccount(accountId)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, key := range account.SetupKeys {
|
||||||
|
if key.Id == keyId {
|
||||||
|
writeSuccess(w, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.Error(w, "setup key not found", http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.Request) {
|
||||||
req := &SetupKeyRequest{}
|
req := &SetupKeyRequest{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -81,50 +125,11 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPut:
|
||||||
req := &SetupKeyRequest{}
|
h.updateKey(accountId, keyId, w, r)
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var key *server.SetupKey
|
|
||||||
if req.Revoked {
|
|
||||||
//handle only if being revoked, don't allow to enable key again for now
|
|
||||||
key, err = h.accountManager.RevokeSetupKey(accountId, keyId)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed revoking key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(req.Name) != 0 {
|
|
||||||
key, err = h.accountManager.RenameSetupKey(accountId, keyId, req.Name)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed renaming key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key != nil {
|
|
||||||
writeSuccess(w, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
account, err := h.accountManager.GetAccount(accountId)
|
h.getKey(accountId, keyId, w, r)
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
if key.Id == keyId {
|
|
||||||
writeSuccess(w, key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.Error(w, "setup key not found", http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
@ -132,9 +137,15 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
accountId := extractAccountIdFromRequestContext(r)
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
h.createKey(accountId, w, r)
|
||||||
|
return
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
accountId := extractAccountIdFromRequestContext(r)
|
|
||||||
//new user -> create a new account
|
//new user -> create a new account
|
||||||
account, err := h.accountManager.GetOrCreateAccount(accountId)
|
account, err := h.accountManager.GetOrCreateAccount(accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -17,6 +17,17 @@ func extractAccountIdFromRequestContext(r *http.Request) string {
|
|||||||
return claims["sub"].(string)
|
return claims["sub"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
||||||
|
func writeJSONObject(w http.ResponseWriter, obj interface{}) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
err := json.NewEncoder(w).Encode(obj)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Duration is used strictly for JSON requests/responses due to duration marshalling issues
|
//Duration is used strictly for JSON requests/responses due to duration marshalling issues
|
||||||
type Duration struct {
|
type Duration struct {
|
||||||
time.Duration
|
time.Duration
|
||||||
|
@ -62,10 +62,10 @@ func (s *Server) Start() error {
|
|||||||
peersHandler := handler.NewPeers(s.accountManager)
|
peersHandler := handler.NewPeers(s.accountManager)
|
||||||
keysHandler := handler.NewSetupKeysHandler(s.accountManager)
|
keysHandler := handler.NewSetupKeysHandler(s.accountManager)
|
||||||
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||||
|
|
||||||
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/setup-keys", keysHandler.CreateKey).Methods("PUT")
|
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
|
||||||
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "POST", "OPTIONS")
|
|
||||||
http.Handle("/", r)
|
http.Handle("/", r)
|
||||||
|
|
||||||
if s.certManager != nil {
|
if s.certManager != nil {
|
||||||
|
184
management/server/peer.go
Normal file
184
management/server/peer.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Peer represents a machine connected to the network.
|
||||||
|
//The Peer is a Wireguard peer identified by a public key
|
||||||
|
type Peer struct {
|
||||||
|
//Wireguard public key
|
||||||
|
Key string
|
||||||
|
//A setup key this peer was registered with
|
||||||
|
SetupKey string
|
||||||
|
//IP address of the Peer
|
||||||
|
IP net.IP
|
||||||
|
//OS is peer's operating system
|
||||||
|
OS string
|
||||||
|
//Name is peer's name (machine name)
|
||||||
|
Name string
|
||||||
|
//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
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy copies Peer object
|
||||||
|
func (p *Peer) Copy() *Peer {
|
||||||
|
return &Peer{
|
||||||
|
Key: p.Key,
|
||||||
|
SetupKey: p.SetupKey,
|
||||||
|
IP: p.IP,
|
||||||
|
OS: p.OS,
|
||||||
|
Name: p.Name,
|
||||||
|
LastSeen: p.LastSeen,
|
||||||
|
Connected: p.Connected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetPeer returns a peer from a Store
|
||||||
|
func (manager *AccountManager) GetPeer(peerKey string) (*Peer, error) {
|
||||||
|
manager.mux.Lock()
|
||||||
|
defer manager.mux.Unlock()
|
||||||
|
|
||||||
|
peer, err := manager.Store.GetPeer(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//RenamePeer changes peer's name
|
||||||
|
func (manager *AccountManager) RenamePeer(accountId string, peerKey string, newName string) (*Peer, error) {
|
||||||
|
manager.mux.Lock()
|
||||||
|
defer manager.mux.Unlock()
|
||||||
|
|
||||||
|
peer, err := manager.Store.GetPeer(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerCopy := peer.Copy()
|
||||||
|
peerCopy.Name = newName
|
||||||
|
err = manager.Store.SavePeer(accountId, peerCopy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerCopy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//DeletePeer removes peer from the account by it's IP
|
||||||
|
func (manager *AccountManager) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
||||||
|
manager.mux.Lock()
|
||||||
|
defer manager.mux.Unlock()
|
||||||
|
return manager.Store.DeletePeer(accountId, peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetPeerByIP returns peer by it's IP
|
||||||
|
func (manager *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, error) {
|
||||||
|
manager.mux.Lock()
|
||||||
|
defer manager.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := manager.Store.GetAccount(accountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, peer := range account.Peers {
|
||||||
|
if peerIP == peer.IP.String() {
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.NotFound, "peer with IP %s not found", peerIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeersForAPeer returns a list of peers available for a given peer (key)
|
||||||
|
// Effectively all the peers of the original peer's account except for the peer itself
|
||||||
|
func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) {
|
||||||
|
manager.mux.Lock()
|
||||||
|
defer manager.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := manager.Store.GetPeerAccount(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "Invalid peer key %s", peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*Peer
|
||||||
|
for _, peer := range account.Peers {
|
||||||
|
if peer.Key != peerKey {
|
||||||
|
res = append(res, peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPeer adds a new peer to the Store.
|
||||||
|
// Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated
|
||||||
|
// will be returned, meaning the key is invalid
|
||||||
|
// 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).
|
||||||
|
// If the specified setupKey is empty then a new Account will be created //todo remove this part
|
||||||
|
func (manager *AccountManager) AddPeer(setupKey string, peerKey string) (*Peer, error) {
|
||||||
|
manager.mux.Lock()
|
||||||
|
defer manager.mux.Unlock()
|
||||||
|
|
||||||
|
upperKey := strings.ToUpper(setupKey)
|
||||||
|
|
||||||
|
var account *Account
|
||||||
|
var err error
|
||||||
|
var sk *SetupKey
|
||||||
|
if len(upperKey) == 0 {
|
||||||
|
// Empty setup key, create a new account for it.
|
||||||
|
account, sk = newAccount()
|
||||||
|
} else {
|
||||||
|
account, err = manager.Store.GetAccountBySetupKey(upperKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
sk = getAccountSetupKeyByKey(account, upperKey)
|
||||||
|
if sk == nil {
|
||||||
|
// shouldn't happen actually
|
||||||
|
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sk.IsValid() {
|
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "setup key was expired or overused %s", upperKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var takenIps []net.IP
|
||||||
|
for _, peer := range account.Peers {
|
||||||
|
takenIps = append(takenIps, peer.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
network := account.Network
|
||||||
|
nextIp, _ := AllocatePeerIP(network.Net, takenIps)
|
||||||
|
|
||||||
|
newPeer := &Peer{
|
||||||
|
Key: peerKey,
|
||||||
|
SetupKey: sk.Key,
|
||||||
|
IP: nextIp,
|
||||||
|
OS: "todo",
|
||||||
|
Name: "todo",
|
||||||
|
LastSeen: time.Now(),
|
||||||
|
Connected: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Peers[newPeer.Key] = newPeer
|
||||||
|
account.SetupKeys[sk.Key] = sk.IncrementUsage()
|
||||||
|
err = manager.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed adding peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPeer, nil
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
GetPeer(peerId string) (*Peer, error)
|
GetPeer(peerKey string) (*Peer, error)
|
||||||
|
DeletePeer(accountId string, peerKey string) (*Peer, error)
|
||||||
|
SavePeer(accountId string, peer *Peer) error
|
||||||
GetAccount(accountId string) (*Account, error)
|
GetAccount(accountId string) (*Account, error)
|
||||||
GetPeerAccount(peerId string) (*Account, error)
|
GetPeerAccount(peerKey string) (*Account, error)
|
||||||
GetAccountBySetupKey(setupKey string) (*Account, error)
|
GetAccountBySetupKey(setupKey string) (*Account, error)
|
||||||
SaveAccount(account *Account) error
|
SaveAccount(account *Account) error
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user