From 1f9808ff597850c830795027e552e8a9576a4ac1 Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Tue, 1 Oct 2024 17:48:36 +0400 Subject: [PATCH] NET-1604: New Simplified RAC Apis (#3147) * ipv6 fix for mobile apps * simplified RAC APIs * add response to invite api * fix get config api * fix middleware for auth * add separate controller for rac apis * Revert "ipv6 fix for mobile apps" This reverts commit dc84d90be260d0a000fd4575e65e93c98b9279be. --- controllers/middleware.go | 4 + models/structs.go | 10 ++ pro/controllers/rac.go | 14 +++ pro/controllers/users.go | 214 +++++++++++++++++++++++++++++++++++++- pro/initialize.go | 1 + pro/logic/security.go | 3 + pro/logic/user_mgmt.go | 25 +++++ 7 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 pro/controllers/rac.go diff --git a/controllers/middleware.go b/controllers/middleware.go index fb2bef68..8c014fca 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -27,6 +27,7 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("TARGET_RSRC", "") r.Header.Set("RSRC_TYPE", "") r.Header.Set("TARGET_RSRC_ID", "") + r.Header.Set("RAC", "") r.Header.Set("NET_ID", params["network"]) if strings.Contains(route, "hosts") || strings.Contains(route, "nodes") { r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) @@ -34,6 +35,9 @@ func userMiddleWare(handler http.Handler) http.Handler { if strings.Contains(route, "dns") { r.Header.Set("TARGET_RSRC", models.DnsRsrc.String()) } + if strings.Contains(route, "rac") { + r.Header.Set("RAC", "true") + } if strings.Contains(route, "users") { r.Header.Set("TARGET_RSRC", models.UserRsrc.String()) } diff --git a/models/structs.go b/models/structs.go index f8dd753f..3cc20ca4 100644 --- a/models/structs.go +++ b/models/structs.go @@ -45,6 +45,16 @@ type UserRemoteGws struct { NetworkAddresses []string `json:"network_addresses"` } +// UserRAGs - struct for user access gws +type UserRAGs struct { + GwID string `json:"remote_access_gw_id"` + GWName string `json:"gw_name"` + Network string `json:"network"` + Connected bool `json:"connected"` + IsInternetGateway bool `json:"is_internet_gateway"` + Metadata string `json:"metadata"` +} + // UserRemoteGwsReq - struct to hold user remote acccess gws req type UserRemoteGwsReq struct { RemoteAccessClientID string `json:"remote_access_clientid"` diff --git a/pro/controllers/rac.go b/pro/controllers/rac.go new file mode 100644 index 00000000..0d1b127f --- /dev/null +++ b/pro/controllers/rac.go @@ -0,0 +1,14 @@ +package controllers + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logic" +) + +func RacHandlers(r *mux.Router) { + r.HandleFunc("/api/v1/rac/networks", logic.SecurityCheck(false, http.HandlerFunc(getUserRemoteAccessNetworks))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/rac/network/{network}/access_points", logic.SecurityCheck(false, http.HandlerFunc(getUserRemoteAccessNetworkGateways))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/rac/access_point/{access_point_id}/config", logic.SecurityCheck(false, http.HandlerFunc(getRemoteAccessGatewayConf))).Methods(http.MethodGet) +} diff --git a/pro/controllers/users.go b/pro/controllers/users.go index c8119b79..d5ea4ddb 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -263,7 +263,6 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { }(invite) } logic.ReturnSuccessResponse(w, r, "triggered user invites") - } // swagger:route GET /api/v1/users/invites user listUserInvites @@ -816,6 +815,218 @@ func removeUserFromRemoteAccessGW(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(logic.ToReturnUser(*user)) } +// @Summary Get Users Remote Access Gw Networks. +// @Router /api/users/{username}/remote_access_gw [get] +// @Tags Users +// @Param username path string true "Username to fetch all the gateways with access" +// @Success 200 {object} map[string][]models.UserRemoteGws +// @Failure 500 {object} models.ErrorResponse +func getUserRemoteAccessNetworks(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + logger.Log(0, username, "failed to fetch user: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) + return + } + userGws := make(map[string][]models.UserRemoteGws) + networks := []models.Network{} + networkMap := make(map[string]struct{}) + userGwNodes := proLogic.GetUserRAGNodes(*user) + for _, node := range userGwNodes { + network, err := logic.GetNetwork(node.Network) + if err != nil { + slog.Error("failed to get node network", "error", err) + continue + } + if _, ok := networkMap[network.NetID]; ok { + continue + } + networkMap[network.NetID] = struct{}{} + networks = append(networks, network) + } + + slog.Debug("returned user gws", "user", username, "gws", userGws) + logic.ReturnSuccessResponseWithJson(w, r, networks, "fetched user accessible networks") +} + +// @Summary Get Users Remote Access Gw Networks. +// @Router /api/users/{username}/remote_access_gw [get] +// @Tags Users +// @Param username path string true "Username to fetch all the gateways with access" +// @Success 200 {object} map[string][]models.UserRemoteGws +// @Failure 500 {object} models.ErrorResponse +func getUserRemoteAccessNetworkGateways(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + logger.Log(0, username, "failed to fetch user: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) + return + } + network := params["network"] + if network == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params network"), "badrequest")) + return + } + userGws := []models.UserRAGs{} + + userGwNodes := proLogic.GetUserRAGNodes(*user) + for _, node := range userGwNodes { + if node.Network != network { + continue + } + + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + continue + } + + userGws = append(userGws, models.UserRAGs{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + IsInternetGateway: node.IsInternetGateway, + Metadata: node.Metadata, + }) + + } + + slog.Debug("returned user gws", "user", username, "gws", userGws) + logic.ReturnSuccessResponseWithJson(w, r, userGws, "fetched user accessible gateways in network "+network) +} + +// @Summary Get Users Remote Access Gw Networks. +// @Router /api/users/{username}/remote_access_gw [get] +// @Tags Users +// @Param username path string true "Username to fetch all the gateways with access" +// @Success 200 {object} map[string][]models.UserRemoteGws +// @Failure 500 {object} models.ErrorResponse +func getRemoteAccessGatewayConf(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + logger.Log(0, username, "failed to fetch user: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) + return + } + remoteGwID := params["access_point_id"] + if remoteGwID == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params access_point_id"), "badrequest")) + return + } + var req models.UserRemoteGwsReq + err = json.NewDecoder(r.Body).Decode(&req) + if err != nil { + slog.Error("error decoding request body: ", "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + + userGwNodes := proLogic.GetUserRAGNodes(*user) + if _, ok := userGwNodes[remoteGwID]; !ok { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access denied"), "forbidden")) + return + } + node, err := logic.GetNodeByID(remoteGwID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch gw node %s, error: %v", remoteGwID, err), "badrequest")) + return + } + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch gw host %s, error: %v", remoteGwID, err), "badrequest")) + return + } + network, err := logic.GetNetwork(node.Network) + if err != nil { + slog.Error("failed to get node network", "error", err) + } + var userConf models.ExtClient + allextClients, err := logic.GetAllExtClients() + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, extClient := range allextClients { + if extClient.Network != network.NetID || extClient.IngressGatewayID != node.ID.String() { + continue + } + if extClient.RemoteAccessClientID == req.RemoteAccessClientID && extClient.OwnerID == username { + userConf = extClient + userConf.AllowedIPs = logic.GetExtclientAllowedIPs(extClient) + } + } + if userConf.ClientID == "" { + // create a new conf + userConf.OwnerID = user.UserName + userConf.RemoteAccessClientID = req.RemoteAccessClientID + userConf.IngressGatewayID = node.ID.String() + + // set extclient dns to ingressdns if extclient dns is not explicitly set + if (userConf.DNS == "") && (node.IngressDNS != "") { + userConf.DNS = node.IngressDNS + } + + userConf.Network = node.Network + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to get ingress gateway host for node [%s] info: %v", node.ID, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + listenPort := logic.GetPeerListenPort(host) + if host.EndpointIP.To4() == nil { + userConf.IngressGatewayEndpoint = fmt.Sprintf("[%s]:%d", host.EndpointIPv6.String(), listenPort) + } else { + userConf.IngressGatewayEndpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), listenPort) + } + userConf.Enabled = true + parentNetwork, err := logic.GetNetwork(node.Network) + if err == nil { // check if parent network default ACL is enabled (yes) or not (no) + userConf.Enabled = parentNetwork.DefaultACL == "yes" + } + if err = logic.CreateExtClient(&userConf); err != nil { + slog.Error( + "failed to create extclient", + "user", + r.Header.Get("user"), + "network", + node.Network, + "error", + err, + ) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + } + userGw := models.UserRemoteGws{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + GwClient: userConf, + Connected: true, + IsInternetGateway: node.IsInternetGateway, + GwPeerPublicKey: host.PublicKey.String(), + GwListenPort: logic.GetPeerListenPort(host), + Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), + NetworkAddresses: []string{network.AddressRange, network.AddressRange6}, + } + + slog.Debug("returned user gw config", "user", user.UserName, "gws", userGw) + logic.ReturnSuccessResponseWithJson(w, r, userGw, "fetched user config to gw "+remoteGwID) +} + // @Summary Get Users Remote Access Gw. // @Router /api/users/{username}/remote_access_gw [get] // @Tags Users @@ -876,6 +1087,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { network, err := logic.GetNetwork(node.Network) if err != nil { slog.Error("failed to get node network", "error", err) + continue } gws := userGws[node.Network] diff --git a/pro/initialize.go b/pro/initialize.go index 1c6ba8a1..3a41b1e3 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -33,6 +33,7 @@ func InitPro() { proControllers.UserHandlers, proControllers.FailOverHandlers, proControllers.InetHandlers, + proControllers.RacHandlers, ) controller.ListRoles = proControllers.ListRoles logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() { diff --git a/pro/logic/security.go b/pro/logic/security.go index fcc6d73c..0bda7e02 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -50,6 +50,9 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { if targetRsrc == "" { return errors.New("target rsrc is missing") } + if r.Header.Get("RAC") == "true" && r.Method == http.MethodGet { + return nil + } if netID == "" { return errors.New("network id is missing") } diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 1d336cc1..0f3db105 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -511,6 +511,31 @@ func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, n _, ok = rsrcScope[rsrcID] return ok } + +func DoesUserHaveAccessToRAGNode(user models.User, node models.Node) bool { + userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user) + logger.Log(3, fmt.Sprintf("User Gw Access Scope: %+v", userGwAccessScope)) + _, allNetAccess := userGwAccessScope["*"] + if node.IsIngressGateway && !node.PendingDelete { + if allNetAccess { + return true + } else { + gwRsrcMap := userGwAccessScope[models.NetworkID(node.Network)] + scope, ok := gwRsrcMap[models.AllRemoteAccessGwRsrcID] + if !ok { + if scope, ok = gwRsrcMap[models.RsrcID(node.ID.String())]; !ok { + return false + } + } + if scope.VPNaccess { + return true + } + + } + } + return false +} + func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { gws = make(map[string]models.Node) userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user)