From 63eac1e79c2f8124f9725b51f7e454e634e81c96 Mon Sep 17 00:00:00 2001 From: 0xdcarns Date: Wed, 22 Mar 2023 14:47:13 -0400 Subject: [PATCH] added host pull model, made API --- controllers/hosts.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ controllers/node.go | 13 +++++++----- logic/jwts.go | 4 ++-- models/structs.go | 8 ++++++++ 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/controllers/hosts.go b/controllers/hosts.go index bad463a5..a9380d22 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -1,6 +1,7 @@ package controller import ( + "context" "encoding/json" "errors" "fmt" @@ -26,6 +27,7 @@ func hostHandlers(r *mux.Router) { r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(createHostRelay))).Methods(http.MethodPost) r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(deleteHostRelay))).Methods(http.MethodDelete) r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost) + r.HandleFunc("/api/v1/host", authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet) } // swagger:route GET /api/hosts hosts getHosts @@ -53,6 +55,53 @@ func getHosts(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(apiHosts) } +// swagger:route GET /api/v1/host pull pullHost +// +// Used by clients for "pull" command +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: pull +func pull(w http.ResponseWriter, r *http.Request) { + + hostID := r.Header.Get(hostIDHeader) // return JSON/API formatted keys + if len(hostID) == 0 { + logger.Log(0, "no host authorized to pull") + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("no host authorized to pull"), "internal")) + return + } + host, err := logic.GetHost(hostID) + if err != nil { + logger.Log(0, "no host found during pull", hostID) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + hPU, err := logic.GetPeerUpdateForHost(context.Background(), "", host, nil, nil) + if err != nil { + logger.Log(0, "could not pull peers for host", hostID) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + serverConf := servercfg.GetServerInfo() + if servercfg.GetBrokerType() == servercfg.EmqxBrokerType { + serverConf.MQUserName = hostID + } + response := models.HostPull{ + Host: *host, + ServerConfig: serverConf, + Peers: hPU.Peers, + PeerIDs: hPU.PeerIDs, + } + + logger.Log(1, hostID, "completed a pull") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(&response) +} + // swagger:route PUT /api/hosts/{hostid} hosts updateHost // // Updates a Netclient host on Netmaker server. diff --git a/controllers/node.go b/controllers/node.go index a2aae609..6cfc5e3c 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -19,6 +19,8 @@ import ( "golang.org/x/crypto/bcrypt" ) +var hostIDHeader = "host-id" + func nodeHandlers(r *mux.Router) { r.HandleFunc("/api/nodes", authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet) @@ -152,7 +154,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) { // even if it's technically ok // This is kind of a poor man's RBAC. There's probably a better/smarter way. // TODO: Consider better RBAC implementations -func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc { +func authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ Code: http.StatusUnauthorized, Message: logic.Unauthorized_Msg, @@ -184,11 +186,11 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha logic.ReturnErrorResponse(w, r, errorResponse) return } - //check if node instead of user - if nodesAllowed { + // check if host instead of user + if hostAllowed { // TODO --- should ensure that node is only operating on itself - if _, _, _, err := logic.VerifyToken(authToken); err == nil { - + if hostID, _, _, err := logic.VerifyHostToken(authToken); err == nil { + r.Header.Set(hostIDHeader, hostID) // this indicates request is from a node // used for failover - if a getNode comes from node, this will trigger a metrics wipe next.ServeHTTP(w, r) @@ -244,6 +246,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha } else { isAuthorized = (nodeID == params["netid"]) } + case "host": case "user": isAuthorized = true default: diff --git a/logic/jwts.go b/logic/jwts.go index 0e1716da..9e26eb2f 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -129,8 +129,8 @@ func VerifyUserToken(tokenString string) (username string, networks []string, is return "", nil, false, err } -// VerifyToken - [nodes] Only -func VerifyToken(tokenString string) (hostID string, mac string, network string, err error) { +// VerifyHostToken - [hosts] Only +func VerifyHostToken(tokenString string) (hostID string, mac string, network string, err error) { claims := &models.Claims{} // this may be a stupid way of serving up a master key diff --git a/models/structs.go b/models/structs.go index 6d1215fb..1fb056e3 100644 --- a/models/structs.go +++ b/models/structs.go @@ -198,6 +198,14 @@ type TrafficKeys struct { Server []byte `json:"server" bson:"server" yaml:"server"` } +// HostPull - response of a host's pull +type HostPull struct { + Host Host `json:"host" yaml:"host"` + Peers []wgtypes.PeerConfig `json:"peers" yaml:"peers"` + ServerConfig ServerConfig `json:"server_config" yaml:"server_config"` + PeerIDs PeerMap `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"` +} + // NodeGet - struct for a single node get response type NodeGet struct { Node Node `json:"node" bson:"node" yaml:"node"`