From abf3f4f55d49b5970dea07114de5fad90bf815da Mon Sep 17 00:00:00 2001 From: Aceix Date: Wed, 8 May 2024 09:15:05 +0000 Subject: [PATCH] feat(NET-1106): support additional RAG endpoint IPs (#2907) --- controllers/ext_client.go | 31 ++++++++++++++++++++++++++++--- controllers/node.go | 8 +++++++- models/api_host.go | 6 ++++++ models/api_node.go | 14 ++++++++++++++ models/node.go | 1 + models/structs.go | 5 +++++ pro/controllers/users.go | 20 ++++++++++++++++++++ 7 files changed, 81 insertions(+), 4 deletions(-) diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 0553f416..eb5308bb 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -21,6 +21,7 @@ import ( "github.com/gravitl/netmaker/mq" "github.com/skip2/go-qrcode" + "golang.org/x/exp/slices" "golang.org/x/exp/slog" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) @@ -199,6 +200,24 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) { return } + preferredIp := strings.TrimSpace(r.URL.Query().Get("preferredip")) + if preferredIp != "" { + allowedPreferredIps := []string{} + for i := range gwnode.AdditionalRagIps { + allowedPreferredIps = append(allowedPreferredIps, gwnode.AdditionalRagIps[i].String()) + } + allowedPreferredIps = append(allowedPreferredIps, host.EndpointIP.String()) + allowedPreferredIps = append(allowedPreferredIps, host.EndpointIPv6.String()) + if !slices.Contains(allowedPreferredIps, preferredIp) { + slog.Warn("preferred endpoint ip is not associated with the RAG. proceeding with preferred ip", "preferred ip", preferredIp) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("preferred endpoint ip is not associated with the RAG"), "badrequest")) + return + } + if net.ParseIP(preferredIp).To4() == nil { + preferredIp = fmt.Sprintf("[%s]", preferredIp) + } + } + addrString := client.Address if addrString != "" { addrString += "/32" @@ -214,12 +233,18 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) { if network.DefaultKeepalive != 0 { keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive)) } + gwendpoint := "" - if host.EndpointIP.To4() == nil { - gwendpoint = fmt.Sprintf("[%s]:%d", host.EndpointIP.String(), host.ListenPort) + if preferredIp == "" { + if host.EndpointIP.To4() == nil { + gwendpoint = fmt.Sprintf("[%s]:%d", host.EndpointIPv6.String(), host.ListenPort) + } else { + gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort) + } } else { - gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort) + gwendpoint = fmt.Sprintf("%s:%d", preferredIp, host.ListenPort) } + var newAllowedIPs string if logic.IsInternetGw(gwnode) || gwnode.InternetGwID != "" { egressrange := "0.0.0.0/0" diff --git a/controllers/node.go b/controllers/node.go index 37217d6b..ed104b35 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -635,14 +635,20 @@ func updateNode(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("metadata cannot be longer than 255 characters"), "badrequest")) return } + if !servercfg.IsPro { + newData.AdditionalRagIps = []string{} + } newNode := newData.ConvertToServerNode(¤tNode) + if newNode == nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("error converting node"), "badrequest")) + return + } if newNode.IsInternetGateway != currentNode.IsInternetGateway { if newNode.IsInternetGateway { logic.SetInternetGw(newNode, models.InetNodeReq{}) } else { logic.UnsetInternetGw(newNode) } - } relayUpdate := logic.RelayUpdates(¤tNode, newNode) if relayUpdate && newNode.IsRelay { diff --git a/models/api_host.go b/models/api_host.go index 087d0585..177799d8 100644 --- a/models/api_host.go +++ b/models/api_host.go @@ -44,7 +44,13 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost { a := ApiHost{} a.Debug = h.Debug a.EndpointIP = h.EndpointIP.String() + if a.EndpointIP == "" { + a.EndpointIP = "" + } a.EndpointIPv6 = h.EndpointIPv6.String() + if a.EndpointIPv6 == "" { + a.EndpointIPv6 = "" + } a.FirewallInUse = h.FirewallInUse a.ID = h.ID.String() a.Interfaces = make([]ApiIface, len(h.Interfaces)) diff --git a/models/api_node.go b/models/api_node.go index 307a0bf0..6015f4e8 100644 --- a/models/api_node.go +++ b/models/api_node.go @@ -5,6 +5,7 @@ import ( "time" "github.com/google/uuid" + "golang.org/x/exp/slog" ) // ApiNode is a stripped down Node DTO that exposes only required fields to external systems @@ -44,6 +45,7 @@ type ApiNode struct { IsInternetGateway bool `json:"isinternetgateway" yaml:"isinternetgateway"` InetNodeReq InetNodeReq `json:"inet_node_req" yaml:"inet_node_req"` InternetGwID string `json:"internetgw_node_id" yaml:"internetgw_node_id"` + AdditionalRagIps []string `json:"additional_rag_ips" yaml:"additional_rag_ips"` } // ApiNode.ConvertToServerNode - converts an api node to a server node @@ -109,6 +111,14 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node { convertedNode.LastPeerUpdate = time.Unix(a.LastPeerUpdate, 0) convertedNode.ExpirationDateTime = time.Unix(a.ExpirationDateTime, 0) convertedNode.Metadata = a.Metadata + for _, ip := range a.AdditionalRagIps { + ragIp := net.ParseIP(ip) + if ragIp == nil { + slog.Error("error parsing additional rag ip", "error", err, "ip", ip) + return nil + } + convertedNode.AdditionalRagIps = append(convertedNode.AdditionalRagIps, ragIp) + } return &convertedNode } @@ -163,6 +173,10 @@ func (nm *Node) ConvertToAPINode() *ApiNode { apiNode.FailOverPeers = nm.FailOverPeers apiNode.FailedOverBy = nm.FailedOverBy apiNode.Metadata = nm.Metadata + apiNode.AdditionalRagIps = []string{} + for _, ip := range nm.AdditionalRagIps { + apiNode.AdditionalRagIps = append(apiNode.AdditionalRagIps, ip.String()) + } return &apiNode } diff --git a/models/node.go b/models/node.go index 9313ed48..c55eef03 100644 --- a/models/node.go +++ b/models/node.go @@ -96,6 +96,7 @@ type Node struct { IsInternetGateway bool `json:"isinternetgateway" yaml:"isinternetgateway"` InetNodeReq InetNodeReq `json:"inet_node_req" yaml:"inet_node_req"` InternetGwID string `json:"internetgw_node_id" yaml:"internetgw_node_id"` + AdditionalRagIps []net.IP `json:"additional_rag_ips" yaml:"additional_rag_ips"` } // LegacyNode - legacy struct for node model diff --git a/models/structs.go b/models/structs.go index a052d7b5..fd11a07e 100644 --- a/models/structs.go +++ b/models/structs.go @@ -73,6 +73,7 @@ type UserRemoteGws struct { GwClient ExtClient `json:"gw_client"` GwPeerPublicKey string `json:"gw_peer_public_key"` Metadata string `json:"metadata"` + AllowedEndpoints []string `json:"allowed_endpoints"` } // UserRemoteGwsReq - struct to hold user remote acccess gws req @@ -372,3 +373,7 @@ type LoginReqDto struct { const ( ResHeaderKeyStAccessToken = "St-Access-Token" ) + +type GetClientConfReqDto struct { + PreferredIp string `json:"preferred_ip"` +} diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 027cdead..0cb7c2f0 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -227,6 +227,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { IsInternetGateway: node.IsInternetGateway, GwPeerPublicKey: host.PublicKey.String(), Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), }) userGws[node.Network] = gws delete(user.RemoteGwIDs, node.ID.String()) @@ -242,6 +243,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { IsInternetGateway: node.IsInternetGateway, GwPeerPublicKey: host.PublicKey.String(), Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), }) userGws[node.Network] = gws processedAdminNodeIds[node.ID.String()] = struct{}{} @@ -275,6 +277,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { IsInternetGateway: node.IsInternetGateway, GwPeerPublicKey: host.PublicKey.String(), Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), }) userGws[node.Network] = gws } @@ -302,6 +305,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { IsInternetGateway: node.IsInternetGateway, GwPeerPublicKey: host.PublicKey.String(), Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), }) userGws[node.Network] = gws } @@ -352,3 +356,19 @@ func ingressGatewayUsers(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(gwUsers) } + +func getAllowedRagEndpoints(ragNode *models.Node, ragHost *models.Host) []string { + endpoints := []string{} + if len(ragHost.EndpointIP) > 0 { + endpoints = append(endpoints, ragHost.EndpointIP.String()) + } + if len(ragHost.EndpointIPv6) > 0 { + endpoints = append(endpoints, ragHost.EndpointIPv6.String()) + } + if servercfg.IsPro { + for _, ip := range ragNode.AdditionalRagIps { + endpoints = append(endpoints, ip.String()) + } + } + return endpoints +}