Files
netmaker/controllers/node_exit_node.go
340ae1521f NM-295: publish TCP uplink flags and peer tcp_proxy_endpoint (#4106)
* wip(go): use default jwt for org tokens;

* wip(go): detach request context from goroutine context;

* wip(go): allow single tenant creation;

* wip(go): remove debug logs;

* wip(go): derive host tenant id from enrollment key tenant id;

* wip(go): skip server settings migration for new deployment;

* wip(go): return auth token with user on create org owner;

* wip(go): prevent use of default tenant slug ;

* wip(go): upsert settings for tenant in ctx;

* wip(go): use passed ctx when updating user; remove password validation from ValidateUser (already validated in UpdateUser);

* wip(go): qualify all tenant id usages with table name;

* wip(go): use passed ctx

* wip(go): return old enrollment key type on create;

* wip(go): prevent null networks return;

* wip(go): wire ctx in dns/nameserver functions; wire ctx in host registration functions;

* wip(go): wire ctx in node functions;

* wip(go): wire ctx in node functions;

* wip(go): wire ctx in extclient functions;

* wip(go): remove not needed field;

* wip(go): use passed ctx;

* wip(go): use unique index instead of primary key;

* wip(go): wire ctx in extclient apis;

* wip(go): wire ctx in extclient apis;

* wip(go): remove org id check from global scoped get tenant api;

* wip(go): add scope slug to discover response;

* wip(go): wire ctx in get users;

* wip(go): set tenant id in posture check violations;

* wip(go): upsert membership instead of user;

* wip(go): pass correct len;

* wip(go): optimize (single loop);

* wip(go): unify todos;

* wip(go): add todo;

* wip(go): wire context for network get;

* wip(go): wire context;

* wip(go): wire context;

* wip(go): wire ctx when getting tenant superadmin;

* wip(go): add network creator to network admin group;

* wip(go): check for user membership on sync;

* wip(go): add methods with strict membership check;

* wip(go): remove unwanted feature flags;

* wip(go): report per-tenant usage during license validation;

* wip(go): move email setting from tenant to org;

* wip(go): pass org scope to email init;

* wip(go): remove old tenant email settings usages; use org settings to get email settings

* wip(go): use get email settings from org settings;

* wip(go): remove email settings migration;

* wip(go): migrate email settings from server to org settings;

* wip(go): assign global network admin group membership only for super-admin and admins;

* wip(go): assign global network admin group membership only for super-admin and admins;

* wip(go): add membership alias to filter;

* wip(go): skip clearing local license validation response cache;

* wip(go): extend license validation response for multi-tenancy;

* wip(go): add functions to get sole tenant and org;

* wip(go): use tenant-specific feature flags;

* wip(go): add tenant status type;

* wip(go): remove old hook comment;

* wip(go): expect ctx during license validation;

* wip(go): always create default org and tenant for ce;

* wip(go): add tenant-specific check for license validation error;

* wip(go): cache license validation response in memory;

* wip(go): add todo to handle multiple-tenant for integration pull;

* wip(go): pass ctx during license validation;

* wip(go): add todo for making changes later; run hook separately for each tenant;

* wip(go): return specific errors when license validation response not in cache;

* wip(go): use retryable.httpclient for calling license validation api;

* wip(go): process license validation;

* wip(go): license validation function;

* wip(go): migration on license validation;

* wip(go): org settings not expected;

* wip(go): remove expiry from license validation response;

* wip(go): add limits and limits enforcement to license validation response;

* wip(go): remove expiry usages;

* wip(go): add arg to clear cache;

* wip(go): add arg to clear cache;

* wip(go): skip license validation if cached resp;

* wip(go): allow org_id and tenant_id query params;

* wip(go): upsert tenant membership on sync;

* wip(go): pass ctx to delete network roles;

* wip(go): create tenant membership on org owner creation on ce;

* wip(go): add tenant orchestrator;

* wip(go): use tenant orchestrator;

* wip(go): add default settings for tenant;

* wip(go): make org owner superadmin on all tenants;

* wip(go): simplify SyncOrgAndTenants;

* wip(go): retry license validation only 3 times

* wip(go): store invalid err;

* wip(go): add function to check if limits are to be enforced on a tenant;

* wip(go): pass tenant scope if possible when checking for license validation error;

* wip(go): remove merge errors;

* wip(go): add functions for limit checks;

* wip(go): wire host limit check;

* wip(go): wire ingress limit check;

* wip(go): wire egress limit check;

* wip(go): wire client limit check;

* wip(go): wire network limit check;

* wip(go): wire user limit check;

* wip(go): use correct errors;

* wip(go): skip telemetry for msps;

* wip(go): disable telemetry on msp tenants;

* wip(go): err check;

* wip(go): uncomment log event;

* wip(go): get sso state to get scope for non-headless oauth login;

* wip(go): inline delete pending user;

* wip(go): allow both org and tenant scope for pending users table;

* wip(go): add migration for pending user table during set tenant id and rekey org/tenant id;

* wip(go): update queries to use new scope fields in pending users table;

* wip(go): allow both org and tenant scope for user invites table;

* wip(go): update queries to use new scope fields;

* wip(go): add migration for rekey-ing org/tenant id and setting tenant id;

* wip(go): inline delete user invite;

* wip(go): set scope when creating invite;

* wip(go): get user count with membership is ctx is tenant scoped;

* wip(go): add methods to upsert and delete org memberships;

* wip(go): add global valid tenant and org roles map;

* wip(go): add scope query in user invite get by email;

* wip(go): add support for upsert and delete of org and tenant membership both;

* wip(go): infer scope for user invite apis;

* wip(go): wire ctx in get user invite by email;

* wip(go): allow no scope headers to be passed in infer scope apis;

* wip(go): add validation and checks for org-scoped invite api handlers;

* wip(go): wire ctx to is pending user check;

* wip(go): infer scope for pending user apis;

* wip(go): set default role based on ctx scope;

* wip(go): move mfa to tenant membership table; move account status to membership tables;

* wip(go): migrate account status and mfa settings;

* wip(go): update account status on sync;

* wip(go): use update mfa to update mfa setting;

* wip(go): set new membership settings on user creation;

* wip(go): use membership where required;

* wip(go): wire ctx on key unlink refs;

* wip(go): use r.Context directly without reinitializing db;

* wip(go): wire ctx in acls;

* wip(go): add tenant scoped primary key constructor;

* wip(go): use tenant scoped primary key for acls, dns, extclients and tags;

* wip(go): wire ctx for get groups in network;

* wip(go): wire ctx for get groups;

* wip(go): scope non-uuid group ids by tenant id;

* wip(go): migrate non-uuid group ids to scoped ids;

* wip(go): don't expect scope id in invite validation and signup apis;

* wip(go): wire ctx for get matching user; allow org scope when querying by external idp id;

* wip(go): add mfa to org;

* wip(go): scope preauth token;

* wip(go): add fallback for older auth tokens;

* wip(go): wire ctx for preauth token creation;

* wip(go): migrate mfa settings;

* wip(go): resolve mfa settings for inherited auth;

* wip(go): update mfa for org;

* wip(go): infer scope for totp apis;

* wip(go): use ctx when passed;

* wip(go): infer scope for list user apis;

* wip(go): ensure set primary keys don't cause errors during fetch;

* wip(go): make primary key for integrations uuid and store provider in separate key;

* wip(go): migrate integration ids;

* wip(go): use provider instead of id everywhere;

* wip(go): remove gorm column;

* wip(go): wire ctx for igw egress creation;

* wip(go): wire ctx for igw egress update;

* wip(go): wire ctx for host registration;

* wip(go): wire ctx for host registration;

* wip(go): merge errors;

* wip(go): merge errors;

* wip(go): wire ctx; remove default scope usages;

* wip(go): use get with membership;

* wip(go): org scope license validation;

* wip(go): remove debug log;

* wip(go): remove scoping on list roles;

* wip(go): ignore invite creation if already exists;

* wip(go): add is msp func to check if deployment is msp deployment;

* wip(go): ensure username is email for msp users;

* wip(go): add email validated check on user table;

* wip(go): mark email validated when oauth user;

* wip(go): handle gorm error;

* wip(go): add tenant id to siem grpc schema;

* wip(go): read tenant from scoped ctx;

* wip(go): pass scoped ctx to siem grpc client;

* wip(go): remove unused publisher;

* wip(go): publish all tenant exporter updates on pull;

* wip(go): fix wg usage;

* wip(go): tenant scope the ctx when getting server info;

* NM-295: publish TCP uplink flags and peer tcp_proxy_endpoint

Add gateway tcp_proxy_enabled/listen port and client use_tcp_uplink,
and populate PeerIDs on host-level pulls so clients receive dial targets.

* wip(go): add tenant id to enrollment key token;

* wip(go): make host register tenant scope;

* wip(go): add default settings;

* wip(go): use correct checks for is msp tenant;

* wip(go): add email validation extension;

* wip(go): add pages for validation check;

* wip(go): use extension for validating user;

* wip(go): add invite type;

* wip(go): create only one validation invite per user email;

* wip(go): add validate email handler;

* wip(go): add email builder;

* wip(go): add user invite type migration logic;

* wip(go): only log email send failures;

* wip(go): add tenant id to flows table;

* wip(go): add func to check if connected to clickhouse;

* wip(go): migrate clickhouse;

* wip(go): add tenant id to mdm and edr state;

* wip(go): add delete methods for tables;

* wip(go): add tenant teardown method;

* wip(go): handle tenant deletion;

* wip(go): clean up flow logs on tenant deletion;

* wip(go): add update org membership role id method;

* wip(go): add transfer org ownership api;

* wip(go): move audit logs retention period to org settings;

* wip(go):get retention period from org settings;

* wip(go): remove old audit logs usage;

* wip(go): migrate audit logs retention period;

* wip(go): unmarshal decodes nums as float64;

* wip(go): run flow logs cleanup for all tenants in one go;

* wip(go): add audit logs retention period and smtp settings to tenant settings table for non-msp tenants;

* wip(go): resolve retention period and smtp settings correctly;

* wip(go): handle retention period and smtp settings updates and fetches correctly;

* wip(go): overlay retention period and smtp settings on server settings;;

* wip(go): upsert overlayed settings;

* NM-295: resolve pr comments

* wip(go): add azure tenant id to org settings;

* wip(go): tenant scope list roles;

* wip(go): add noop creds;

* wip(go): use first instead of find;

* NM-295: check network hook cleanup time

* wip(go): use correct roles when transfering ownership;

* wip(go): delete access tokens only if tenant scoped;

* wip(go): make delete user work for both org and tenant scope;

* wip(go): delete extclients only if tenant scoped;

* wip(go): check if caller is allowed to delete user;

* wip(go): infer scope for update user;

* wip(go): prevent org-owner elevation;

* wip(go): permissions check for update;

* wip(go): delete user access tokens only if tenant scope;

* wip(go): delete extclients only if tenant scope;

* wip(go): move upsert membership in upsert user;

* wip(go): set api host when constructing server config;

* NM-295: mv proxy settings to host

* NM-295: preserve tcp uplink setting on disconnect/connect

* NM-295: show tcpuplink option on exit node gw if enabled

* NM-295: fix orphan gateway relayed client nodes

---------

Co-authored-by: VishalDalwadi <[email protected]>
Co-authored-by: Vishal Dalwadi <[email protected]>
2026-08-18 16:17:30 +05:30

140 lines
4.8 KiB
Go

package controller
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/db"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/middleware"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/scope"
)
func nodeExitNodeHandlers(r *mux.Router) {
r.HandleFunc("/api/v1/nodes/{network}/{nodeid}/exit_nodes",
middleware.Scope(scope.TenantScope, logic.SecurityCheck(true, http.HandlerFunc(listNodeExitNodes)))).
Methods(http.MethodGet)
r.HandleFunc("/api/v1/nodes/{network}/{nodeid}/exit_node",
middleware.Scope(scope.TenantScope, logic.SecurityCheck(true, http.HandlerFunc(getNodeExitNode)))).
Methods(http.MethodGet)
r.HandleFunc("/api/v1/nodes/{network}/{nodeid}/exit_node",
middleware.Scope(scope.TenantScope, logic.SecurityCheck(true, http.HandlerFunc(assignNodeExitNode)))).
Methods(http.MethodPut)
}
// @Summary List internet exit nodes available for a node
// @Router /api/v1/nodes/{network}/{nodeid}/exit_nodes [get]
// @Tags Nodes
// @Security oauth
// @Produce json
// @Param network path string true "Network ID"
// @Param nodeid path string true "Node ID"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} models.ErrorResponse
// @Failure 401 {object} models.ErrorResponse
func listNodeExitNodes(w http.ResponseWriter, r *http.Request) {
network, nodeID, ok := nodeExitNodeParams(w, r)
if !ok {
return
}
nodes, err := logic.ListNodeExitNodes(db.WithContext(r.Context()), network, nodeID)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, exitNodeErrType(err)))
return
}
logic.ReturnSuccessResponseWithJson(w, r, nodes, "fetched exit nodes")
}
// @Summary Get the internet exit node assigned to a node
// @Router /api/v1/nodes/{network}/{nodeid}/exit_node [get]
// @Tags Nodes
// @Security oauth
// @Produce json
// @Param network path string true "Network ID"
// @Param nodeid path string true "Node ID"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} models.ErrorResponse
// @Failure 401 {object} models.ErrorResponse
func getNodeExitNode(w http.ResponseWriter, r *http.Request) {
network, nodeID, ok := nodeExitNodeParams(w, r)
if !ok {
return
}
selected, err := logic.GetNodeExitNode(db.WithContext(r.Context()), network, nodeID)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, exitNodeErrType(err)))
return
}
logic.ReturnSuccessResponseWithJson(w, r, selected, "fetched selected exit node")
}
// @Summary Assign or clear the internet exit node for a node
// @Router /api/v1/nodes/{network}/{nodeid}/exit_node [put]
// @Tags Nodes
// @Security oauth
// @Accept json
// @Produce json
// @Param network path string true "Network ID"
// @Param nodeid path string true "Node ID"
// @Param body body models.NodeExitNodeSelectionReq true "Exit node assignment"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} models.ErrorResponse
// @Failure 401 {object} models.ErrorResponse
func assignNodeExitNode(w http.ResponseWriter, r *http.Request) {
network, nodeID, ok := nodeExitNodeParams(w, r)
if !ok {
return
}
var req models.NodeExitNodeSelectionReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
return
}
selected, err := logic.AssignNodeExitNode(db.WithContext(r.Context()), network, nodeID, req.EgressID, req.UseTcpUplink)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, exitNodeErrType(err)))
return
}
msg := "exit node selected"
if req.EgressID == "" {
msg = "exit node cleared"
}
logic.ReturnSuccessResponseWithJson(w, r, selected, msg)
}
func nodeExitNodeParams(w http.ResponseWriter, r *http.Request) (network, nodeID string, ok bool) {
vars := mux.Vars(r)
network = vars["network"]
nodeID = vars["nodeid"]
if network == "" || nodeID == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network and node are required"), logic.BadReq))
return "", "", false
}
return network, nodeID, true
}
func exitNodeErrType(err error) logic.ApiErrorType {
switch err.Error() {
case "network and node are required",
"node not found",
"node not in network",
"exit node not found",
"egress is not an active internet exit node in this network",
"exit node cannot use another exit node",
"routing node cannot select itself as exit node",
"internet egress has no routing node",
"gateway nodes cannot be assigned an exit node",
"node is relayed by a different gateway":
return logic.BadReq
default:
if strings.Contains(err.Error(), "does not have TCP proxy enabled") {
return logic.BadReq
}
return logic.Internal
}
}