Browse Source

Merge pull request #1490 from gravitl/release_v0.15.0

Release v0.15.0
Alex Feiszli 3 years ago
parent
commit
d2aa528d0f
64 changed files with 1367 additions and 559 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 1 1
      README.md
  3. 0 1
      auth/auth.go
  4. 9 2
      auth/azure-ad.go
  5. 9 2
      auth/github.go
  6. 9 2
      auth/google.go
  7. 9 2
      auth/oidc.go
  8. 2 2
      compose/docker-compose.reference.yml
  9. 2 2
      compose/docker-compose.yml
  10. 1 0
      config/config.go
  11. 1 0
      config/environments/dev.yaml
  12. 0 1
      controllers/config/dnsconfig/netmaker.hosts
  13. 1 0
      controllers/controller.go
  14. 69 0
      controllers/ipservice.go
  15. 2 4
      controllers/network.go
  16. 15 18
      controllers/node.go
  17. 4 0
      database/database.go
  18. 7 4
      go.mod
  19. 8 6
      go.sum
  20. 1 1
      k8s/client/netclient-daemonset.yaml
  21. 1 1
      k8s/client/netclient.yaml
  22. 1 1
      k8s/server/netmaker-server.yaml
  23. 1 1
      k8s/server/netmaker-ui.yaml
  24. 0 3
      logger/util.go
  25. 50 0
      logic/auth.go
  26. 149 36
      logic/gateway.go
  27. 0 30
      logic/networks.go
  28. 3 2
      logic/nodes.go
  29. 45 4
      logic/peers.go
  30. 0 6
      logic/relay.go
  31. 3 32
      logic/server.go
  32. 3 200
      logic/wireguard.go
  33. 22 18
      logic/zombie.go
  34. 6 0
      main.go
  35. 65 49
      models/node.go
  36. 17 0
      models/ssocache.go
  37. 1 0
      mq/handlers.go
  38. 7 0
      netclient/cli_options/flags.go
  39. 14 12
      netclient/config/config.go
  40. 1 0
      netclient/daemon/common.go
  41. 39 24
      netclient/functions/common.go
  42. 9 0
      netclient/functions/daemon.go
  43. 22 4
      netclient/functions/join.go
  44. 21 2
      netclient/functions/mqhandlers.go
  45. 13 1
      netclient/functions/mqpublish.go
  46. 1 0
      netclient/functions/upgrades/upgrades.go
  47. 6 0
      netclient/global_settings/globalsettings.go
  48. 92 0
      netclient/local/routes.go
  49. 40 3
      netclient/local/routes_darwin.go
  50. 35 2
      netclient/local/routes_freebsd.go
  51. 30 0
      netclient/local/routes_linux.go
  52. 45 0
      netclient/local/routes_windows.go
  53. 5 0
      netclient/main.go
  54. 4 0
      netclient/ncutils/iface.go
  55. 53 7
      netclient/ncutils/netclientutils.go
  56. 1 1
      netclient/netclient.exe.manifest.xml
  57. 5 5
      netclient/versioninfo.json
  58. 41 15
      netclient/wireguard/common.go
  59. 1 0
      netclient/wireguard/mac.go
  60. 0 35
      scripts/netmaker-server.sh
  61. 302 0
      scripts/nm-quick-interactive.sh
  62. 33 4
      scripts/nm-quick.sh
  63. 18 13
      scripts/openwrt-daemon.sh
  64. 11 0
      servercfg/serverconf.go

+ 1 - 0
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,6 +31,7 @@ body:
       label: Version
       label: Version
       description: What version are you running?
       description: What version are you running?
       options:
       options:
+        - v0.15.0
         - v0.14.6
         - v0.14.6
         - v0.14.5
         - v0.14.5
         - v0.14.4
         - v0.14.4

+ 1 - 1
README.md

@@ -17,7 +17,7 @@
 
 
 <p align="center">
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.14.6-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.15.0-informational?style=flat-square" />
   </a>
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 0 - 1
auth/auth.go

@@ -29,7 +29,6 @@ const (
 	auth_key               = "netmaker_auth"
 	auth_key               = "netmaker_auth"
 )
 )
 
 
-var oauth_state_string = "netmaker-oauth-state" // should be set randomly each provider login
 var auth_provider *oauth2.Config
 var auth_provider *oauth2.Config
 
 
 func getCurrentAuthFunctions() map[string]interface{} {
 func getCurrentAuthFunctions() map[string]interface{} {

+ 9 - 2
auth/azure-ad.go

@@ -41,7 +41,7 @@ func initAzureAD(redirectURL string, clientID string, clientSecret string) {
 }
 }
 
 
 func handleAzureLogin(w http.ResponseWriter, r *http.Request) {
 func handleAzureLogin(w http.ResponseWriter, r *http.Request) {
-	oauth_state_string = logic.RandomString(16)
+	var oauth_state_string = logic.RandomString(16)
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		return
 		return
@@ -49,6 +49,12 @@ func handleAzureLogin(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		return
 		return
 	}
 	}
+
+	if err := logic.SetState(oauth_state_string); err != nil {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 }
 }
@@ -88,7 +94,8 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 }
 }
 
 
 func getAzureUserInfo(state string, code string) (*azureOauthUser, error) {
 func getAzureUserInfo(state string, code string) (*azureOauthUser, error) {
-	if state != oauth_state_string {
+	oauth_state_string, isValid := logic.IsStateValid(state)
+	if !isValid || state != oauth_state_string {
 		return nil, fmt.Errorf("invalid oauth state")
 		return nil, fmt.Errorf("invalid oauth state")
 	}
 	}
 	var token, err = auth_provider.Exchange(context.Background(), code)
 	var token, err = auth_provider.Exchange(context.Background(), code)

+ 9 - 2
auth/github.go

@@ -41,7 +41,7 @@ func initGithub(redirectURL string, clientID string, clientSecret string) {
 }
 }
 
 
 func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
 func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
-	oauth_state_string = logic.RandomString(16)
+	var oauth_state_string = logic.RandomString(16)
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		return
 		return
@@ -49,6 +49,12 @@ func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		return
 		return
 	}
 	}
+
+	if err := logic.SetState(oauth_state_string); err != nil {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 }
 }
@@ -88,7 +94,8 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 }
 }
 
 
 func getGithubUserInfo(state string, code string) (*githubOauthUser, error) {
 func getGithubUserInfo(state string, code string) (*githubOauthUser, error) {
-	if state != oauth_state_string {
+	oauth_state_string, isValid := logic.IsStateValid(state)
+	if !isValid || state != oauth_state_string {
 		return nil, fmt.Errorf("invalid OAuth state")
 		return nil, fmt.Errorf("invalid OAuth state")
 	}
 	}
 	var token, err = auth_provider.Exchange(context.Background(), code)
 	var token, err = auth_provider.Exchange(context.Background(), code)

+ 9 - 2
auth/google.go

@@ -42,7 +42,7 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) {
 }
 }
 
 
 func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
 func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
-	oauth_state_string = logic.RandomString(16)
+	var oauth_state_string = logic.RandomString(16)
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		return
 		return
@@ -50,6 +50,12 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		return
 		return
 	}
 	}
+
+	if err := logic.SetState(oauth_state_string); err != nil {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 }
 }
@@ -89,7 +95,8 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 }
 }
 
 
 func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) {
 func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) {
-	if state != oauth_state_string {
+	oauth_state_string, isValid := logic.IsStateValid(state)
+	if !isValid || state != oauth_state_string {
 		return nil, fmt.Errorf("invalid OAuth state")
 		return nil, fmt.Errorf("invalid OAuth state")
 	}
 	}
 	var token, err = auth_provider.Exchange(context.Background(), code)
 	var token, err = auth_provider.Exchange(context.Background(), code)

+ 9 - 2
auth/oidc.go

@@ -54,7 +54,7 @@ func initOIDC(redirectURL string, clientID string, clientSecret string, issuer s
 }
 }
 
 
 func handleOIDCLogin(w http.ResponseWriter, r *http.Request) {
 func handleOIDCLogin(w http.ResponseWriter, r *http.Request) {
-	oauth_state_string = logic.RandomString(16)
+	var oauth_state_string = logic.RandomString(16)
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
 		return
 		return
@@ -62,6 +62,12 @@ func handleOIDCLogin(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
 		return
 		return
 	}
 	}
+
+	if err := logic.SetState(oauth_state_string); err != nil {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
 }
 }
@@ -101,7 +107,8 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 }
 }
 
 
 func getOIDCUserInfo(state string, code string) (u *OIDCUser, e error) {
 func getOIDCUserInfo(state string, code string) (u *OIDCUser, e error) {
-	if state != oauth_state_string {
+	oauth_state_string, isValid := logic.IsStateValid(state)
+	if !isValid || state != oauth_state_string {
 		return nil, fmt.Errorf("invalid OAuth state")
 		return nil, fmt.Errorf("invalid OAuth state")
 	}
 	}
 
 

+ 2 - 2
compose/docker-compose.reference.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
 services:
   netmaker: # The Primary Server for running Netmaker
   netmaker: # The Primary Server for running Netmaker
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.14.6
+    image: gravitl/netmaker:v0.15.0
     cap_add: 
     cap_add: 
       - NET_ADMIN
       - NET_ADMIN
       - NET_RAW
       - NET_RAW
@@ -62,7 +62,7 @@ services:
       - traefik.http.services.netmaker-api.loadbalancer.server.port=8081
       - traefik.http.services.netmaker-api.loadbalancer.server.port=8081
   netmaker-ui:  # The Netmaker UI Component
   netmaker-ui:  # The Netmaker UI Component
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.14.6
+    image: gravitl/netmaker-ui:v0.15.0
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:

+ 2 - 2
compose/docker-compose.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
 services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.14.6
+    image: gravitl/netmaker:v0.15.0
     cap_add: 
     cap_add: 
       - NET_ADMIN
       - NET_ADMIN
       - NET_RAW
       - NET_RAW
@@ -51,7 +51,7 @@ services:
       - traefik.http.services.netmaker-api.loadbalancer.server.port=8081
       - traefik.http.services.netmaker-api.loadbalancer.server.port=8081
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.14.6
+    image: gravitl/netmaker-ui:v0.15.0
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:

+ 1 - 0
config/config.go

@@ -69,6 +69,7 @@ type ServerConfig struct {
 	MQPort                string `yaml:"mqport"`
 	MQPort                string `yaml:"mqport"`
 	MQServerPort          string `yaml:"mqserverport"`
 	MQServerPort          string `yaml:"mqserverport"`
 	Server                string `yaml:"server"`
 	Server                string `yaml:"server"`
+	PublicIPService       string `yaml:"publicipservice"`
 }
 }
 
 
 // SQLConfig - Generic SQL Config
 // SQLConfig - Generic SQL Config

+ 1 - 0
config/environments/dev.yaml

@@ -11,3 +11,4 @@ server:
   disableremoteipcheck: "" # defaults to "false" or DISABLE_REMOTE_IP_CHECK (if set)
   disableremoteipcheck: "" # defaults to "false" or DISABLE_REMOTE_IP_CHECK (if set)
   version: "" # version of server
   version: "" # version of server
   rce: "" # defaults to "off"
   rce: "" # defaults to "off"
+  publicipservice: "" # defaults to "" or PUBLIC_IP_SERVICE (if set)

+ 0 - 1
controllers/config/dnsconfig/netmaker.hosts

@@ -1 +0,0 @@
-10.0.0.2         testnode.skynet myhost.skynet

+ 1 - 0
controllers/controller.go

@@ -24,6 +24,7 @@ var HttpHandlers = []interface{}{
 	fileHandlers,
 	fileHandlers,
 	serverHandlers,
 	serverHandlers,
 	extClientHandlers,
 	extClientHandlers,
+	ipHandlers,
 }
 }
 
 
 // HandleRESTRequests - handles the rest requests
 // HandleRESTRequests - handles the rest requests

+ 69 - 0
controllers/ipservice.go

@@ -0,0 +1,69 @@
+package controller
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"strings"
+
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+func ipHandlers(r *mux.Router) {
+	r.HandleFunc("/api/getip", http.HandlerFunc(getPublicIP)).Methods("GET")
+}
+
+func getPublicIP(w http.ResponseWriter, r *http.Request) {
+	r.Header.Set("Connection", "close")
+	ip, err := parseIP(r)
+	if err != nil {
+		w.WriteHeader(400)
+		if ip != "" {
+			w.Write([]byte("ip is invalid: " + ip))
+			return
+		} else {
+			w.Write([]byte("no ip found"))
+			return
+		}
+	} else {
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+	w.WriteHeader(200)
+	w.Write([]byte(ip))
+}
+
+func parseIP(r *http.Request) (string, error) {
+	// Get Public IP from header
+	ip := r.Header.Get("X-REAL-IP")
+	ipnet := net.ParseIP(ip)
+	if ipnet != nil && !ncutils.IpIsPrivate(ipnet) {
+		return ip, nil
+	}
+
+	// If above fails, get Public IP from other header instead
+	forwardips := r.Header.Get("X-FORWARDED-FOR")
+	iplist := strings.Split(forwardips, ",")
+	for _, ip := range iplist {
+		ipnet := net.ParseIP(ip)
+		if ipnet != nil && !ncutils.IpIsPrivate(ipnet) {
+			return ip, nil
+		}
+	}
+
+	// If above also fails, get Public IP from Remote Address of request
+	ip, _, err := net.SplitHostPort(r.RemoteAddr)
+	if err != nil {
+		return "", err
+	}
+	ipnet = net.ParseIP(ip)
+	if ipnet != nil {
+		if ncutils.IpIsPrivate(ipnet) {
+			return ip, fmt.Errorf("ip is a private address")
+		}
+		return ip, nil
+	}
+	return "", fmt.Errorf("no ip found")
+}

+ 2 - 4
controllers/network.go

@@ -39,7 +39,7 @@ func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks/{networkname}/acls", securityCheck(true, http.HandlerFunc(getNetworkACL))).Methods("GET")
 	r.HandleFunc("/api/networks/{networkname}/acls", securityCheck(true, http.HandlerFunc(getNetworkACL))).Methods("GET")
 }
 }
 
 
-//simple get all networks function
+// simple get all networks function
 func getNetworks(w http.ResponseWriter, r *http.Request) {
 func getNetworks(w http.ResponseWriter, r *http.Request) {
 
 
 	headerNetworks := r.Header.Get("networks")
 	headerNetworks := r.Header.Get("networks")
@@ -216,9 +216,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 		for _, node := range nodes {
 		for _, node := range nodes {
-			if err = mq.NodeUpdate(&node); err != nil {
-				logger.Log(1, "failed to send update to node during a network wide update", node.Name, node.ID, err.Error())
-			}
+			runUpdates(&node, true)
 		}
 		}
 	}
 	}
 
 

+ 15 - 18
controllers/node.go

@@ -171,13 +171,13 @@ func nodeauth(next http.Handler) http.HandlerFunc {
 	}
 	}
 }
 }
 
 
-//The middleware for most requests to the API
-//They all pass  through here first
-//This will validate the JWT (or check for master token)
-//This will also check against the authNetwork and make sure the node should be accessing that endpoint,
-//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
+// The middleware for most requests to the API
+// They all pass  through here first
+// This will validate the JWT (or check for master token)
+// This will also check against the authNetwork and make sure the node should be accessing that endpoint,
+// 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(nodesAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
 		var errorResponse = models.ErrorResponse{
@@ -302,7 +302,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 	}
 	}
 }
 }
 
 
-//Gets all nodes associated with network, including pending nodes
+// Gets all nodes associated with network, including pending nodes
 func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 
 
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
@@ -325,8 +325,8 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(nodes)
 	json.NewEncoder(w).Encode(nodes)
 }
 }
 
 
-//A separate function to get all nodes, not just nodes for a particular network.
-//Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
+// A separate function to get all nodes, not just nodes for a particular network.
+// Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
 func getAllNodes(w http.ResponseWriter, r *http.Request) {
 func getAllNodes(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	user, err := logic.GetUser(r.Header.Get("user"))
 	user, err := logic.GetUser(r.Header.Get("user"))
@@ -372,7 +372,7 @@ func getUsersNodes(user models.User) ([]models.Node, error) {
 	return nodes, err
 	return nodes, err
 }
 }
 
 
-//Get an individual node. Nothin fancy here folks.
+// Get an individual node. Nothin fancy here folks.
 func getNode(w http.ResponseWriter, r *http.Request) {
 func getNode(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
@@ -406,10 +406,10 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(response)
 	json.NewEncoder(w).Encode(response)
 }
 }
 
 
-//Get the time that a network of nodes was last modified.
-//TODO: This needs to be refactored
-//Potential way to do this: On UpdateNode, set a new field for "LastModified"
-//If we go with the existing way, we need to at least set network.NodesLastModified on UpdateNode
+// Get the time that a network of nodes was last modified.
+// TODO: This needs to be refactored
+// Potential way to do this: On UpdateNode, set a new field for "LastModified"
+// If we go with the existing way, we need to at least set network.NodesLastModified on UpdateNode
 func getLastModified(w http.ResponseWriter, r *http.Request) {
 func getLastModified(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
@@ -736,9 +736,6 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	if relayupdate {
 	if relayupdate {
 		updatenodes := logic.UpdateRelay(node.Network, node.RelayAddrs, newNode.RelayAddrs)
 		updatenodes := logic.UpdateRelay(node.Network, node.RelayAddrs, newNode.RelayAddrs)
-		if err = logic.NetworkNodesUpdatePullChanges(node.Network); err != nil {
-			logger.Log(1, "error setting relay updates:", err.Error())
-		}
 		if len(updatenodes) > 0 {
 		if len(updatenodes) > 0 {
 			for _, relayedNode := range updatenodes {
 			for _, relayedNode := range updatenodes {
 				runUpdates(&relayedNode, false)
 				runUpdates(&relayedNode, false)

+ 4 - 0
database/database.go

@@ -56,6 +56,9 @@ const GENERATED_TABLE_NAME = "generated"
 // NODE_ACLS_TABLE_NAME - stores the node ACL rules
 // NODE_ACLS_TABLE_NAME - stores the node ACL rules
 const NODE_ACLS_TABLE_NAME = "nodeacls"
 const NODE_ACLS_TABLE_NAME = "nodeacls"
 
 
+// SSO_STATE_CACHE - holds sso session information for OAuth2 sign-ins
+const SSO_STATE_CACHE = "ssostatecache"
+
 // == ERROR CONSTS ==
 // == ERROR CONSTS ==
 
 
 // NO_RECORD - no singular result found
 // NO_RECORD - no singular result found
@@ -135,6 +138,7 @@ func createTables() {
 	createTable(SERVER_UUID_TABLE_NAME)
 	createTable(SERVER_UUID_TABLE_NAME)
 	createTable(GENERATED_TABLE_NAME)
 	createTable(GENERATED_TABLE_NAME)
 	createTable(NODE_ACLS_TABLE_NAME)
 	createTable(NODE_ACLS_TABLE_NAME)
+	createTable(SSO_STATE_CACHE)
 }
 }
 
 
 func createTable(tableName string) error {
 func createTable(tableName string) error {

+ 7 - 4
go.mod

@@ -15,13 +15,13 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.0
 	github.com/stretchr/testify v1.8.0
 	github.com/txn2/txeh v1.3.0
 	github.com/txn2/txeh v1.3.0
-	github.com/urfave/cli/v2 v2.11.1
+	github.com/urfave/cli/v2 v2.11.2
 	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
 	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
 	golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
 	golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
 	golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.0 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
-	gopkg.in/ini.v1 v1.66.6
+	gopkg.in/ini.v1 v1.67.0
 	gopkg.in/yaml.v3 v3.0.1
 	gopkg.in/yaml.v3 v3.0.1
 )
 )
 
 
@@ -35,7 +35,10 @@ require (
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 )
 
 
-require github.com/coreos/go-oidc/v3 v3.2.0
+require (
+	github.com/coreos/go-oidc/v3 v3.2.0
+	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
+)
 
 
 require (
 require (
 	cloud.google.com/go v0.81.0 // indirect
 	cloud.google.com/go v0.81.0 // indirect
@@ -61,7 +64,7 @@ require (
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
 	github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.7 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/josharian/native v1.0.0 // indirect

+ 8 - 6
go.sum

@@ -207,8 +207,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -445,8 +446,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
 github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
-github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE=
-github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
+github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA=
+github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -503,6 +504,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
 golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
@@ -740,7 +743,6 @@ golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
 golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
 golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
 golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
@@ -864,8 +866,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY=
 gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
-gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
 gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

+ 1 - 1
k8s/client/netclient-daemonset.yaml

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       hostNetwork: true
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient-go:v0.14.6
+        image: gravitl/netclient-go:v0.15.0
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/client/netclient.yaml

@@ -28,7 +28,7 @@ spec:
       #           - "<node label value>"
       #           - "<node label value>"
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.14.6
+        image: gravitl/netclient:v0.15.0
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/server/netmaker-server.yaml

@@ -83,7 +83,7 @@ spec:
           value: "Kubernetes"
           value: "Kubernetes"
         - name: VERBOSITY
         - name: VERBOSITY
           value: "3"
           value: "3"
-        image: gravitl/netmaker:v0.14.6
+        image: gravitl/netmaker:v0.15.0
         imagePullPolicy: Always
         imagePullPolicy: Always
         name: netmaker
         name: netmaker
         ports:
         ports:

+ 1 - 1
k8s/server/netmaker-ui.yaml

@@ -15,7 +15,7 @@ spec:
     spec:
     spec:
       containers:
       containers:
       - name: netmaker-ui
       - name: netmaker-ui
-        image: gravitl/netmaker-ui:v0.14.6
+        image: gravitl/netmaker-ui:v0.15.0
         ports:
         ports:
         - containerPort: 443
         - containerPort: 443
         env:
         env:

+ 0 - 3
logger/util.go

@@ -2,8 +2,6 @@ package logger
 
 
 import (
 import (
 	"strings"
 	"strings"
-
-	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
 // Verbosity - current logging verbosity level (optionally set)
 // Verbosity - current logging verbosity level (optionally set)
@@ -25,6 +23,5 @@ func getVerbose() int32 {
 	if Verbosity >= 1 && Verbosity <= 4 {
 	if Verbosity >= 1 && Verbosity <= 4 {
 		return int32(Verbosity)
 		return int32(Verbosity)
 	}
 	}
-	Verbosity = int(servercfg.GetVerbosity())
 	return int32(Verbosity)
 	return int32(Verbosity)
 }
 }

+ 50 - 0
logic/auth.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"time"
 
 
 	"github.com/go-playground/validator/v10"
 	"github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -270,3 +271,52 @@ func FetchAuthSecret(key string, secret string) (string, error) {
 	}
 	}
 	return record, nil
 	return record, nil
 }
 }
+
+// GetState - gets an SsoState from DB, if expired returns error
+func GetState(state string) (*models.SsoState, error) {
+	var s models.SsoState
+	record, err := database.FetchRecord(database.SSO_STATE_CACHE, state)
+	if err != nil {
+		return &s, err
+	}
+
+	if err = json.Unmarshal([]byte(record), &s); err != nil {
+		return &s, err
+	}
+
+	if s.IsExpired() {
+		return &s, fmt.Errorf("state expired")
+	}
+
+	return &s, nil
+}
+
+// SetState - sets a state with new expiration
+func SetState(state string) error {
+	s := models.SsoState{
+		Value:      state,
+		Expiration: time.Now().Add(models.DefaultExpDuration),
+	}
+
+	data, err := json.Marshal(&s)
+	if err != nil {
+		return err
+	}
+
+	return database.Insert(state, string(data), database.SSO_STATE_CACHE)
+}
+
+// IsStateValid - checks if given state is valid or not
+// deletes state after call is made to clean up, should only be called once per sign-in
+func IsStateValid(state string) (string, bool) {
+	s, err := GetState(state)
+	if s.Value != "" {
+		delState(state)
+	}
+	return s.Value, err == nil
+}
+
+// delState - removes a state from cache/db
+func delState(state string) error {
+	return database.DeleteRecord(database.SSO_STATE_CACHE, state)
+}

+ 149 - 36
logic/gateway.go

@@ -3,6 +3,7 @@ package logic
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
+	"fmt"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -20,6 +21,9 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	if node.OS != "linux" && node.OS != "freebsd" { // add in darwin later
 	if node.OS != "linux" && node.OS != "freebsd" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for egress gateways")
 		return models.Node{}, errors.New(node.OS + " is unsupported for egress gateways")
 	}
 	}
+	if node.OS == "linux" && node.FirewallInUse == models.FIREWALL_NONE {
+		return models.Node{}, errors.New("firewall is not supported for egress gateways")
+	}
 	if gateway.NatEnabled == "" {
 	if gateway.NatEnabled == "" {
 		gateway.NatEnabled = "yes"
 		gateway.NatEnabled = "yes"
 	}
 	}
@@ -30,20 +34,29 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	node.IsEgressGateway = "yes"
 	node.IsEgressGateway = "yes"
 	node.EgressGatewayRanges = gateway.Ranges
 	node.EgressGatewayRanges = gateway.Ranges
 	node.EgressGatewayNatEnabled = gateway.NatEnabled
 	node.EgressGatewayNatEnabled = gateway.NatEnabled
+	node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway
 	postUpCmd := ""
 	postUpCmd := ""
 	postDownCmd := ""
 	postDownCmd := ""
+	logger.Log(3, "creating egress gateway firewall in use is '", node.FirewallInUse, "'")
 	if node.OS == "linux" {
 	if node.OS == "linux" {
-		postUpCmd = "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT; "
-		postUpCmd += "iptables -A FORWARD -o " + node.Interface + " -j ACCEPT"
-		postDownCmd = "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT; "
-		postDownCmd += "iptables -D FORWARD -o " + node.Interface + " -j ACCEPT"
-
-		if node.EgressGatewayNatEnabled == "yes" {
-			postUpCmd += "; iptables -t nat -A POSTROUTING -o " + gateway.Interface + " -j MASQUERADE"
-			postDownCmd += "; iptables -t nat -D POSTROUTING -o " + gateway.Interface + " -j MASQUERADE"
+		switch node.FirewallInUse {
+		case models.FIREWALL_NFTABLES:
+			// nftables only supported on Linux
+			// assumes chains eg FORWARD and POSTROUTING already exist
+			logger.Log(3, "creating egress gateway nftables is present")
+			// down commands don't remove as removal of the rules leaves an empty chain while
+			// removing the chain with rules in it would remove all rules in that section (not safe
+			// if there are remaining rules on the host that need to stay).  In practice the chain is removed
+			// when non-empty even though the removal of a non-empty chain should not be possible per nftables wiki.
+			postUpCmd, postDownCmd = firewallNFTCommandsCreateEgress(node.Interface, gateway.Interface, gateway.Ranges, node.EgressGatewayNatEnabled)
+
+		default: // iptables assumed
+			logger.Log(3, "creating egress gateway nftables is not present")
+			postUpCmd, postDownCmd = firewallIPTablesCommandsCreateEgress(node.Interface, gateway.Interface, node.EgressGatewayNatEnabled)
 		}
 		}
 	}
 	}
 	if node.OS == "freebsd" {
 	if node.OS == "freebsd" {
+		// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
 		postUpCmd = "kldload ipfw ipfw_nat ; "
 		postUpCmd = "kldload ipfw ipfw_nat ; "
 		postUpCmd += "ipfw disable one_pass ; "
 		postUpCmd += "ipfw disable one_pass ; "
 		postUpCmd += "ipfw nat 1 config if " + gateway.Interface + " same_ports unreg_only reset ; "
 		postUpCmd += "ipfw nat 1 config if " + gateway.Interface + " same_ports unreg_only reset ; "
@@ -65,12 +78,12 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	}
 	}
 	if node.PostUp != "" {
 	if node.PostUp != "" {
 		if !strings.Contains(node.PostUp, postUpCmd) {
 		if !strings.Contains(node.PostUp, postUpCmd) {
-			postUpCmd = node.PostUp + "; " + postUpCmd
+			postUpCmd = node.PostUp + " ; " + postUpCmd
 		}
 		}
 	}
 	}
 	if node.PostDown != "" {
 	if node.PostDown != "" {
 		if !strings.Contains(node.PostDown, postDownCmd) {
 		if !strings.Contains(node.PostDown, postDownCmd) {
-			postDownCmd = node.PostDown + "; " + postDownCmd
+			postDownCmd = node.PostDown + " ; " + postDownCmd
 		}
 		}
 	}
 	}
 
 
@@ -84,9 +97,6 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	if err = database.Insert(node.ID, string(nodeData), database.NODES_TABLE_NAME); err != nil {
 	if err = database.Insert(node.ID, string(nodeData), database.NODES_TABLE_NAME); err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
-	if err = NetworkNodesUpdatePullChanges(node.Network); err != nil {
-		return models.Node{}, err
-	}
 	return node, nil
 	return node, nil
 }
 }
 
 
@@ -115,23 +125,27 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
 
 
 	node.IsEgressGateway = "no"
 	node.IsEgressGateway = "no"
 	node.EgressGatewayRanges = []string{}
 	node.EgressGatewayRanges = []string{}
+	node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
+	// needed in case we don't preserve a gateway (i.e., no ingress to preserve)
 	node.PostUp = ""
 	node.PostUp = ""
 	node.PostDown = ""
 	node.PostDown = ""
+
+	logger.Log(3, "deleting egress gateway firewall in use is '", node.FirewallInUse, "'")
 	if node.IsIngressGateway == "yes" { // check if node is still an ingress gateway before completely deleting postdown/up rules
 	if node.IsIngressGateway == "yes" { // check if node is still an ingress gateway before completely deleting postdown/up rules
+		// still have an ingress gateway so preserve it
 		if node.OS == "linux" {
 		if node.OS == "linux" {
-			node.PostUp = "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT ; "
-			node.PostUp += "iptables -A FORWARD -o " + node.Interface + " -j ACCEPT ; "
-			node.PostUp += "iptables -t nat -A POSTROUTING -o " + node.Interface + " -j MASQUERADE"
-			node.PostDown = "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT ; "
-			node.PostDown += "iptables -D FORWARD -o " + node.Interface + " -j ACCEPT ; "
-			node.PostDown += "iptables -t nat -D POSTROUTING -o " + node.Interface + " -j MASQUERADE"
-		}
-		if node.OS == "freebsd" {
-			node.PostUp = ""
-			node.PostDown = "ipfw delete 64000 ; "
-			node.PostDown += "ipfw delete 65534 ; "
-			node.PostDown += "kldunload ipfw_nat ipfw"
+			switch node.FirewallInUse {
+			case models.FIREWALL_NFTABLES:
+				// nftables only supported on Linux
+				// assumes chains eg FORWARD and POSTROUTING already exist
+				logger.Log(3, "deleting egress gateway nftables is present")
+				node.PostUp, node.PostDown = firewallNFTCommandsCreateIngress(node.Interface)
+			default:
+				logger.Log(3, "deleting egress gateway nftables is not present")
+				node.PostUp, node.PostDown = firewallIPTablesCommandsCreateIngress(node.Interface)
+			}
 		}
 		}
+		// no need to preserve ingress gateway on FreeBSD as ingress is not supported on that OS
 	}
 	}
 	node.SetLastModified()
 	node.SetLastModified()
 
 
@@ -142,19 +156,20 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
 	if err = database.Insert(node.ID, string(data), database.NODES_TABLE_NAME); err != nil {
 	if err = database.Insert(node.ID, string(data), database.NODES_TABLE_NAME); err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
-	if err = NetworkNodesUpdatePullChanges(network); err != nil {
-		return models.Node{}, err
-	}
 	return node, nil
 	return node, nil
 }
 }
 
 
 // CreateIngressGateway - creates an ingress gateway
 // CreateIngressGateway - creates an ingress gateway
 func CreateIngressGateway(netid string, nodeid string) (models.Node, error) {
 func CreateIngressGateway(netid string, nodeid string) (models.Node, error) {
 
 
+	var postUpCmd, postDownCmd string
 	node, err := GetNodeByID(nodeid)
 	node, err := GetNodeByID(nodeid)
 	if node.OS != "linux" { // add in darwin later
 	if node.OS != "linux" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for ingress gateways")
 		return models.Node{}, errors.New(node.OS + " is unsupported for ingress gateways")
 	}
 	}
+	if node.OS == "linux" && node.FirewallInUse == models.FIREWALL_NONE {
+		return models.Node{}, errors.New("firewall is not supported for ingress gateways")
+	}
 
 
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
@@ -166,20 +181,26 @@ func CreateIngressGateway(netid string, nodeid string) (models.Node, error) {
 	}
 	}
 	node.IsIngressGateway = "yes"
 	node.IsIngressGateway = "yes"
 	node.IngressGatewayRange = network.AddressRange
 	node.IngressGatewayRange = network.AddressRange
-	postUpCmd := "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT ; "
-	postUpCmd += "iptables -A FORWARD -o " + node.Interface + " -j ACCEPT ; "
-	postUpCmd += "iptables -t nat -A POSTROUTING -o " + node.Interface + " -j MASQUERADE"
-	postDownCmd := "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT ; "
-	postDownCmd += "iptables -D FORWARD -o " + node.Interface + " -j ACCEPT ; "
-	postDownCmd += "iptables -t nat -D POSTROUTING -o " + node.Interface + " -j MASQUERADE"
+	logger.Log(3, "creating ingress gateway firewall in use is '", node.FirewallInUse, "'")
+	switch node.FirewallInUse {
+	case models.FIREWALL_NFTABLES:
+		// nftables only supported on Linux
+		// assumes chains eg FORWARD and POSTROUTING already exist
+		logger.Log(3, "creating ingress gateway nftables is present")
+		postUpCmd, postDownCmd = firewallNFTCommandsCreateIngress(node.Interface)
+	default:
+		logger.Log(3, "creating ingress gateway using nftables is not present")
+		postUpCmd, postDownCmd = firewallIPTablesCommandsCreateIngress(node.Interface)
+	}
+
 	if node.PostUp != "" {
 	if node.PostUp != "" {
 		if !strings.Contains(node.PostUp, postUpCmd) {
 		if !strings.Contains(node.PostUp, postUpCmd) {
-			postUpCmd = node.PostUp + "; " + postUpCmd
+			postUpCmd = node.PostUp + " ; " + postUpCmd
 		}
 		}
 	}
 	}
 	if node.PostDown != "" {
 	if node.PostDown != "" {
 		if !strings.Contains(node.PostDown, postDownCmd) {
 		if !strings.Contains(node.PostDown, postDownCmd) {
-			postDownCmd = node.PostDown + "; " + postDownCmd
+			postDownCmd = node.PostDown + " ; " + postDownCmd
 		}
 		}
 	}
 	}
 	node.SetLastModified()
 	node.SetLastModified()
@@ -214,12 +235,26 @@ func DeleteIngressGateway(networkName string, nodeid string) (models.Node, error
 	if err = DeleteGatewayExtClients(node.ID, networkName); err != nil {
 	if err = DeleteGatewayExtClients(node.ID, networkName); err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
+	logger.Log(3, "deleting ingress gateway")
 
 
 	node.UDPHolePunch = network.DefaultUDPHolePunch
 	node.UDPHolePunch = network.DefaultUDPHolePunch
 	node.LastModified = time.Now().Unix()
 	node.LastModified = time.Now().Unix()
 	node.IsIngressGateway = "no"
 	node.IsIngressGateway = "no"
 	node.IngressGatewayRange = ""
 	node.IngressGatewayRange = ""
 
 
+	// default to removing postup and postdown
+	node.PostUp = ""
+	node.PostDown = ""
+
+	logger.Log(3, "deleting ingress gateway firewall in use is '", node.FirewallInUse, "' and isEgressGateway is", node.IsEgressGateway)
+	if node.EgressGatewayRequest.NodeID != "" {
+		_, err := CreateEgressGateway(node.EgressGatewayRequest)
+		if err != nil {
+			logger.Log(0, fmt.Sprintf("failed to create egress gateway on node [%s] on network [%s]: %v",
+				node.EgressGatewayRequest.NodeID, node.EgressGatewayRequest.NetID, err))
+		}
+	}
+
 	data, err := json.Marshal(&node)
 	data, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
@@ -248,3 +283,81 @@ func DeleteGatewayExtClients(gatewayID string, networkName string) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+// firewallNFTCommandsCreateIngress - used to centralize firewall command maintenance for creating an ingress gateway using the nftables firewall.
+func firewallNFTCommandsCreateIngress(networkInterface string) (string, string) {
+	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
+	postUp := "nft add table ip filter ; "
+	postUp += "nft add chain ip filter FORWARD ; "
+	postUp += "nft add rule ip filter FORWARD iifname " + networkInterface + " counter accept ; "
+	postUp += "nft add rule ip filter FORWARD oifname " + networkInterface + " counter accept ; "
+	postUp += "nft add table nat ; "
+	postUp += "nft add chain nat POSTROUTING ; "
+	postUp += "nft add rule ip nat POSTROUTING oifname " + networkInterface + " counter masquerade"
+
+	// doesn't remove potentially empty tables or chains
+	postDown := "nft flush table filter ; "
+	postDown += "nft flush table nat ; "
+
+	return postUp, postDown
+}
+
+// firewallNFTCommandsCreateEgress - used to centralize firewall command maintenance for creating an egress gateway using the nftables firewall.
+func firewallNFTCommandsCreateEgress(networkInterface string, gatewayInterface string, gatewayranges []string, egressNatEnabled string) (string, string) {
+	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
+	postUp := "nft add table ip filter ; "
+	postUp += "nft add chain ip filter forward ; "
+	postUp += "nft add rule filter forward ct state related,established accept ; "
+	postUp += "nft add rule ip filter forward iifname " + networkInterface + " accept ; "
+	postUp += "nft add rule ip filter forward oifname " + networkInterface + " accept ; "
+	postUp += "nft add table nat ; "
+	postUp += "nft 'add chain ip nat prerouting { type nat hook prerouting priority 0 ;}' ; "
+	postUp += "nft 'add chain ip nat postrouting { type nat hook postrouting priority 0 ;}' ; "
+	for _, networkCIDR := range gatewayranges {
+		postUp += "nft add rule nat postrouting iifname " + networkInterface + " oifname " + gatewayInterface + " ip saddr " + networkCIDR + " masquerade ; "
+	}
+
+	postDown := "nft flush table filter ; "
+
+	if egressNatEnabled == "yes" {
+		postUp += "nft add table nat ; "
+		postUp += "nft add chain nat POSTROUTING ; "
+		postUp += "nft add rule ip nat POSTROUTING oifname " + gatewayInterface + " counter masquerade ; "
+
+		postDown += "nft flush table nat ; "
+	}
+
+	return postUp, postDown
+}
+
+// firewallIPTablesCommandsCreateIngress - used to centralize firewall command maintenance for creating an ingress gateway using the iptables firewall.
+func firewallIPTablesCommandsCreateIngress(networkInterface string) (string, string) {
+	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
+	postUp := "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
+	postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT ; "
+	postUp += "iptables -t nat -A POSTROUTING -o " + networkInterface + " -j MASQUERADE"
+
+	// doesn't remove potentially empty tables or chains
+	postDown := "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
+	postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT ; "
+	postDown += "iptables -t nat -D POSTROUTING -o " + networkInterface + " -j MASQUERADE"
+
+	return postUp, postDown
+}
+
+// firewallIPTablesCommandsCreateEgress - used to centralize firewall command maintenance for creating an egress gateway using the iptables firewall.
+func firewallIPTablesCommandsCreateEgress(networkInterface string, gatewayInterface string, egressNatEnabled string) (string, string) {
+	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
+	postUp := "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
+	postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT"
+	postDown := "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
+	postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT"
+
+	if egressNatEnabled == "yes" {
+		postUp += " ; iptables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE"
+		postDown += " ; iptables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE"
+	}
+
+	return postUp, postDown
+
+}

+ 0 - 30
logic/networks.go

@@ -91,36 +91,6 @@ func CreateNetwork(network models.Network) (models.Network, error) {
 	return network, nil
 	return network, nil
 }
 }
 
 
-// NetworkNodesUpdatePullChanges - tells nodes on network to pull
-func NetworkNodesUpdatePullChanges(networkName string) error {
-
-	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != nil {
-		if database.IsEmptyRecord(err) {
-			return nil
-		}
-		return err
-	}
-
-	for _, value := range collections {
-		var node models.Node
-		err := json.Unmarshal([]byte(value), &node)
-		if err != nil {
-			fmt.Println("error in node address assignment!")
-			return err
-		}
-		if node.Network == networkName {
-			data, err := json.Marshal(&node)
-			if err != nil {
-				return err
-			}
-			database.Insert(node.ID, string(data), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
 // GetNetworkNonServerNodeCount - get number of network non server nodes
 // GetNetworkNonServerNodeCount - get number of network non server nodes
 func GetNetworkNonServerNodeCount(networkName string) (int, error) {
 func GetNetworkNonServerNodeCount(networkName string) (int, error) {
 
 

+ 3 - 2
logic/nodes.go

@@ -185,7 +185,7 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error {
 		// ignoring for now, could hit a nil pointer if delete called twice
 		// ignoring for now, could hit a nil pointer if delete called twice
 		logger.Log(2, "attempted to remove node ACL for node", node.Name, node.ID)
 		logger.Log(2, "attempted to remove node ACL for node", node.Name, node.ID)
 	}
 	}
-	removeZombie <- node.ID
+	// removeZombie <- node.ID
 	if node.IsServer == "yes" {
 	if node.IsServer == "yes" {
 		return removeLocalServer(node)
 		return removeLocalServer(node)
 	}
 	}
@@ -288,7 +288,7 @@ func CreateNode(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	CheckZombies(node)
+	// CheckZombies(node)
 
 
 	nodebytes, err := json.Marshal(&node)
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {
@@ -427,6 +427,7 @@ func SetNodeDefaults(node *models.Node) {
 	node.SetDefaultIngressGateway()
 	node.SetDefaultIngressGateway()
 	node.SetDefaulIsPending()
 	node.SetDefaulIsPending()
 	node.SetDefaultMTU()
 	node.SetDefaultMTU()
+	node.SetDefaultNFTablesPresent()
 	node.SetDefaultIsRelayed()
 	node.SetDefaultIsRelayed()
 	node.SetDefaultIsRelay()
 	node.SetDefaultIsRelay()
 	node.SetDefaultIsDocker()
 	node.SetDefaultIsDocker()

+ 45 - 4
logic/peers.go

@@ -16,6 +16,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slices"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
@@ -268,6 +269,14 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 	if peer.IsEgressGateway == "yes" {
 	if peer.IsEgressGateway == "yes" {
 		//hasGateway = true
 		//hasGateway = true
 		egressIPs := getEgressIPs(node, peer)
 		egressIPs := getEgressIPs(node, peer)
+		// remove internet gateway if server
+		if node.IsServer == "yes" {
+			for i := len(egressIPs) - 1; i >= 0; i-- {
+				if egressIPs[i].IP.String() == "0.0.0.0/0" || egressIPs[i].IP.String() == "::/0" {
+					egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
+				}
+			}
+		}
 		allowedips = append(allowedips, egressIPs...)
 		allowedips = append(allowedips, egressIPs...)
 	}
 	}
 
 
@@ -324,6 +333,16 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 				extAllowedIPs := getEgressIPs(node, relayedNode)
 				extAllowedIPs := getEgressIPs(node, relayedNode)
 				allowedips = append(allowedips, extAllowedIPs...)
 				allowedips = append(allowedips, extAllowedIPs...)
 			}
 			}
+			if relayedNode.IsIngressGateway == "yes" {
+				extPeers, err := getExtPeers(relayedNode)
+				if err == nil {
+					for _, extPeer := range extPeers {
+						allowedips = append(allowedips, extPeer.AllowedIPs...)
+					}
+				} else {
+					logger.Log(0, "failed to retrieve extclients from relayed ingress", err.Error())
+				}
+			}
 		}
 		}
 	}
 	}
 	return allowedips
 	return allowedips
@@ -408,7 +427,15 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string)
 	//delete egressrange from allowedip if we are egress gateway
 	//delete egressrange from allowedip if we are egress gateway
 	if node.IsEgressGateway == "yes" {
 	if node.IsEgressGateway == "yes" {
 		for i := len(allowedips) - 1; i >= 0; i-- {
 		for i := len(allowedips) - 1; i >= 0; i-- {
-			if StringSliceContains(node.EgressGatewayRanges, allowedips[i].IP.String()) {
+			if StringSliceContains(node.EgressGatewayRanges, allowedips[i].String()) {
+				allowedips = append(allowedips[:i], allowedips[i+1:]...)
+			}
+		}
+	}
+	//delete extclients from allowedip if we are ingress gateway
+	if node.IsIngressGateway == "yes" {
+		for i := len(allowedips) - 1; i >= 0; i-- {
+			if strings.Contains(node.IngressGatewayRange, allowedips[i].IP.String()) {
 				allowedips = append(allowedips[:i], allowedips[i+1:]...)
 				allowedips = append(allowedips[:i], allowedips[i+1:]...)
 			}
 			}
 		}
 		}
@@ -458,6 +485,15 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string)
 	if relay.IsServer == "yes" {
 	if relay.IsServer == "yes" {
 		serverNodeAddresses = append(serverNodeAddresses, models.ServerAddr{IsLeader: IsLeader(relay), Address: relay.Address})
 		serverNodeAddresses = append(serverNodeAddresses, models.ServerAddr{IsLeader: IsLeader(relay), Address: relay.Address})
 	}
 	}
+	//if ingress add extclients
+	if node.IsIngressGateway == "yes" {
+		extPeers, err := getExtPeers(node)
+		if err == nil {
+			peers = append(peers, extPeers...)
+		} else {
+			logger.Log(2, "could not retrieve ext peers for ", node.Name, err.Error())
+		}
+	}
 	peerUpdate.Network = node.Network
 	peerUpdate.Network = node.Network
 	peerUpdate.ServerVersion = servercfg.Version
 	peerUpdate.ServerVersion = servercfg.Version
 	peerUpdate.Peers = peers
 	peerUpdate.Peers = peers
@@ -467,6 +503,11 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string)
 }
 }
 
 
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
+	//check for internet gateway
+	internetGateway := false
+	if slices.Contains(peer.EgressGatewayRanges, "0.0.0.0/0") || slices.Contains(peer.EgressGatewayRanges, "::0") {
+		internetGateway = true
+	}
 	allowedips := []net.IPNet{}
 	allowedips := []net.IPNet{}
 	for _, iprange := range peer.EgressGatewayRanges { // go through each cidr for egress gateway
 	for _, iprange := range peer.EgressGatewayRanges { // go through each cidr for egress gateway
 		_, ipnet, err := net.ParseCIDR(iprange) // confirming it's valid cidr
 		_, ipnet, err := net.ParseCIDR(iprange) // confirming it's valid cidr
@@ -474,13 +515,13 @@ func getEgressIPs(node, peer *models.Node) []net.IPNet {
 			logger.Log(1, "could not parse gateway IP range. Not adding ", iprange)
 			logger.Log(1, "could not parse gateway IP range. Not adding ", iprange)
 			continue // if can't parse CIDR
 			continue // if can't parse CIDR
 		}
 		}
-		nodeEndpointArr := strings.Split(peer.Endpoint, ":") // getting the public ip of node
-		if ipnet.Contains(net.ParseIP(nodeEndpointArr[0])) { // ensuring egress gateway range does not contain endpoint of node
+		nodeEndpointArr := strings.Split(peer.Endpoint, ":")                     // getting the public ip of node
+		if ipnet.Contains(net.ParseIP(nodeEndpointArr[0])) && !internetGateway { // ensuring egress gateway range does not contain endpoint of node
 			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", node.Endpoint, ", omitting")
 			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", node.Endpoint, ", omitting")
 			continue // skip adding egress range if overlaps with node's ip
 			continue // skip adding egress range if overlaps with node's ip
 		}
 		}
 		// TODO: Could put in a lot of great logic to avoid conflicts / bad routes
 		// TODO: Could put in a lot of great logic to avoid conflicts / bad routes
-		if ipnet.Contains(net.ParseIP(node.LocalAddress)) { // ensuring egress gateway range does not contain public ip of node
+		if ipnet.Contains(net.ParseIP(node.LocalAddress)) && !internetGateway { // ensuring egress gateway range does not contain public ip of node
 			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", node.LocalAddress, ", omitting")
 			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", node.LocalAddress, ", omitting")
 			continue // skip adding egress range if overlaps with node's local ip
 			continue // skip adding egress range if overlaps with node's local ip
 		}
 		}

+ 0 - 6
logic/relay.go

@@ -41,9 +41,6 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error)
 	if err != nil {
 	if err != nil {
 		return returnnodes, node, err
 		return returnnodes, node, err
 	}
 	}
-	if err = NetworkNodesUpdatePullChanges(node.Network); err != nil {
-		return returnnodes, models.Node{}, err
-	}
 	return returnnodes, node, nil
 	return returnnodes, node, nil
 }
 }
 
 
@@ -125,8 +122,5 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 	if err = database.Insert(nodeid, string(data), database.NODES_TABLE_NAME); err != nil {
 	if err = database.Insert(nodeid, string(data), database.NODES_TABLE_NAME); err != nil {
 		return returnnodes, models.Node{}, err
 		return returnnodes, models.Node{}, err
 	}
 	}
-	if err = NetworkNodesUpdatePullChanges(network); err != nil {
-		return returnnodes, models.Node{}, err
-	}
 	return returnnodes, node, nil
 	return returnnodes, node, nil
 }
 }

+ 3 - 32
logic/server.go

@@ -11,6 +11,7 @@ import (
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/netclient/wireguard"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
@@ -147,13 +148,13 @@ func ServerJoin(networkSettings *models.Network) (models.Node, error) {
 		return returnNode, err
 		return returnNode, err
 	}
 	}
 
 
-	peers, hasGateway, gateways, err := GetServerPeers(node)
+	peers, err := GetPeerUpdate(node)
 	if err != nil && !ncutils.IsEmptyRecord(err) {
 	if err != nil && !ncutils.IsEmptyRecord(err) {
 		logger.Log(1, "failed to retrieve peers")
 		logger.Log(1, "failed to retrieve peers")
 		return returnNode, err
 		return returnNode, err
 	}
 	}
 
 
-	err = initWireguard(node, privateKey, peers[:], hasGateway, gateways[:])
+	err = wireguard.InitWireguard(node, privateKey, peers.Peers, false)
 	if err != nil {
 	if err != nil {
 		return returnNode, err
 		return returnNode, err
 	}
 	}
@@ -187,36 +188,6 @@ func ServerUpdate(serverNode *models.Node, ifaceDelta bool) error {
 	return serverPush(serverNode)
 	return serverPush(serverNode)
 }
 }
 
 
-/**
- * Below function needs major refactor
- *
- */
-
-// GetServerPeers - gets peers of server
-func GetServerPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, bool, []string, error) {
-	update, err := GetPeerUpdate(serverNode)
-	if err != nil {
-		return []wgtypes.PeerConfig{}, false, []string{}, err
-	}
-
-	// this is temporary code, should be removed by 0.14.4
-	// refactor server routing to use client-side routing code
-	var hasGateways = false
-	var gateways = []string{}
-	nodes, err := GetNetworkNodes(serverNode.Network)
-	if err == nil {
-		for _, node := range nodes {
-			if node.IsEgressGateway == "yes" && !IsLocalServer(&node) {
-				gateways = append(gateways, node.EgressGatewayRanges...)
-			}
-		}
-		hasGateways = len(gateways) > 0
-	}
-	// end temporary code
-
-	return update.Peers, hasGateways, gateways, nil
-}
-
 // == Private ==
 // == Private ==
 
 
 func isDeleteError(err error) bool {
 func isDeleteError(err error) bool {

+ 3 - 200
logic/wireguard.go

@@ -1,18 +1,12 @@
 package logic
 package logic
 
 
 import (
 import (
-	"errors"
-	"fmt"
-	"net"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
-	"strconv"
 	"strings"
 	"strings"
-	"time"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl"
@@ -123,177 +117,6 @@ func getSystemPeers(node *models.Node) (map[string]string, error) {
 	}
 	}
 	return peers, nil
 	return peers, nil
 }
 }
-
-func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
-
-	key, err := wgtypes.ParseKey(privkey)
-	if err != nil {
-		return err
-	}
-
-	wgclient, err := wgctrl.New()
-	if err != nil {
-		return err
-	}
-	defer wgclient.Close()
-
-	var ifacename string
-	if node.Interface != "" {
-		ifacename = node.Interface
-	} else {
-		logger.Log(2, "no server interface provided to configure")
-	}
-	if node.Address == "" {
-		logger.Log(2, "no server address to provided configure")
-	}
-
-	if ncutils.IsKernel() {
-		logger.Log(2, "setting kernel device", ifacename)
-		network, err := GetNetwork(node.Network)
-		if err != nil {
-			logger.Log(0, "failed to get network"+err.Error())
-			return err
-		}
-		var address4 string
-		var address6 string
-		var mask4 string
-		var mask6 string
-		if network.AddressRange != "" {
-			net := strings.Split(network.AddressRange, "/")
-			mask4 = net[len(net)-1]
-			address4 = node.Address
-		}
-		if network.AddressRange6 != "" {
-			net := strings.Split(network.AddressRange6, "/")
-			mask6 = net[len(net)-1]
-			address6 = node.Address6
-		}
-
-		setKernelDevice(ifacename, address4, mask4, address6, mask6)
-	}
-
-	nodeport := int(node.ListenPort)
-	var conf = wgtypes.Config{
-		PrivateKey:   &key,
-		ListenPort:   &nodeport,
-		ReplacePeers: true,
-		Peers:        peers,
-	}
-
-	if !ncutils.IsKernel() {
-		if err := wireguard.WriteWgConfig(node, key.String(), peers); err != nil {
-			logger.Log(1, "error writing wg conf file: ", err.Error())
-			return err
-		}
-		// spin up userspace + apply the conf file
-		var deviceiface = ifacename
-		confPath := ncutils.GetNetclientPathSpecific() + ifacename + ".conf"
-		d, _ := wgclient.Device(deviceiface)
-		for d != nil && d.Name == deviceiface {
-			_ = RemoveConf(ifacename, false) // remove interface first
-			time.Sleep(time.Second >> 2)
-			d, _ = wgclient.Device(deviceiface)
-		}
-		time.Sleep(time.Second >> 2)
-		err = applyWGQuickConf(confPath)
-		if err != nil {
-			logger.Log(1, "failed to create wireguard interface")
-			return err
-		}
-	} else {
-		ipExec, err := exec.LookPath("ip")
-		if err != nil {
-			return err
-		}
-
-		_, err = wgclient.Device(ifacename)
-		if err != nil {
-			if os.IsNotExist(err) {
-				fmt.Println("Device does not exist: ")
-				fmt.Println(err)
-			} else {
-				return errors.New("Unknown config error: " + err.Error())
-			}
-		}
-
-		err = wgclient.ConfigureDevice(ifacename, conf)
-		if err != nil {
-			if os.IsNotExist(err) {
-				fmt.Println("Device does not exist: ")
-				fmt.Println(err)
-			} else {
-				fmt.Printf("This is inconvenient: %v", err)
-			}
-		}
-
-		if _, err := ncutils.RunCmd(ipExec+" link set down dev "+ifacename, false); err != nil {
-			logger.Log(2, "attempted to remove interface before editing")
-			return err
-		}
-
-		if node.PostDown != "" {
-			runcmds := strings.Split(node.PostDown, "; ")
-			_ = ncutils.RunCmds(runcmds, false)
-		}
-		// set MTU of node interface
-		if _, err := ncutils.RunCmd(ipExec+" link set mtu "+strconv.Itoa(int(node.MTU))+" up dev "+ifacename, true); err != nil {
-			logger.Log(2, "failed to create interface with mtu", strconv.Itoa(int(node.MTU)), "-", ifacename)
-			return err
-		}
-
-		if node.PostUp != "" {
-			runcmds := strings.Split(node.PostUp, "; ")
-			_ = ncutils.RunCmds(runcmds, true)
-		}
-		if hasGateway {
-			for _, gateway := range gateways {
-				_, _ = ncutils.RunCmd(ipExec+" -4 route add "+gateway+" dev "+ifacename, true)
-			}
-		}
-		if node.Address != "" {
-			logger.Log(1, "adding address:", node.Address)
-			_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address+"/32", true)
-		}
-		if node.Address6 != "" {
-			logger.Log(1, "adding address6:", node.Address6)
-			_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address6+"/128", true)
-		}
-		wireguard.SetPeers(ifacename, node, peers)
-	}
-
-	if node.IsServer == "yes" {
-		setServerRoutes(node.Interface, node.Network)
-	}
-
-	return err
-}
-
-func setKernelDevice(ifacename, address4, mask4, address6, mask6 string) error {
-	ipExec, err := exec.LookPath("ip")
-	if err != nil {
-		return err
-	}
-
-	// == best effort ==
-	ncutils.RunCmd("ip link delete dev "+ifacename, false)
-	ncutils.RunCmd(ipExec+" link add dev "+ifacename+" type wireguard", true)
-	if address4 != "" {
-		ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+address4+"/"+mask4, true)
-	}
-	if address6 != "" {
-		ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+address6+"/"+mask6, true)
-	}
-
-	return nil
-}
-
-func applyWGQuickConf(confPath string) error {
-	if _, err := ncutils.RunCmd("wg-quick up "+confPath, true); err != nil {
-		return err
-	}
-	return nil
-}
-
 func removeWGQuickConf(confPath string, printlog bool) error {
 func removeWGQuickConf(confPath string, printlog bool) error {
 	if _, err := ncutils.RunCmd("wg-quick down "+confPath, printlog); err != nil {
 	if _, err := ncutils.RunCmd("wg-quick down "+confPath, printlog); err != nil {
 		return err
 		return err
@@ -302,8 +125,7 @@ func removeWGQuickConf(confPath string, printlog bool) error {
 }
 }
 
 
 func setWGConfig(node *models.Node, peerupdate bool) error {
 func setWGConfig(node *models.Node, peerupdate bool) error {
-
-	peers, hasGateway, gateways, err := GetServerPeers(node)
+	peers, err := GetPeerUpdate(node)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -312,15 +134,14 @@ func setWGConfig(node *models.Node, peerupdate bool) error {
 		return err
 		return err
 	}
 	}
 	if peerupdate {
 	if peerupdate {
-		if err := wireguard.SetPeers(node.Interface, node, peers); err != nil {
+		if err := wireguard.SetPeers(node.Interface, node, peers.Peers); err != nil {
 			logger.Log(0, "error updating peers", err.Error())
 			logger.Log(0, "error updating peers", err.Error())
 		}
 		}
 		logger.Log(2, "updated peers on server", node.Name)
 		logger.Log(2, "updated peers on server", node.Name)
 	} else {
 	} else {
-		err = initWireguard(node, privkey, peers[:], hasGateway, gateways[:])
+		err = wireguard.InitWireguard(node, privkey, peers.Peers, false)
 		logger.Log(3, "finished setting wg config on server", node.Name)
 		logger.Log(3, "finished setting wg config on server", node.Name)
 	}
 	}
-	peers = nil
 	return err
 	return err
 }
 }
 
 
@@ -392,21 +213,3 @@ func removeLocalServer(node *models.Node) error {
 	}
 	}
 	return err
 	return err
 }
 }
-
-func setServerRoutes(iface, network string) {
-	parentNetwork, err := GetParentNetwork(network)
-	if err == nil {
-		if parentNetwork.AddressRange != "" {
-			ip, cidr, err := net.ParseCIDR(parentNetwork.AddressRange)
-			if err == nil {
-				local.SetCIDRRoute(iface, ip.String(), cidr)
-			}
-		}
-		if parentNetwork.AddressRange6 != "" {
-			ip, cidr, err := net.ParseCIDR(parentNetwork.AddressRange6)
-			if err == nil {
-				local.SetCIDRRoute(iface, ip.String(), cidr)
-			}
-		}
-	}
-}

+ 22 - 18
logic/zombie.go

@@ -17,8 +17,8 @@ const (
 
 
 var (
 var (
 	zombies      []string
 	zombies      []string
-	removeZombie chan string = make(chan (string))
-	newZombie    chan string = make(chan (string))
+	removeZombie chan string = make(chan (string), 10)
+	newZombie    chan string = make(chan (string), 10)
 )
 )
 
 
 // CheckZombies - checks if new node has same macaddress as existing node
 // CheckZombies - checks if new node has same macaddress as existing node
@@ -47,30 +47,34 @@ func ManageZombies(ctx context.Context) {
 			zombies = append(zombies, id)
 			zombies = append(zombies, id)
 		case id := <-removeZombie:
 		case id := <-removeZombie:
 			found := false
 			found := false
-			for i, zombie := range zombies {
-				if zombie == id {
-					logger.Log(1, "removing zombie from quaratine list", zombie)
-					zombies = append(zombies[:i], zombies[i+1:]...)
-					found = true
+			if len(zombies) > 0 {
+				for i := len(zombies) - 1; i >= 0; i-- {
+					if zombies[i] == id {
+						logger.Log(1, "removing zombie from quaratine list", zombies[i])
+						zombies = append(zombies[:i], zombies[i+1:]...)
+						found = true
+					}
 				}
 				}
 			}
 			}
 			if !found {
 			if !found {
 				logger.Log(3, "no zombies found")
 				logger.Log(3, "no zombies found")
 			}
 			}
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
-			for i, zombie := range zombies {
-				node, err := GetNodeByID(zombie)
-				if err != nil {
-					logger.Log(1, "error retrieving zombie node", zombie, err.Error())
-					continue
-				}
-				if time.Since(time.Unix(node.LastCheckIn, 0)) > time.Minute*ZOMBIE_DELETE_TIME {
-					if err := DeleteNodeByID(&node, true); err != nil {
-						logger.Log(1, "error deleting zombie node", zombie, err.Error())
+			if len(zombies) > 0 {
+				for i := len(zombies) - 1; i >= 0; i-- {
+					node, err := GetNodeByID(zombies[i])
+					if err != nil {
+						logger.Log(1, "error retrieving zombie node", zombies[i], err.Error())
 						continue
 						continue
 					}
 					}
-					logger.Log(1, "deleting zombie node", node.Name)
-					zombies = append(zombies[:i], zombies[i+1:]...)
+					if time.Since(time.Unix(node.LastCheckIn, 0)) > time.Minute*ZOMBIE_DELETE_TIME {
+						if err := DeleteNodeByID(&node, true); err != nil {
+							logger.Log(1, "error deleting zombie node", zombies[i], err.Error())
+							continue
+						}
+						logger.Log(1, "deleting zombie node", node.Name)
+						zombies = append(zombies[:i], zombies[i+1:]...)
+					}
 				}
 				}
 			}
 			}
 		}
 		}

+ 6 - 0
main.go

@@ -42,6 +42,7 @@ func main() {
 	fmt.Println(models.RetrieveLogo()) // print the logo
 	fmt.Println(models.RetrieveLogo()) // print the logo
 	initialize()                       // initial db and acls; gen cert if required
 	initialize()                       // initial db and acls; gen cert if required
 	setGarbageCollection()
 	setGarbageCollection()
+	setVerbosity()
 	defer database.CloseDB()
 	defer database.CloseDB()
 	startControllers() // start the api endpoint and mq
 	startControllers() // start the api endpoint and mq
 }
 }
@@ -182,6 +183,11 @@ func runMessageQueue(wg *sync.WaitGroup) {
 	client.Disconnect(250)
 	client.Disconnect(250)
 }
 }
 
 
+func setVerbosity() {
+	verbose := int(servercfg.GetVerbosity())
+	logger.Verbosity = verbose
+}
+
 func setGarbageCollection() {
 func setGarbageCollection() {
 	_, gcset := os.LookupEnv("GOGC")
 	_, gcset := os.LookupEnv("GOGC")
 	if !gcset {
 	if !gcset {

+ 65 - 49
models/node.go

@@ -28,6 +28,12 @@ const (
 	NODE_NOOP = "noop"
 	NODE_NOOP = "noop"
 	// NODE_FORCE_UPDATE - indicates a node should pull all changes
 	// NODE_FORCE_UPDATE - indicates a node should pull all changes
 	NODE_FORCE_UPDATE = "force"
 	NODE_FORCE_UPDATE = "force"
+	// FIREWALL_IPTABLES - indicates that iptables is the firewall in use
+	FIREWALL_IPTABLES = "iptables"
+	// FIREWALL_NFTABLES - indicates nftables is in use (Linux only)
+	FIREWALL_NFTABLES = "nftables"
+	// FIREWALL_NONE - indicates that no supported firewall in use
+	FIREWALL_NONE = "none"
 )
 )
 
 
 var seededRand *rand.Rand = rand.New(
 var seededRand *rand.Rand = rand.New(
@@ -35,55 +41,58 @@ var seededRand *rand.Rand = rand.New(
 
 
 // Node - struct for node model
 // Node - struct for node model
 type Node struct {
 type Node struct {
-	ID                      string   `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5" validate:"id_unique`
-	Address                 string   `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
-	Address6                string   `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
-	LocalAddress            string   `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty,ip"`
-	Name                    string   `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"`
-	NetworkSettings         Network  `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
-	ListenPort              int32    `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
-	LocalListenPort         int32    `json:"locallistenport" bson:"locallistenport" yaml:"locallistenport" validate:"numeric,min=0,max=65535"`
-	PublicKey               string   `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
-	Endpoint                string   `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`
-	PostUp                  string   `json:"postup" bson:"postup" yaml:"postup"`
-	PostDown                string   `json:"postdown" bson:"postdown" yaml:"postdown"`
-	AllowedIPs              []string `json:"allowedips" bson:"allowedips" yaml:"allowedips"`
-	PersistentKeepalive     int32    `json:"persistentkeepalive" bson:"persistentkeepalive" yaml:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
-	IsHub                   string   `json:"ishub" bson:"ishub" yaml:"ishub" validate:"checkyesorno"`
-	AccessKey               string   `json:"accesskey" bson:"accesskey" yaml:"accesskey"`
-	Interface               string   `json:"interface" bson:"interface" yaml:"interface"`
-	LastModified            int64    `json:"lastmodified" bson:"lastmodified" yaml:"lastmodified"`
-	ExpirationDateTime      int64    `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
-	LastPeerUpdate          int64    `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
-	LastCheckIn             int64    `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
-	MacAddress              string   `json:"macaddress" bson:"macaddress" yaml:"macaddress"`
-	Password                string   `json:"password" bson:"password" yaml:"password" validate:"required,min=6"`
-	Network                 string   `json:"network" bson:"network" yaml:"network" validate:"network_exists"`
-	IsRelayed               string   `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
-	IsPending               string   `json:"ispending" bson:"ispending" yaml:"ispending"`
-	IsRelay                 string   `json:"isrelay" bson:"isrelay" yaml:"isrelay" validate:"checkyesorno"`
-	IsDocker                string   `json:"isdocker" bson:"isdocker" yaml:"isdocker" validate:"checkyesorno"`
-	IsK8S                   string   `json:"isk8s" bson:"isk8s" yaml:"isk8s" validate:"checkyesorno"`
-	IsEgressGateway         string   `json:"isegressgateway" bson:"isegressgateway" yaml:"isegressgateway"`
-	IsIngressGateway        string   `json:"isingressgateway" bson:"isingressgateway" yaml:"isingressgateway"`
-	EgressGatewayRanges     []string `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
-	EgressGatewayNatEnabled string   `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"`
-	RelayAddrs              []string `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"`
-	IngressGatewayRange     string   `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
+	ID                      string               `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"`
+	Address                 string               `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
+	Address6                string               `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
+	LocalAddress            string               `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty,ip"`
+	Name                    string               `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"`
+	NetworkSettings         Network              `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
+	ListenPort              int32                `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
+	LocalListenPort         int32                `json:"locallistenport" bson:"locallistenport" yaml:"locallistenport" validate:"numeric,min=0,max=65535"`
+	PublicKey               string               `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
+	Endpoint                string               `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`
+	PostUp                  string               `json:"postup" bson:"postup" yaml:"postup"`
+	PostDown                string               `json:"postdown" bson:"postdown" yaml:"postdown"`
+	AllowedIPs              []string             `json:"allowedips" bson:"allowedips" yaml:"allowedips"`
+	PersistentKeepalive     int32                `json:"persistentkeepalive" bson:"persistentkeepalive" yaml:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
+	IsHub                   string               `json:"ishub" bson:"ishub" yaml:"ishub" validate:"checkyesorno"`
+	AccessKey               string               `json:"accesskey" bson:"accesskey" yaml:"accesskey"`
+	Interface               string               `json:"interface" bson:"interface" yaml:"interface"`
+	LastModified            int64                `json:"lastmodified" bson:"lastmodified" yaml:"lastmodified"`
+	ExpirationDateTime      int64                `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
+	LastPeerUpdate          int64                `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
+	LastCheckIn             int64                `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
+	MacAddress              string               `json:"macaddress" bson:"macaddress" yaml:"macaddress"`
+	Password                string               `json:"password" bson:"password" yaml:"password" validate:"required,min=6"`
+	Network                 string               `json:"network" bson:"network" yaml:"network" validate:"network_exists"`
+	IsRelayed               string               `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
+	IsPending               string               `json:"ispending" bson:"ispending" yaml:"ispending"`
+	IsRelay                 string               `json:"isrelay" bson:"isrelay" yaml:"isrelay" validate:"checkyesorno"`
+	IsDocker                string               `json:"isdocker" bson:"isdocker" yaml:"isdocker" validate:"checkyesorno"`
+	IsK8S                   string               `json:"isk8s" bson:"isk8s" yaml:"isk8s" validate:"checkyesorno"`
+	IsEgressGateway         string               `json:"isegressgateway" bson:"isegressgateway" yaml:"isegressgateway"`
+	IsIngressGateway        string               `json:"isingressgateway" bson:"isingressgateway" yaml:"isingressgateway"`
+	EgressGatewayRanges     []string             `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
+	EgressGatewayNatEnabled string               `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"`
+	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
+	RelayAddrs              []string             `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"`
+	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
 	// IsStatic - refers to if the Endpoint is set manually or dynamically
 	// IsStatic - refers to if the Endpoint is set manually or dynamically
-	IsStatic     string      `json:"isstatic" bson:"isstatic" yaml:"isstatic" validate:"checkyesorno"`
-	UDPHolePunch string      `json:"udpholepunch" bson:"udpholepunch" yaml:"udpholepunch" validate:"checkyesorno"`
-	DNSOn        string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
-	IsServer     string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
-	Action       string      `json:"action" bson:"action" yaml:"action"`
-	IsLocal      string      `json:"islocal" bson:"islocal" yaml:"islocal" validate:"checkyesorno"`
-	LocalRange   string      `json:"localrange" bson:"localrange" yaml:"localrange"`
-	IPForwarding string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
-	OS           string      `json:"os" bson:"os" yaml:"os"`
-	MTU          int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
-	Version      string      `json:"version" bson:"version" yaml:"version"`
-	Server       string      `json:"server" bson:"server" yaml:"server"`
-	TrafficKeys  TrafficKeys `json:"traffickeys" bson:"traffickeys" yaml:"traffickeys"`
+	IsStatic        string      `json:"isstatic" bson:"isstatic" yaml:"isstatic" validate:"checkyesorno"`
+	UDPHolePunch    string      `json:"udpholepunch" bson:"udpholepunch" yaml:"udpholepunch" validate:"checkyesorno"`
+	DNSOn           string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
+	IsServer        string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
+	Action          string      `json:"action" bson:"action" yaml:"action"`
+	IsLocal         string      `json:"islocal" bson:"islocal" yaml:"islocal" validate:"checkyesorno"`
+	LocalRange      string      `json:"localrange" bson:"localrange" yaml:"localrange"`
+	IPForwarding    string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
+	OS              string      `json:"os" bson:"os" yaml:"os"`
+	MTU             int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
+	Version         string      `json:"version" bson:"version" yaml:"version"`
+	Server          string      `json:"server" bson:"server" yaml:"server"`
+	TrafficKeys     TrafficKeys `json:"traffickeys" bson:"traffickeys" yaml:"traffickeys"`
+	FirewallInUse   string      `json:"firewallinuse" bson:"firewallinuse" yaml:"firewallinuse"`
+	InternetGateway string      `json:"internetgateway" bson:"internetgateway" yaml:"internetgateway"`
 }
 }
 
 
 // NodesArray - used for node sorting
 // NodesArray - used for node sorting
@@ -119,6 +128,13 @@ func (node *Node) SetDefaultMTU() {
 	}
 	}
 }
 }
 
 
+// Node.SetDefaultNFTablesPresent - sets default for nftables check
+func (node *Node) SetDefaultNFTablesPresent() {
+	if node.FirewallInUse == "" {
+		node.FirewallInUse = FIREWALL_IPTABLES // default to iptables
+	}
+}
+
 // Node.SetDefaulIsPending - sets ispending default
 // Node.SetDefaulIsPending - sets ispending default
 func (node *Node) SetDefaulIsPending() {
 func (node *Node) SetDefaulIsPending() {
 	if node.IsPending == "" {
 	if node.IsPending == "" {
@@ -254,7 +270,7 @@ func (node *Node) SetDefaultName() {
 }
 }
 
 
 // Node.Fill - fills other node data into calling node data if not set on calling node
 // Node.Fill - fills other node data into calling node data if not set on calling node
-func (newNode *Node) Fill(currentNode *Node) {
+func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftables present
 	newNode.ID = currentNode.ID
 	newNode.ID = currentNode.ID
 
 
 	if newNode.Address == "" {
 	if newNode.Address == "" {

+ 17 - 0
models/ssocache.go

@@ -0,0 +1,17 @@
+package models
+
+import "time"
+
+// DefaultExpDuration - the default expiration time of SsoState
+const DefaultExpDuration = time.Minute * 5
+
+// SsoState - holds SSO sign-in session data
+type SsoState struct {
+	Value      string    `json:"value"`
+	Expiration time.Time `json:"expiration"`
+}
+
+// SsoState.IsExpired - tells if an SsoState is expired or not
+func (s *SsoState) IsExpired() bool {
+	return time.Now().After(s.Expiration)
+}

+ 1 - 0
mq/handlers.go

@@ -77,6 +77,7 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 			logger.Log(1, "error unmarshaling payload ", err.Error())
 			logger.Log(1, "error unmarshaling payload ", err.Error())
 			return
 			return
 		}
 		}
+		newNode.SetLastCheckIn()
 		if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
 		if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
 			logger.Log(1, "error saving node", err.Error())
 			logger.Log(1, "error saving node", err.Error())
 			return
 			return

+ 7 - 0
netclient/cli_options/flags.go

@@ -66,6 +66,13 @@ func GetFlags(hostname string) []cli.Flag {
 			Value:   "",
 			Value:   "",
 			Usage:   "Identifiable name for machine within Netmaker network.",
 			Usage:   "Identifiable name for machine within Netmaker network.",
 		},
 		},
+		&cli.StringFlag{
+			Name:    "publicipservice",
+			Aliases: []string{"ip-service"},
+			EnvVars: []string{"NETCLIENT_IP_SERVICE"},
+			Value:   "",
+			Usage:   "The service to call to obtain the public IP of the machine that is running netclient.",
+		},
 		&cli.StringFlag{
 		&cli.StringFlag{
 			Name:    "name",
 			Name:    "name",
 			EnvVars: []string{"NETCLIENT_NAME"},
 			EnvVars: []string{"NETCLIENT_NAME"},

+ 14 - 12
netclient/config/config.go

@@ -12,6 +12,7 @@ import (
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/global_settings"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2"
 	"gopkg.in/yaml.v3"
 	"gopkg.in/yaml.v3"
@@ -30,6 +31,7 @@ type ClientConfig struct {
 	Daemon          string              `yaml:"daemon"`
 	Daemon          string              `yaml:"daemon"`
 	OperatingSystem string              `yaml:"operatingsystem"`
 	OperatingSystem string              `yaml:"operatingsystem"`
 	AccessKey       string              `yaml:"accesskey"`
 	AccessKey       string              `yaml:"accesskey"`
+	PublicIPService string              `yaml:"publicipservice"`
 }
 }
 
 
 // RegisterRequest - struct for registation with netmaker server
 // RegisterRequest - struct for registation with netmaker server
@@ -231,6 +233,10 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 		cfg.Server.CoreDNSAddr = c.String("corednsaddr")
 		cfg.Server.CoreDNSAddr = c.String("corednsaddr")
 		cfg.Server.API = c.String("apiserver")
 		cfg.Server.API = c.String("apiserver")
 	}
 	}
+	cfg.PublicIPService = c.String("publicipservice")
+	// populate the map as we're not running as a daemon so won't be building the map otherwise
+	// (and the map will be used by GetPublicIP()).
+	global_settings.PublicIPServices[cfg.Network] = cfg.PublicIPService
 	cfg.Node.Name = c.String("name")
 	cfg.Node.Name = c.String("name")
 	cfg.Node.Interface = c.String("interface")
 	cfg.Node.Interface = c.String("interface")
 	cfg.Node.Password = c.String("password")
 	cfg.Node.Password = c.String("password")
@@ -264,32 +270,28 @@ func ReadConfig(network string) (*ClientConfig, error) {
 		err := errors.New("no network provided - exiting")
 		err := errors.New("no network provided - exiting")
 		return nil, err
 		return nil, err
 	}
 	}
-	nofile := false
 	home := ncutils.GetNetclientPathSpecific()
 	home := ncutils.GetNetclientPathSpecific()
 	file := fmt.Sprintf(home + "netconfig-" + network)
 	file := fmt.Sprintf(home + "netconfig-" + network)
 	f, err := os.Open(file)
 	f, err := os.Open(file)
-
 	if err != nil {
 	if err != nil {
 		if err = ReplaceWithBackup(network); err != nil {
 		if err = ReplaceWithBackup(network); err != nil {
-			nofile = true
+			return nil, err
 		}
 		}
 		f, err = os.Open(file)
 		f, err = os.Open(file)
 		if err != nil {
 		if err != nil {
-			nofile = true
+			return nil, err
 		}
 		}
 	}
 	}
 	defer f.Close()
 	defer f.Close()
 
 
 	var cfg ClientConfig
 	var cfg ClientConfig
-
-	if !nofile {
-		decoder := yaml.NewDecoder(f)
-		err = decoder.Decode(&cfg)
-		if err != nil {
-			fmt.Println("trouble decoding file")
-			return nil, err
-		}
+	decoder := yaml.NewDecoder(f)
+	err = decoder.Decode(&cfg)
+	if err != nil {
+		logger.Log(2, "trouble decoding file", err.Error())
+		return nil, err
 	}
 	}
+
 	return &cfg, err
 	return &cfg, err
 }
 }
 
 

+ 1 - 0
netclient/daemon/common.go

@@ -13,6 +13,7 @@ import (
 
 
 // InstallDaemon - Calls the correct function to install the netclient as a daemon service on the given operating system.
 // InstallDaemon - Calls the correct function to install the netclient as a daemon service on the given operating system.
 func InstallDaemon() error {
 func InstallDaemon() error {
+
 	os := runtime.GOOS
 	os := runtime.GOOS
 	var err error
 	var err error
 
 

+ 39 - 24
netclient/functions/common.go

@@ -30,6 +30,16 @@ const LINUX_APP_DATA_PATH = "/etc/netmaker"
 // HTTP_TIMEOUT - timeout in seconds for http requests
 // HTTP_TIMEOUT - timeout in seconds for http requests
 const HTTP_TIMEOUT = 30
 const HTTP_TIMEOUT = 30
 
 
+// HTTPClient - http client to be reused by all
+var HTTPClient http.Client
+
+// SetHTTPClient -sets http client with sane default
+func SetHTTPClient() {
+	HTTPClient = http.Client{
+		Timeout: HTTP_TIMEOUT * time.Second,
+	}
+}
+
 // ListPorts - lists ports of WireGuard devices
 // ListPorts - lists ports of WireGuard devices
 func ListPorts() error {
 func ListPorts() error {
 	wgclient, err := wgctrl.New()
 	wgclient, err := wgctrl.New()
@@ -193,7 +203,6 @@ func LeaveNetwork(network string) error {
 			if wgErr == nil && removeIface != "" {
 			if wgErr == nil && removeIface != "" {
 				removeIface = macIface
 				removeIface = macIface
 			}
 			}
-			wgErr = nil
 		}
 		}
 		dev, devErr := wgClient.Device(removeIface)
 		dev, devErr := wgClient.Device(removeIface)
 		if devErr == nil {
 		if devErr == nil {
@@ -224,18 +233,24 @@ func DeleteInterface(ifacename string, postdown string) error {
 
 
 // WipeLocal - wipes local instance
 // WipeLocal - wipes local instance
 func WipeLocal(network string) error {
 func WipeLocal(network string) error {
-	cfg, err := config.ReadConfig(network)
-	if err != nil {
-		return err
+	var ifacename string
+
+	if network == "" {
+		return errors.New("no network provided")
 	}
 	}
-	nodecfg := cfg.Node
-	ifacename := nodecfg.Interface
-	if ifacename != "" {
-		if err = wireguard.RemoveConf(ifacename, true); err == nil {
-			logger.Log(1, "network:", nodecfg.Network, "removed WireGuard interface: ", ifacename)
-		} else if strings.Contains(err.Error(), "does not exist") {
-			err = nil
+	cfg, err := config.ReadConfig(network)
+	if err == nil {
+		nodecfg := cfg.Node
+		ifacename = nodecfg.Interface
+		if ifacename != "" {
+			if err = wireguard.RemoveConf(ifacename, true); err == nil {
+				logger.Log(1, "network:", nodecfg.Network, "removed WireGuard interface: ", ifacename)
+			} else if strings.Contains(err.Error(), "does not exist") {
+				err = nil
+			}
 		}
 		}
+	} else {
+		logger.Log(0, "failed to read "+network+" config: ", err.Error())
 	}
 	}
 
 
 	home := ncutils.GetNetclientPathSpecific()
 	home := ncutils.GetNetclientPathSpecific()
@@ -281,17 +296,20 @@ func WipeLocal(network string) error {
 			log.Println(err.Error())
 			log.Println(err.Error())
 		}
 		}
 	}
 	}
-	if ncutils.FileExists(home + ifacename + ".conf") {
-		err = os.Remove(home + ifacename + ".conf")
+	if ifacename != "" {
+		if ncutils.FileExists(home + ifacename + ".conf") {
+			err = os.Remove(home + ifacename + ".conf")
+			if err != nil {
+				log.Println("error removing .conf:")
+				log.Println(err.Error())
+			}
+		}
+		err = removeHostDNS(ifacename, ncutils.IsWindows())
 		if err != nil {
 		if err != nil {
-			log.Println("error removing .conf:")
-			log.Println(err.Error())
+			logger.Log(0, "failed to delete dns entries for", ifacename, err.Error())
 		}
 		}
 	}
 	}
-	err = removeHostDNS(ifacename, ncutils.IsWindows())
-	if err != nil {
-		logger.Log(0, "failed to delete dns entries for", ifacename, err.Error())
-	}
+
 	return err
 	return err
 }
 }
 
 
@@ -300,7 +318,7 @@ func GetNetmakerPath() string {
 	return LINUX_APP_DATA_PATH
 	return LINUX_APP_DATA_PATH
 }
 }
 
 
-//API function to interact with netmaker api endpoints. response from endpoint is returned
+// API function to interact with netmaker api endpoints. response from endpoint is returned
 func API(data any, method, url, authorization string) (*http.Response, error) {
 func API(data any, method, url, authorization string) (*http.Response, error) {
 	var request *http.Request
 	var request *http.Request
 	var err error
 	var err error
@@ -323,10 +341,7 @@ func API(data any, method, url, authorization string) (*http.Response, error) {
 	if authorization != "" {
 	if authorization != "" {
 		request.Header.Set("authorization", "Bearer "+authorization)
 		request.Header.Set("authorization", "Bearer "+authorization)
 	}
 	}
-	client := http.Client{
-		Timeout: HTTP_TIMEOUT * time.Second,
-	}
-	return client.Do(request)
+	return HTTPClient.Do(request)
 }
 }
 
 
 // Authenticate authenticates with api to permit subsequent interactions with the api
 // Authenticate authenticates with api to permit subsequent interactions with the api

+ 9 - 0
netclient/functions/daemon.go

@@ -22,6 +22,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/daemon"
 	"github.com/gravitl/netmaker/netclient/daemon"
+	"github.com/gravitl/netmaker/netclient/global_settings"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"github.com/gravitl/netmaker/netclient/wireguard"
@@ -96,11 +97,19 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc {
 		if err := wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, ncutils.GetNetclientPathSpecific()+cfg.Node.Interface+".conf"); err != nil {
 		if err := wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, ncutils.GetNetclientPathSpecific()+cfg.Node.Interface+".conf"); err != nil {
 			logger.Log(0, "failed to start ", cfg.Node.Interface, "wg interface", err.Error())
 			logger.Log(0, "failed to start ", cfg.Node.Interface, "wg interface", err.Error())
 		}
 		}
+		if cfg.PublicIPService != "" {
+			global_settings.PublicIPServices[network] = cfg.PublicIPService
+		}
+
 		server := cfg.Server.Server
 		server := cfg.Server.Server
 		if !serverSet[server] {
 		if !serverSet[server] {
 			// == subscribe to all nodes for each on machine ==
 			// == subscribe to all nodes for each on machine ==
 			serverSet[server] = true
 			serverSet[server] = true
 			logger.Log(1, "started daemon for server ", server)
 			logger.Log(1, "started daemon for server ", server)
+			err := local.SetNetmakerDomainRoute(cfg.Server.API)
+			if err != nil {
+				logger.Log(0, "error setting route for netmaker: "+err.Error())
+			}
 			wg.Add(1)
 			wg.Add(1)
 			go messageQueue(ctx, wg, &cfg)
 			go messageQueue(ctx, wg, &cfg)
 		}
 		}

+ 22 - 4
netclient/functions/join.go

@@ -85,7 +85,7 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 		if cfg.Node.IsLocal == "yes" && cfg.Node.LocalAddress != "" {
 		if cfg.Node.IsLocal == "yes" && cfg.Node.LocalAddress != "" {
 			cfg.Node.Endpoint = cfg.Node.LocalAddress
 			cfg.Node.Endpoint = cfg.Node.LocalAddress
 		} else {
 		} else {
-			cfg.Node.Endpoint, err = ncutils.GetPublicIP()
+			cfg.Node.Endpoint, err = ncutils.GetPublicIP(cfg.Server.API)
 		}
 		}
 		if err != nil || cfg.Node.Endpoint == "" {
 		if err != nil || cfg.Node.Endpoint == "" {
 			logger.Log(0, "network:", cfg.Network, "error setting cfg.Node.Endpoint.")
 			logger.Log(0, "network:", cfg.Network, "error setting cfg.Node.Endpoint.")
@@ -114,7 +114,19 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 
 
 	if ncutils.IsFreeBSD() {
 	if ncutils.IsFreeBSD() {
 		cfg.Node.UDPHolePunch = "no"
 		cfg.Node.UDPHolePunch = "no"
+		cfg.Node.FirewallInUse = models.FIREWALL_IPTABLES // nftables not supported by FreeBSD
 	}
 	}
+
+	if cfg.Node.FirewallInUse == "" {
+		if ncutils.IsNFTablesPresent() {
+			cfg.Node.FirewallInUse = models.FIREWALL_NFTABLES
+		} else if ncutils.IsIPTablesPresent() {
+			cfg.Node.FirewallInUse = models.FIREWALL_IPTABLES
+		} else {
+			cfg.Node.FirewallInUse = models.FIREWALL_NONE
+		}
+	}
+
 	// make sure name is appropriate, if not, give blank name
 	// make sure name is appropriate, if not, give blank name
 	cfg.Node.Name = formatName(cfg.Node)
 	cfg.Node.Name = formatName(cfg.Node)
 	cfg.Node.OS = runtime.GOOS
 	cfg.Node.OS = runtime.GOOS
@@ -188,15 +200,21 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 	if err = config.SaveBackup(node.Network); err != nil {
 	if err = config.SaveBackup(node.Network); err != nil {
 		logger.Log(0, "network:", node.Network, "failed to make backup, node will not auto restore if config is corrupted")
 		logger.Log(0, "network:", node.Network, "failed to make backup, node will not auto restore if config is corrupted")
 	}
 	}
-	logger.Log(0, "starting wireguard")
-	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:], false)
+
+	err = local.SetNetmakerDomainRoute(cfg.Server.API)
 	if err != nil {
 	if err != nil {
-		return err
+		logger.Log(0, "error setting route for netmaker: "+err.Error())
 	}
 	}
 	cfg.Node = node
 	cfg.Node = node
 	if err := Register(cfg); err != nil {
 	if err := Register(cfg); err != nil {
 		return err
 		return err
 	}
 	}
+
+	logger.Log(0, "starting wireguard")
+	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:], false)
+	if err != nil {
+		return err
+	}
 	if cfg.Server.Server == "" {
 	if cfg.Server.Server == "" {
 		return errors.New("did not receive broker address from registration")
 		return errors.New("did not receive broker address from registration")
 	}
 	}

+ 21 - 2
netclient/functions/mqhandlers.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"net"
 	"os"
 	"os"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
@@ -206,13 +207,31 @@ func UpdatePeers(client mqtt.Client, msg mqtt.Message) {
 		cfg.Server.Version = peerUpdate.ServerVersion
 		cfg.Server.Version = peerUpdate.ServerVersion
 		config.Write(&cfg, cfg.Network)
 		config.Write(&cfg, cfg.Network)
 	}
 	}
-
 	file := ncutils.GetNetclientPathSpecific() + cfg.Node.Interface + ".conf"
 	file := ncutils.GetNetclientPathSpecific() + cfg.Node.Interface + ".conf"
-	err = wireguard.UpdateWgPeers(file, peerUpdate.Peers)
+	internetGateway, err := wireguard.UpdateWgPeers(file, peerUpdate.Peers)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "error updating wireguard peers"+err.Error())
 		logger.Log(0, "error updating wireguard peers"+err.Error())
 		return
 		return
 	}
 	}
+	//check if internet gateway has changed
+	oldGateway, err := net.ResolveUDPAddr("udp", cfg.Node.InternetGateway)
+	if err != nil || (oldGateway == &net.UDPAddr{}) {
+		oldGateway = nil
+	}
+	if (internetGateway == nil && oldGateway != nil) || (internetGateway != nil && internetGateway != oldGateway) {
+		cfg.Node.InternetGateway = internetGateway.String()
+		if err := config.ModNodeConfig(&cfg.Node); err != nil {
+			logger.Log(0, "failed to save internet gateway", err.Error())
+		}
+		if ncutils.IsWindows() {
+			wireguard.RemoveConfGraceful(cfg.Node.Interface)
+		}
+		if err := wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, file); err != nil {
+			logger.Log(0, "error applying internet gateway", err.Error())
+		}
+		UpdateLocalListenPort(&cfg)
+		return
+	}
 	queryAddr := cfg.Node.PrimaryAddress()
 	queryAddr := cfg.Node.PrimaryAddress()
 
 
 	//err = wireguard.SyncWGQuickConf(cfg.Node.Interface, file)
 	//err = wireguard.SyncWGQuickConf(cfg.Node.Interface, file)

+ 13 - 1
netclient/functions/mqpublish.go

@@ -13,6 +13,7 @@ import (
 
 
 	"github.com/cloverstd/tcping/ping"
 	"github.com/cloverstd/tcping/ping"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
@@ -43,8 +44,19 @@ func checkin() {
 		var nodeCfg config.ClientConfig
 		var nodeCfg config.ClientConfig
 		nodeCfg.Network = network
 		nodeCfg.Network = network
 		nodeCfg.ReadConfig()
 		nodeCfg.ReadConfig()
+		// check for nftables present if on Linux
+		if ncutils.IsLinux() {
+			if ncutils.IsNFTablesPresent() {
+				nodeCfg.Node.FirewallInUse = models.FIREWALL_NFTABLES
+			} else {
+				nodeCfg.Node.FirewallInUse = models.FIREWALL_IPTABLES
+			}
+		} else {
+			// defaults to iptables for now, may need another default for non-Linux OSes
+			nodeCfg.Node.FirewallInUse = models.FIREWALL_IPTABLES
+		}
 		if nodeCfg.Node.IsStatic != "yes" {
 		if nodeCfg.Node.IsStatic != "yes" {
-			extIP, err := ncutils.GetPublicIP()
+			extIP, err := ncutils.GetPublicIP(nodeCfg.Server.API)
 			if err != nil {
 			if err != nil {
 				logger.Log(1, "error encountered checking public ip addresses: ", err.Error())
 				logger.Log(1, "error encountered checking public ip addresses: ", err.Error())
 			}
 			}

+ 1 - 0
netclient/functions/upgrades/upgrades.go

@@ -3,6 +3,7 @@ package upgrades
 func init() {
 func init() {
 	addUpgrades([]UpgradeInfo{
 	addUpgrades([]UpgradeInfo{
 		upgrade0145,
 		upgrade0145,
+		upgrade0146,
 	})
 	})
 }
 }
 
 

+ 6 - 0
netclient/global_settings/globalsettings.go

@@ -0,0 +1,6 @@
+package global_settings
+
+// globalsettings - settings that are global in nature.  Avoids circular dependencies between config loading and usage.
+
+// PublicIPServices - the list of user-specified IP services to use to obtain the node's public IP
+var PublicIPServices map[string]string = make(map[string]string)

+ 92 - 0
netclient/local/routes.go

@@ -1,7 +1,9 @@
 package local
 package local
 
 
 import (
 import (
+	"fmt"
 	"net"
 	"net"
+	"strings"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
@@ -12,6 +14,17 @@ import (
 
 
 // SetPeerRoutes - sets/removes ip routes for each peer on a network
 // SetPeerRoutes - sets/removes ip routes for each peer on a network
 func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.PeerConfig) {
 func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.PeerConfig) {
+
+	// get the default route
+	var hasRoute bool
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		logger.Log(0, "error getting default route:", err.Error())
+	}
+	if gwIP != "" && gwIface != "" && err == nil {
+		hasRoute = true
+	}
+
 	// traverse through all recieved peers
 	// traverse through all recieved peers
 	for _, peer := range newPeers {
 	for _, peer := range newPeers {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
@@ -23,6 +36,16 @@ func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.Pe
 				delete(oldPeers, allowedIP.String())
 				delete(oldPeers, allowedIP.String())
 			}
 			}
 		}
 		}
+		if peer.Endpoint == nil {
+			continue
+		}
+		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
+			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
+			if err != nil {
+				logger.Log(0, "error parsing ip:", err.Error())
+			}
+			SetExplicitRoute(gwIface, &ipNet, gwIP)
+		}
 	}
 	}
 	// traverse through all remaining existing peers
 	// traverse through all remaining existing peers
 	for i := range oldPeers {
 	for i := range oldPeers {
@@ -37,19 +60,59 @@ func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.Pe
 
 
 // SetCurrentPeerRoutes - sets all the current peers
 // SetCurrentPeerRoutes - sets all the current peers
 func SetCurrentPeerRoutes(iface, currentAddr string, peers []wgtypes.PeerConfig) {
 func SetCurrentPeerRoutes(iface, currentAddr string, peers []wgtypes.PeerConfig) {
+
+	// get the default route
+	var hasRoute bool
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		logger.Log(0, "error getting default route:", err.Error())
+	}
+	if gwIP != "" && gwIface != "" && err == nil {
+		hasRoute = true
+	}
+
+	// traverse through all recieved peers
 	for _, peer := range peers {
 	for _, peer := range peers {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
 			setRoute(iface, &allowedIP, currentAddr)
 			setRoute(iface, &allowedIP, currentAddr)
 		}
 		}
+		if peer.Endpoint == nil {
+			continue
+		}
+		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
+			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
+			if err != nil {
+				logger.Log(0, "error parsing ip:", err.Error())
+			}
+			SetExplicitRoute(gwIface, &ipNet, gwIP)
+		}
 	}
 	}
+
 }
 }
 
 
 // FlushPeerRoutes - removes all current peer routes
 // FlushPeerRoutes - removes all current peer routes
 func FlushPeerRoutes(iface, currentAddr string, peers []wgtypes.Peer) {
 func FlushPeerRoutes(iface, currentAddr string, peers []wgtypes.Peer) {
+	// get the default route
+	var hasRoute bool
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		logger.Log(0, "error getting default route:", err.Error())
+	}
+	if gwIP != "" && gwIface != "" && err == nil {
+		hasRoute = true
+	}
+
 	for _, peer := range peers {
 	for _, peer := range peers {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
 			deleteRoute(iface, &allowedIP, currentAddr)
 			deleteRoute(iface, &allowedIP, currentAddr)
 		}
 		}
+		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
+			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
+			if err != nil {
+				logger.Log(0, "error parsing ip:", err.Error())
+			}
+			deleteRoute(gwIface, &ipNet, gwIP)
+		}
 	}
 	}
 }
 }
 
 
@@ -62,3 +125,32 @@ func SetCIDRRoute(iface, currentAddr string, cidr *net.IPNet) {
 func RemoveCIDRRoute(iface, currentAddr string, cidr *net.IPNet) {
 func RemoveCIDRRoute(iface, currentAddr string, cidr *net.IPNet) {
 	removeCidr(iface, cidr, currentAddr)
 	removeCidr(iface, cidr, currentAddr)
 }
 }
+
+// SetNetmakerDomainRoute - sets explicit route over Gateway for a given DNS name
+func SetNetmakerDomainRoute(domainRaw string) error {
+	parts := strings.Split(domainRaw, ":")
+	hostname := parts[0]
+	var address net.IPNet
+
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		return fmt.Errorf("error getting default route: %w", err)
+	}
+
+	ips, err := net.LookupIP(hostname)
+	if err != nil {
+		return err
+	}
+	for _, ip := range ips {
+		if ipv4 := ip.To4(); ipv4 != nil {
+			address, err = ncutils.GetIPNetFromString(ipv4.String())
+			if err == nil {
+				break
+			}
+		}
+	}
+	if err != nil || address.IP == nil {
+		return fmt.Errorf("address not found")
+	}
+	return SetExplicitRoute(gwIface, &address, gwIP)
+}

+ 40 - 3
netclient/local/routes_darwin.go

@@ -1,14 +1,46 @@
 package local
 package local
 
 
 import (
 import (
-	"net"
-	"strings"
-
+	"fmt"
 	"github.com/c-robinson/iplib"
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"net"
+	"regexp"
+	"strings"
 )
 )
 
 
+// GetDefaultRoute - Gets the default route (ip and interface) on a mac machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
+	var err error
+	var outLine string
+	output, err := ncutils.RunCmd("netstat -nr", false)
+	for _, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
+		if strings.Contains(line, "default") {
+			outLine = line
+			break
+		}
+	}
+	space := regexp.MustCompile(`\s+`)
+	outFormatted := space.ReplaceAllString(outLine, " ")
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	outputSlice := strings.Split(string(outFormatted), " ")
+	if !strings.Contains(outputSlice[0], "default") {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	ipaddr = outputSlice[1]
+	if err = ncutils.CheckIPAddress(ipaddr); err != nil {
+		return ipaddr, iface, err
+	}
+	iface = outputSlice[3]
+
+	return ipaddr, iface, err
+}
+
 // route -n add -net 10.0.0.0/8 192.168.0.254
 // route -n add -net 10.0.0.0/8 192.168.0.254
 // networksetup -setadditionalroutes Ethernet 192.168.1.0 255.255.255.0 10.0.0.2 persistent
 // networksetup -setadditionalroutes Ethernet 192.168.1.0 255.255.255.0 10.0.0.2 persistent
 func setRoute(iface string, addr *net.IPNet, address string) error {
 func setRoute(iface string, addr *net.IPNet, address string) error {
@@ -28,6 +60,11 @@ func setRoute(iface string, addr *net.IPNet, address string) error {
 	return err
 	return err
 }
 }
 
 
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	return setRoute(iface, destination, gateway)
+}
+
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	_, err = ncutils.RunCmd("route -q -n delete "+addr.String(), false)
 	_, err = ncutils.RunCmd("route -q -n delete "+addr.String(), false)

+ 35 - 2
netclient/local/routes_freebsd.go

@@ -1,16 +1,49 @@
 package local
 package local
 
 
 import (
 import (
+	"fmt"
 	"net"
 	"net"
+	"strings"
 
 
 	"github.com/c-robinson/iplib"
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
-func setRoute(iface string, addr *net.IPNet, address string) error {
+// GetDefaultRoute - Gets the default route (ip and interface) on a freebsd machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
 	var err error
 	var err error
-	_, _ = ncutils.RunCmd("route add -net "+addr.String()+" -interface "+iface, false)
+
+	output, err := ncutils.RunCmd("route show default", true)
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	outFormatted := strings.ReplaceAll(output, "\n", "")
+	if !strings.Contains(outFormatted, "default") && !strings.Contains(outFormatted, "interface:") {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	outputSlice := strings.Split(string(outFormatted), " ")
+	for i, outString := range outputSlice {
+		if outString == "gateway:" {
+			ipaddr = outputSlice[i+1]
+		}
+		if outString == "interface:" {
+			iface = outputSlice[i+1]
+		}
+	}
+	return ipaddr, iface, err
+}
+
+func setRoute(iface string, addr *net.IPNet, address string) error {
+	_, err := ncutils.RunCmd("route add -net "+addr.String()+" -interface "+iface, false)
+	return err
+}
+
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	_, err := ncutils.RunCmd("route add "+destination.String()+" "+gateway, false)
 	return err
 	return err
 }
 }
 
 

+ 30 - 0
netclient/local/routes_linux.go

@@ -12,6 +12,30 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
+// GetDefaultRoute - Gets the default route (ip and interface) on a linux machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
+	var err error
+	output, err := ncutils.RunCmd("ip route show default", false)
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	outputSlice := strings.Split(output, " ")
+	if !strings.Contains(outputSlice[0], "default") {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	for i, outString := range outputSlice {
+		if outString == "via" {
+			ipaddr = outputSlice[i+1]
+		}
+		if outString == "dev" {
+			iface = outputSlice[i+1]
+		}
+	}
+	return ipaddr, iface, err
+}
+
 func setRoute(iface string, addr *net.IPNet, address string) error {
 func setRoute(iface string, addr *net.IPNet, address string) error {
 	out, err := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)
 	out, err := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)
 	if err != nil || !strings.Contains(out, iface) {
 	if err != nil || !strings.Contains(out, iface) {
@@ -20,6 +44,12 @@ func setRoute(iface string, addr *net.IPNet, address string) error {
 	return err
 	return err
 }
 }
 
 
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	_, err := ncutils.RunCmd(fmt.Sprintf("ip route add %s via %s dev %s", destination.String(), gateway, iface), false)
+	return err
+}
+
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	out, _ := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)
 	out, _ := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)

+ 45 - 0
netclient/local/routes_windows.go

@@ -1,12 +1,48 @@
 package local
 package local
 
 
 import (
 import (
+	"fmt"
 	"net"
 	"net"
+	"regexp"
+	"strings"
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
+// GetDefaultRoute - Gets the default route (ip and interface) on a windows machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
+	var err error
+	var outLine string
+	output, err := ncutils.RunCmd("netstat -rn", false)
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	var startLook bool
+	for _, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
+		if strings.Contains(line, "Active Routes:") {
+			startLook = true
+		}
+		if startLook && strings.Contains(line, "0.0.0.0") {
+			outLine = line
+			break
+		}
+	}
+	if outLine == "" {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	space := regexp.MustCompile(`\s+`)
+	outputSlice := strings.Split(strings.TrimSpace(space.ReplaceAllString(outLine, " ")), " ")
+	ipaddr = outputSlice[len(outputSlice)-3]
+	if err = ncutils.CheckIPAddress(ipaddr); err != nil {
+		return ipaddr, iface, fmt.Errorf("invalid output for ip address check: " + err.Error())
+	}
+	iface = "irrelevant"
+	return ipaddr, iface, err
+}
+
 func setRoute(iface string, addr *net.IPNet, address string) error {
 func setRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	_, err = ncutils.RunCmd("route ADD "+addr.String()+" "+address, false)
 	_, err = ncutils.RunCmd("route ADD "+addr.String()+" "+address, false)
@@ -15,6 +51,15 @@ func setRoute(iface string, addr *net.IPNet, address string) error {
 	return err
 	return err
 }
 }
 
 
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	var err error
+	_, err = ncutils.RunCmd("route ADD "+destination.String()+" "+gateway, false)
+	time.Sleep(time.Second >> 2)
+	ncutils.RunCmd("route CHANGE "+destination.IP.String()+" MASK "+destination.Mask.String()+" "+gateway, false)
+	return err
+}
+
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	_, err = ncutils.RunCmd("route DELETE "+addr.IP.String()+" MASK "+addr.Mask.String()+" "+address, false)
 	_, err = ncutils.RunCmd("route DELETE "+addr.IP.String()+" MASK "+addr.Mask.String()+" "+address, false)

+ 5 - 0
netclient/main.go

@@ -10,6 +10,7 @@ import (
 
 
 	"github.com/gravitl/netmaker/netclient/cli_options"
 	"github.com/gravitl/netmaker/netclient/cli_options"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/functions"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncwindows"
 	"github.com/gravitl/netmaker/netclient/ncwindows"
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2"
@@ -29,12 +30,16 @@ func main() {
 	app.UsageText = "netclient [global options] command [command options] [arguments...]. Adjust verbosity of given command with -v, -vv or -vvv (max)."
 	app.UsageText = "netclient [global options] command [command options] [arguments...]. Adjust verbosity of given command with -v, -vv or -vvv (max)."
 
 
 	setGarbageCollection()
 	setGarbageCollection()
+	functions.SetHTTPClient()
 
 
 	if ncutils.IsWindows() {
 	if ncutils.IsWindows() {
 		ncwindows.InitWindows()
 		ncwindows.InitWindows()
 	} else {
 	} else {
 		ncutils.CheckUID()
 		ncutils.CheckUID()
 		ncutils.CheckWG()
 		ncutils.CheckWG()
+		if ncutils.IsLinux() {
+			ncutils.CheckFirewall()
+		}
 	}
 	}
 
 
 	if len(os.Args) <= 1 && config.GuiActive {
 	if len(os.Args) <= 1 && config.GuiActive {

+ 4 - 0
netclient/ncutils/iface.go

@@ -90,3 +90,7 @@ func IfaceExists(ifacename string) bool {
 	}
 	}
 	return false
 	return false
 }
 }
+
+func IpIsPrivate(ipnet net.IP) bool {
+	return ipnet.IsPrivate() || ipnet.IsLoopback()
+}

+ 53 - 7
netclient/ncutils/netclientutils.go

@@ -22,6 +22,7 @@ import (
 	"github.com/c-robinson/iplib"
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/global_settings"
 )
 )
 
 
 var (
 var (
@@ -41,15 +42,12 @@ const NO_DB_RECORDS = "could not find any records"
 // LINUX_APP_DATA_PATH - linux path
 // LINUX_APP_DATA_PATH - linux path
 const LINUX_APP_DATA_PATH = "/etc/netclient"
 const LINUX_APP_DATA_PATH = "/etc/netclient"
 
 
-// MAC_APP_DATA_PATH - linux path
+// MAC_APP_DATA_PATH - mac path
 const MAC_APP_DATA_PATH = "/Applications/Netclient"
 const MAC_APP_DATA_PATH = "/Applications/Netclient"
 
 
 // WINDOWS_APP_DATA_PATH - windows path
 // WINDOWS_APP_DATA_PATH - windows path
 const WINDOWS_APP_DATA_PATH = "C:\\Program Files (x86)\\Netclient"
 const WINDOWS_APP_DATA_PATH = "C:\\Program Files (x86)\\Netclient"
 
 
-// WINDOWS_APP_DATA_PATH - windows path
-//const WINDOWS_WG_DPAPI_PATH = "C:\\Program Files\\WireGuard\\Data\\Configurations"
-
 // WINDOWS_SVC_NAME - service name
 // WINDOWS_SVC_NAME - service name
 const WINDOWS_SVC_NAME = "netclient"
 const WINDOWS_SVC_NAME = "netclient"
 
 
@@ -89,7 +87,7 @@ func IsLinux() bool {
 	return runtime.GOOS == "linux"
 	return runtime.GOOS == "linux"
 }
 }
 
 
-// IsLinux - checks if is linux
+// IsFreeBSD - checks if is freebsd
 func IsFreeBSD() bool {
 func IsFreeBSD() bool {
 	return runtime.GOOS == "freebsd"
 	return runtime.GOOS == "freebsd"
 }
 }
@@ -109,6 +107,28 @@ func GetWireGuard() string {
 	return "wg"
 	return "wg"
 }
 }
 
 
+// IsNFTablesPresent - returns true if nftables is present, false otherwise.
+// Does not consider OS, up to the caller to determine if the OS supports nftables/whether this check is valid.
+func IsNFTablesPresent() bool {
+	found := false
+	_, err := exec.LookPath("nft")
+	if err == nil {
+		found = true
+	}
+	return found
+}
+
+// IsIPTablesPresent - returns true if iptables is present, false otherwise
+// Does not consider OS, up to the caller to determine if the OS supports iptables/whether this check is valid.
+func IsIPTablesPresent() bool {
+	found := false
+	_, err := exec.LookPath("iptables")
+	if err == nil {
+		found = true
+	}
+	return found
+}
+
 // IsKernel - checks if running kernel WireGuard
 // IsKernel - checks if running kernel WireGuard
 func IsKernel() bool {
 func IsKernel() bool {
 	//TODO
 	//TODO
@@ -126,9 +146,21 @@ func IsEmptyRecord(err error) bool {
 }
 }
 
 
 // GetPublicIP - gets public ip
 // GetPublicIP - gets public ip
-func GetPublicIP() (string, error) {
+func GetPublicIP(api string) (string, error) {
 
 
 	iplist := []string{"https://ip.client.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
 	iplist := []string{"https://ip.client.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
+
+	for network, ipService := range global_settings.PublicIPServices {
+		logger.Log(3, "User provided public IP service defined for network", network, "is", ipService)
+
+		// prepend the user-specified service so it's checked first
+		iplist = append([]string{ipService}, iplist...)
+	}
+	if api != "" {
+		api = "https://" + api + "/api/getip"
+		iplist = append([]string{api}, iplist...)
+	}
+
 	endpoint := ""
 	endpoint := ""
 	var err error
 	var err error
 	for _, ipserver := range iplist {
 	for _, ipserver := range iplist {
@@ -218,7 +250,7 @@ func GetLocalIP(localrange string) (string, error) {
 	return local, nil
 	return local, nil
 }
 }
 
 
-//GetNetworkIPMask - Pulls the netmask out of the network
+// GetNetworkIPMask - Pulls the netmask out of the network
 func GetNetworkIPMask(networkstring string) (string, string, error) {
 func GetNetworkIPMask(networkstring string) (string, string, error) {
 	ip, ipnet, err := net.ParseCIDR(networkstring)
 	ip, ipnet, err := net.ParseCIDR(networkstring)
 	if err != nil {
 	if err != nil {
@@ -321,6 +353,13 @@ func GetNetclientPathSpecific() string {
 	}
 	}
 }
 }
 
 
+func CheckIPAddress(ip string) error {
+	if net.ParseIP(ip) == nil {
+		return fmt.Errorf("ip address %s is invalid", ip)
+	}
+	return nil
+}
+
 // GetNewIface - Gets the name of the real interface created on Mac
 // GetNewIface - Gets the name of the real interface created on Mac
 func GetNewIface(dir string) (string, error) {
 func GetNewIface(dir string) (string, error) {
 	files, _ := os.ReadDir(dir)
 	files, _ := os.ReadDir(dir)
@@ -492,6 +531,13 @@ func CheckUID() {
 	}
 	}
 }
 }
 
 
+// CheckFirewall - checks if iptables of nft install, if not exit
+func CheckFirewall() {
+	if !IsIPTablesPresent() && !IsNFTablesPresent() {
+		log.Fatal("neither iptables nor nft is installed - please install one or the other and try again")
+	}
+}
+
 // CheckWG - Checks if WireGuard is installed. If not, exit
 // CheckWG - Checks if WireGuard is installed. If not, exit
 func CheckWG() {
 func CheckWG() {
 	uspace := GetWireGuard()
 	uspace := GetWireGuard()

+ 1 - 1
netclient/netclient.exe.manifest.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
     <assemblyIdentity
     <assemblyIdentity
-            version="0.14.6.0"
+            version="0.15.0.0"
             processorArchitecture="*"
             processorArchitecture="*"
             name="netclient.exe"
             name="netclient.exe"
             type="win32"
             type="win32"

+ 5 - 5
netclient/versioninfo.json

@@ -2,14 +2,14 @@
     "FixedFileInfo": {
     "FixedFileInfo": {
         "FileVersion": {
         "FileVersion": {
             "Major": 0,
             "Major": 0,
-            "Minor": 14,
-            "Patch": 6,
+            "Minor": 15,
+            "Patch": 0,
             "Build": 0
             "Build": 0
         },
         },
         "ProductVersion": {
         "ProductVersion": {
             "Major": 0,
             "Major": 0,
-            "Minor": 14,
-            "Patch": 6,
+            "Minor": 15,
+            "Patch": 0,
             "Build": 0
             "Build": 0
         },
         },
         "FileFlagsMask": "3f",
         "FileFlagsMask": "3f",
@@ -29,7 +29,7 @@
         "OriginalFilename": "",
         "OriginalFilename": "",
         "PrivateBuild": "",
         "PrivateBuild": "",
         "ProductName": "Netclient",
         "ProductName": "Netclient",
-        "ProductVersion": "v0.14.6.0",
+        "ProductVersion": "v0.15.0.0",
         "SpecialBuild": ""
         "SpecialBuild": ""
     },
     },
     "VarFileInfo": {
     "VarFileInfo": {

+ 41 - 15
netclient/wireguard/common.go

@@ -2,7 +2,6 @@ package wireguard
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"log"
 	"net"
 	"net"
 	"runtime"
 	"runtime"
 	"strconv"
 	"strconv"
@@ -29,7 +28,6 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 	var devicePeers []wgtypes.Peer
 	var devicePeers []wgtypes.Peer
 	var keepalive = node.PersistentKeepalive
 	var keepalive = node.PersistentKeepalive
 	var oldPeerAllowedIps = make(map[string]bool, len(peers))
 	var oldPeerAllowedIps = make(map[string]bool, len(peers))
-
 	var err error
 	var err error
 	devicePeers, err = GetDevicePeers(iface)
 	devicePeers, err = GetDevicePeers(iface)
 	if err != nil {
 	if err != nil {
@@ -52,7 +50,7 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 				currentPeer.PublicKey.String() != peer.PublicKey.String() {
 				currentPeer.PublicKey.String() != peer.PublicKey.String() {
 				_, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
 				_, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
 				if err != nil {
 				if err != nil {
-					log.Println("error removing peer", peer.Endpoint.String())
+					logger.Log(0, "error removing peer", peer.Endpoint.String())
 				}
 				}
 			}
 			}
 		}
 		}
@@ -82,10 +80,9 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 				" allowed-ips "+allowedips, true)
 				" allowed-ips "+allowedips, true)
 		}
 		}
 		if err != nil {
 		if err != nil {
-			log.Println("error setting peer", peer.PublicKey.String())
+			logger.Log(0, "error setting peer", peer.PublicKey.String())
 		}
 		}
 	}
 	}
-
 	if len(devicePeers) > 0 {
 	if len(devicePeers) > 0 {
 		for _, currentPeer := range devicePeers {
 		for _, currentPeer := range devicePeers {
 			shouldDelete := true
 			shouldDelete := true
@@ -104,7 +101,7 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 				if shouldDelete {
 				if shouldDelete {
 					output, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
 					output, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
 					if err != nil {
 					if err != nil {
-						log.Println(output, "error removing peer", currentPeer.PublicKey.String())
+						logger.Log(0, output, "error removing peer", currentPeer.PublicKey.String())
 					}
 					}
 				}
 				}
 				for _, ip := range currentPeer.AllowedIPs {
 				for _, ip := range currentPeer.AllowedIPs {
@@ -319,7 +316,6 @@ func ApplyConf(node *models.Node, ifacename string, confPath string) error {
 }
 }
 
 
 // WriteWgConfig - creates a wireguard config file
 // WriteWgConfig - creates a wireguard config file
-//func WriteWgConfig(cfg *config.ClientConfig, privateKey string, peers []wgtypes.PeerConfig) error {
 func WriteWgConfig(node *models.Node, privateKey string, peers []wgtypes.PeerConfig) error {
 func WriteWgConfig(node *models.Node, privateKey string, peers []wgtypes.PeerConfig) error {
 	options := ini.LoadOptions{
 	options := ini.LoadOptions{
 		AllowNonUniqueSections: true,
 		AllowNonUniqueSections: true,
@@ -342,11 +338,24 @@ func WriteWgConfig(node *models.Node, privateKey string, peers []wgtypes.PeerCon
 	//if node.DNSOn == "yes" {
 	//if node.DNSOn == "yes" {
 	//	wireguard.Section(section_interface).Key("DNS").SetValue(cfg.Server.CoreDNSAddr)
 	//	wireguard.Section(section_interface).Key("DNS").SetValue(cfg.Server.CoreDNSAddr)
 	//}
 	//}
+	//need to split postup/postdown because ini lib adds a ` and the ` breaks freebsd
 	if node.PostUp != "" {
 	if node.PostUp != "" {
-		wireguard.Section(section_interface).Key("PostUp").SetValue(node.PostUp)
+		parts := strings.Split(node.PostUp, " ; ")
+		for i, part := range parts {
+			if i == 0 {
+				wireguard.Section(section_interface).Key("PostUp").SetValue(part)
+			}
+			wireguard.Section(section_interface).Key("PostUp").AddShadow(part)
+		}
 	}
 	}
 	if node.PostDown != "" {
 	if node.PostDown != "" {
-		wireguard.Section(section_interface).Key("PostDown").SetValue(node.PostDown)
+		parts := strings.Split(node.PostDown, " ; ")
+		for i, part := range parts {
+			if i == 0 {
+				wireguard.Section(section_interface).Key("PostDown").SetValue(part)
+			}
+			wireguard.Section(section_interface).Key("PostDown").AddShadow(part)
+		}
 	}
 	}
 	if node.MTU != 0 {
 	if node.MTU != 0 {
 		wireguard.Section(section_interface).Key("MTU").SetValue(strconv.FormatInt(int64(node.MTU), 10))
 		wireguard.Section(section_interface).Key("MTU").SetValue(strconv.FormatInt(int64(node.MTU), 10))
@@ -382,14 +391,15 @@ func WriteWgConfig(node *models.Node, privateKey string, peers []wgtypes.PeerCon
 }
 }
 
 
 // UpdateWgPeers - updates the peers of a network
 // UpdateWgPeers - updates the peers of a network
-func UpdateWgPeers(file string, peers []wgtypes.PeerConfig) error {
+func UpdateWgPeers(file string, peers []wgtypes.PeerConfig) (*net.UDPAddr, error) {
+	var internetGateway *net.UDPAddr
 	options := ini.LoadOptions{
 	options := ini.LoadOptions{
 		AllowNonUniqueSections: true,
 		AllowNonUniqueSections: true,
 		AllowShadows:           true,
 		AllowShadows:           true,
 	}
 	}
 	wireguard, err := ini.LoadSources(options, file)
 	wireguard, err := ini.LoadSources(options, file)
 	if err != nil {
 	if err != nil {
-		return err
+		return internetGateway, err
 	}
 	}
 	//delete the peers sections as they are going to be replaced
 	//delete the peers sections as they are going to be replaced
 	wireguard.DeleteSection(section_peers)
 	wireguard.DeleteSection(section_peers)
@@ -408,6 +418,9 @@ func UpdateWgPeers(file string, peers []wgtypes.PeerConfig) error {
 				}
 				}
 			}
 			}
 			wireguard.SectionWithIndex(section_peers, i).Key("AllowedIps").SetValue(allowedIPs)
 			wireguard.SectionWithIndex(section_peers, i).Key("AllowedIps").SetValue(allowedIPs)
+			if strings.Contains(allowedIPs, "0.0.0.0/0") || strings.Contains(allowedIPs, "::/0") {
+				internetGateway = peer.Endpoint
+			}
 		}
 		}
 		if peer.Endpoint != nil {
 		if peer.Endpoint != nil {
 			wireguard.SectionWithIndex(section_peers, i).Key("Endpoint").SetValue(peer.Endpoint.String())
 			wireguard.SectionWithIndex(section_peers, i).Key("Endpoint").SetValue(peer.Endpoint.String())
@@ -417,9 +430,9 @@ func UpdateWgPeers(file string, peers []wgtypes.PeerConfig) error {
 		}
 		}
 	}
 	}
 	if err := wireguard.SaveTo(file); err != nil {
 	if err := wireguard.SaveTo(file); err != nil {
-		return err
+		return internetGateway, err
 	}
 	}
-	return nil
+	return internetGateway, nil
 }
 }
 
 
 // UpdateWgInterface - updates the interface section of a wireguard config file
 // UpdateWgInterface - updates the interface section of a wireguard config file
@@ -448,11 +461,24 @@ func UpdateWgInterface(file, privateKey, nameserver string, node models.Node) er
 	//if node.DNSOn == "yes" {
 	//if node.DNSOn == "yes" {
 	//	wireguard.Section(section_interface).Key("DNS").SetValue(nameserver)
 	//	wireguard.Section(section_interface).Key("DNS").SetValue(nameserver)
 	//}
 	//}
+	//need to split postup/postdown because ini lib adds a quotes which breaks freebsd
 	if node.PostUp != "" {
 	if node.PostUp != "" {
-		wireguard.Section(section_interface).Key("PostUp").SetValue(node.PostUp)
+		parts := strings.Split(node.PostUp, " ; ")
+		for i, part := range parts {
+			if i == 0 {
+				wireguard.Section(section_interface).Key("PostUp").SetValue(part)
+			}
+			wireguard.Section(section_interface).Key("PostUp").AddShadow(part)
+		}
 	}
 	}
 	if node.PostDown != "" {
 	if node.PostDown != "" {
-		wireguard.Section(section_interface).Key("PostDown").SetValue(node.PostDown)
+		parts := strings.Split(node.PostDown, " ; ")
+		for i, part := range parts {
+			if i == 0 {
+				wireguard.Section(section_interface).Key("PostDown").SetValue(part)
+			}
+			wireguard.Section(section_interface).Key("PostDown").AddShadow(part)
+		}
 	}
 	}
 	if node.MTU != 0 {
 	if node.MTU != 0 {
 		wireguard.Section(section_interface).Key("MTU").SetValue(strconv.FormatInt(int64(node.MTU), 10))
 		wireguard.Section(section_interface).Key("MTU").SetValue(strconv.FormatInt(int64(node.MTU), 10))

+ 1 - 0
netclient/wireguard/mac.go

@@ -49,6 +49,7 @@ func WgQuickUpMac(node *models.Node, iface string, confPath string) error {
 		return err
 		return err
 	}
 	}
 	time.Sleep(time.Second / 2)
 	time.Sleep(time.Second / 2)
+
 	err = setConfig(realIface, confPath)
 	err = setConfig(realIface, confPath)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "error setting config for ", realIface)
 		logger.Log(1, "error setting config for ", realIface)

+ 0 - 35
scripts/netmaker-server.sh

@@ -1,35 +0,0 @@
-#!/bin/sh
-set -e
-
-mkdir -p /etc/netmaker/config/environments
-wget -O /etc/netmaker/netmaker https://github.com/gravitl/netmaker/releases/download/latest/netmaker
-chmod +x /etc/netmaker/netmaker
-
-cat >/etc/netmaker/config/environments/dev.yaml<<EOL
-server:
-  host:
-  apiport: "8081"
-  masterkey: "secretkey"
-  allowedorigin: "*"
-  restbackend: true            
-  agentbackend: true
-  dnsmode: "on"
-EOL
-
-cat >/etc/systemd/system/netmaker.service<<EOL
-[Unit]
-Description=Netmaker Server
-After=network.target
-
-[Service]
-Type=simple
-Restart=on-failure
-
-WorkingDirectory=/etc/netmaker
-ExecStart=/etc/netmaker/netmaker
-
-[Install]
-WantedBy=multi-user.target
-EOL
-systemctl daemon-reload
-systemctl start netmaker.service

+ 302 - 0
scripts/nm-quick-interactive.sh

@@ -0,0 +1,302 @@
+#!/bin/bash
+
+set -e
+
+cat << "EOF"
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+                                                                                         
+ __   __     ______     ______   __    __     ______     __  __     ______     ______    
+/\ "-.\ \   /\  ___\   /\__  _\ /\ "-./  \   /\  __ \   /\ \/ /    /\  ___\   /\  == \   
+\ \ \-.  \  \ \  __\   \/_/\ \/ \ \ \-./\ \  \ \  __ \  \ \  _"-.  \ \  __\   \ \  __<   
+ \ \_\\"\_\  \ \_____\    \ \_\  \ \_\ \ \_\  \ \_\ \_\  \ \_\ \_\  \ \_____\  \ \_\ \_\ 
+  \/_/ \/_/   \/_____/     \/_/   \/_/  \/_/   \/_/\/_/   \/_/\/_/   \/_____/   \/_/ /_/ 
+                                                                                                                                                                                                 
+
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+EOF
+
+NETMAKER_BASE_DOMAIN=nm.$(curl -s ifconfig.me | tr . -).nip.io
+COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
+SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
+MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
+
+echo "Default Base Domain: $NETMAKER_BASE_DOMAIN"
+echo "To Override, add a Wildcard (*.netmaker.example.com) DNS record pointing to $SERVER_PUBLIC_IP"
+echo "Or, add three DNS records pointing to $SERVER_PUBLIC_IP for the following (Replacing 'netmaker.example.com' with the domain of your choice):"
+echo "   dashboard.netmaker.example.com"
+echo "         api.netmaker.example.com"
+echo "        grpc.netmaker.example.com"
+echo "-----------------------------------------------------"
+read -p "Domain (Hit 'enter' to use $NETMAKER_BASE_DOMAIN): " domain
+read -p "Email for LetsEncrypt (Hit 'enter' to use $EMAIL): " email
+
+if [ -n "$domain" ]; then
+  NETMAKER_BASE_DOMAIN=$domain
+fi
+if [ -n "$email" ]; then
+  EMAIL=$email
+fi
+
+while true; do
+    read -p 'Configure a default network automatically? [y/n]: ' yn
+    case $yn in
+        [Yy]* ) MESH_SETUP="true"; break;;
+        [Nn]* ) MESH_SETUP="false"; break;;
+        * ) echo "Please answer yes or no.";;
+    esac
+done
+
+while true; do
+    read -p 'Configure a VPN gateway automatically? [y/n]: ' yn
+    case $yn in
+        [Yy]* ) VPN_SETUP="true"; break;;
+        [Nn]* ) VPN_SETUP="false"; break;;
+        * ) echo "Please answer yes or no.";;
+    esac
+done
+
+if [ "${VPN_SETUP}" == "true" ]; then
+while :; do
+    read -ep '# of VPN clients to configure by default: ' num_clients
+    [[ $num_clients =~ ^[[:digit:]]+$ ]] || continue
+    (( ( (num_clients=(10#$num_clients)) <= 200 ) && num_clients >= 0 )) || continue
+    break
+done
+fi
+
+if [ -n "$num_clients" ]; then
+  NUM_CLIENTS=$num_clients
+fi
+
+echo "-----------------------------------------------------------------"
+echo "                SETUP ARGUMENTS"
+echo "-----------------------------------------------------------------"
+echo "        domain: $NETMAKER_BASE_DOMAIN"
+echo "         email: $EMAIL"
+echo "     public ip: $SERVER_PUBLIC_IP"
+echo "   setup mesh?: $MESH_SETUP"
+echo "    setup vpn?: $VPN_SETUP"
+if [ "${VPN_SETUP}" == "true" ]; then
+echo "     # clients: $NUM_CLIENTS"
+fi
+
+while true; do
+    read -p 'Does everything look right? [y/n]: ' yn
+    case $yn in
+        [Yy]* ) override="true"; break;;
+        [Nn]* ) echo "exiting..."; exit;;
+        * ) echo "Please answer yes or no.";;
+    esac
+done
+
+
+echo "Beginning installation in 5 seconds..."
+
+sleep 5
+
+if [ -f "/root/docker-compose.yml" ]; then
+    echo "Using existing docker compose"
+else 
+    echo "Pulling docker compose"
+    wget -q -O /root/docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.yml
+fi
+
+
+if [ -f "/root/mosquitto.conf" ]; then
+    echo "Using existing mosquitto config"
+else
+    echo "Pulling mosquitto config"
+    wget -q -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf
+fi
+
+
+mkdir -p /etc/netmaker
+
+echo "Setting docker-compose..."
+
+sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
+sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
+sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
+sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/docker-compose.yml
+
+echo "Starting containers..."
+
+docker-compose -f /root/docker-compose.yml up -d
+
+sleep 2
+
+test_connection() {
+
+echo "Testing Traefik setup (please be patient, this may take 1-2 minutes)"
+for i in 1 2 3 4 5 6
+do
+curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
+
+if [[ "$i" == 6 ]]; then
+  echo "    Traefik is having an issue setting up certificates, please investigate (docker logs traefik)"
+  echo "    Exiting..."
+  exit 1
+elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
+  echo "    Certificates not yet configured, retrying..."
+
+elif [[ "$curlresponse" == *"left intact"* ]]; then
+  echo "    Certificates ok"
+  break
+else
+  secs=$(($i*5+10))
+  echo "    Issue establishing connection...retrying in $secs seconds..."       
+fi
+sleep $secs
+done
+}
+
+
+setup_mesh() {( set -e
+sleep 5
+echo "Creating netmaker network (10.101.0.0/16)"
+
+curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
+
+sleep 5
+
+echo "Creating netmaker access key"
+
+curlresponse=$(curl -s -d '{"uses":99999,"name":"netmaker-key"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks/netmaker/keys)
+ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
+
+sleep 5
+
+echo "Configuring netmaker server as ingress gateway"
+
+curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker)
+SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
+
+curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress
+
+sleep 5
+)}
+
+mesh_connect_logs() {
+sleep 5
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+echo "DEFAULT NETWORK CLIENT INSTALL INSTRUCTIONS:"
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+sleep 5
+echo "For Linux and Mac clients, install with the following command:"
+echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+echo "curl -sfL https://raw.githubusercontent.com/gravitl/netmaker/develop/scripts/netclient-install.sh | sudo KEY=$VPN_ACCESS_TOKEN sh -"
+sleep 5
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+echo "For Windows clients, perform the following from powershell, as administrator:"
+echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+echo "1. Make sure WireGuardNT is installed - https://download.wireguard.com/windows-client/wireguard-installer.exe"
+echo "2. Download netclient.exe - wget https://github.com/gravitl/netmaker/releases/download/latest/netclient.exe"
+echo "3. Install Netclient - powershell.exe .\\netclient.exe join -t $VPN_ACCESS_TOKEN"
+echo "4. Whitelist C:\ProgramData\Netclient in Windows Defender"
+sleep 5
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+echo "For Android and iOS clients, perform the following steps:"
+echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+echo "1. Log into UI at dashboard.$NETMAKER_BASE_DOMAIN"
+echo "2. Navigate to \"EXTERNAL CLIENTS\" tab"
+echo "3. Select the gateway and create clients"
+echo "4. Scan the QR Code from WireGuard app in iOS or Android"
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+sleep 5
+}
+
+setup_vpn() {( set -e
+
+echo "Creating vpn network (10.201.0.0/16)"
+
+sleep 5
+curl -s -o /dev/null -d '{"addressrange":"10.201.0.0/16","netid":"vpn","defaultextclientdns":"8.8.8.8"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
+
+sleep 5
+
+echo "Configuring netmaker server as vpn inlet..."
+
+curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn)
+SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
+
+curl -s -o /dev/null -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/createingress
+
+echo "Waiting 10 seconds for server to apply configuration..."
+
+sleep 10
+
+
+echo "Configuring netmaker server vpn gateway..."
+
+[ -z "$GATEWAY_IFACE" ] && GATEWAY_IFACE=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)')
+
+echo "Gateway iface: $GATEWAY_IFACE"
+
+curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn)
+SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
+
+EGRESS_JSON=$( jq -n \
+                  --arg gw "$GATEWAY_IFACE" \
+                  '{ranges: ["0.0.0.0/5","8.0.0.0/7","11.0.0.0/8","12.0.0.0/6","16.0.0.0/4","32.0.0.0/3","64.0.0.0/2","128.0.0.0/3","160.0.0.0/5","168.0.0.0/6","172.0.0.0/12","172.32.0.0/11","172.64.0.0/10","172.128.0.0/9","173.0.0.0/8","174.0.0.0/7","176.0.0.0/4","192.0.0.0/9","192.128.0.0/11","192.160.0.0/13","192.169.0.0/16","192.170.0.0/15","192.172.0.0/14","192.176.0.0/12","192.192.0.0/10","193.0.0.0/8","194.0.0.0/7","196.0.0.0/6","200.0.0.0/5","208.0.0.0/4"], interface: $gw}' )
+
+echo "Egress json: $EGRESS_JSON"
+curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway
+
+echo "Creating client configs..."
+
+for ((a=1; a <= $NUM_CLIENTS; a++))
+do
+        CLIENT_JSON=$( jq -n \
+                  --arg clientid "vpnclient-$a" \
+                  '{clientid: $clientid}' )
+
+        curl -s -o /dev/null -d "$CLIENT_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/extclients/vpn/$SERVER_ID
+done
+sleep 5
+)}
+
+vpn_connect_logs() {
+sleep 5
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+echo "VPN GATEWAY CLIENT INSTALL INSTRUCTIONS:"
+echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+echo "1. log into dashboard.$NETMAKER_BASE_DOMAIN"
+echo "2. Navigate to \"EXTERNAL CLIENTS\" tab"
+echo "3. Download or scan a client config (vpnclient-x) to the appropriate device"
+echo "4. Follow the steps for your system to configure WireGuard on the appropriate device"
+echo "5. Create and delete clients as necessary. Changes to netmaker server settings require regenerating ext clients."
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+sleep 5
+}
+
+set +e
+test_connection
+
+if [ "${MESH_SETUP}" != "false" ]; then
+        setup_mesh
+fi
+
+if [ "${VPN_SETUP}" == "true" ]; then
+        setup_vpn
+fi
+
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
+echo "Visit dashboard.$NETMAKER_BASE_DOMAIN to log in"
+echo "-----------------------------------------------------------------"
+echo "-----------------------------------------------------------------"
+
+# cp -f /etc/skel/.bashrc /root/.bashrc

+ 33 - 4
scripts/nm-quick.sh

@@ -144,6 +144,35 @@ echo "starting containers..."
 
 
 docker-compose -f /root/docker-compose.yml up -d
 docker-compose -f /root/docker-compose.yml up -d
 
 
+test_connection() {
+
+echo "testing Traefik setup (please be patient, this may take 1-2 minutes)"
+for i in 1 2 3 4 5 6
+do
+curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
+
+if [[ "$i" == 6 ]]; then
+  echo "    Traefik is having an issue setting up certificates, please investigate (docker logs traefik)"
+  echo "    exiting..."
+  exit 1
+elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
+  echo "    certificates not yet configured, retrying..."
+
+elif [[ "$curlresponse" == *"left intact"* ]]; then
+  echo "    certificates ok"
+  break
+else
+  secs=$(($i*5+10))
+  echo "    issue establishing connection...retrying in $secs seconds..."       
+fi
+sleep $secs
+done
+}
+
+set +e
+test_connection
+
+
 cat << "EOF"
 cat << "EOF"
 
 
                                                                                          
                                                                                          
@@ -160,7 +189,7 @@ EOF
 echo "visit https://dashboard.$NETMAKER_BASE_DOMAIN to log in"
 echo "visit https://dashboard.$NETMAKER_BASE_DOMAIN to log in"
 sleep 7
 sleep 7
 
 
-setup_mesh() {
+setup_mesh() {( set -e
 echo "creating netmaker network (10.101.0.0/16)"
 echo "creating netmaker network (10.101.0.0/16)"
 
 
 curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
 curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
@@ -196,9 +225,9 @@ echo "        3. Select the gateway and create clients"
 echo "        4. Scan the QR Code from WireGuard app in iOS or Android"
 echo "        4. Scan the QR Code from WireGuard app in iOS or Android"
 echo ""
 echo ""
 echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
 echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
-}
+)}
 
 
-setup_vpn() {
+setup_vpn() {( set -e
 echo "creating vpn network (10.201.0.0/16)"
 echo "creating vpn network (10.201.0.0/16)"
 
 
 curl -s -o /dev/null -d '{"addressrange":"10.201.0.0/16","netid":"vpn","defaultextclientdns":"8.8.8.8"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
 curl -s -o /dev/null -d '{"addressrange":"10.201.0.0/16","netid":"vpn","defaultextclientdns":"8.8.8.8"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
@@ -253,7 +282,7 @@ echo "        3. Download or scan a client config (vpnclient-x) to the appropria
 echo "        4. Follow the steps for your system to configure WireGuard on the appropriate device"
 echo "        4. Follow the steps for your system to configure WireGuard on the appropriate device"
 echo "        5. Create and delete clients as necessary. Changes to netmaker server settings require regenerating ext clients."
 echo "        5. Create and delete clients as necessary. Changes to netmaker server settings require regenerating ext clients."
 
 
-}
+)}
 
 
 if [ "${MESH_SETUP}" != "false" ]; then
 if [ "${MESH_SETUP}" != "false" ]; then
         setup_mesh
         setup_mesh

+ 18 - 13
scripts/openwrt-daemon.sh

@@ -1,38 +1,43 @@
-#!/bin/bash /etc/rc.common
+#!/bin/sh /etc/rc.common
 #Created by oycol<[email protected]>
 #Created by oycol<[email protected]>
 
 
 EXTRA_COMMANDS="status"
 EXTRA_COMMANDS="status"
-EXTRA_HELP="        status  	Check service is running"
+EXTRA_HELP="        status      Check service is running"
 START=99
 START=99
 
 
 LOG_FILE="/tmp/netclient.logs"
 LOG_FILE="/tmp/netclient.logs"
 
 
 start() {
 start() {
+  mkdir -p /etc/netclient/config
+  mkdir -p /etc/systemd/system
+
   if [ ! -f "${LOG_FILE}" ];then
   if [ ! -f "${LOG_FILE}" ];then
       touch "${LOG_FILE}"
       touch "${LOG_FILE}"
   fi
   fi
-  local PIDS=($(ps -e|grep "netclient checkin -n all"|grep -v grep|awk '{print $1}'))
-  if [ ${PIDS} ];then
+
+  local PID=$(ps|grep "netclient daemon"|grep -v grep|awk '{print $1}')
+
+  if [ "${PID}" ];then
     echo "service is running"
     echo "service is running"
     return
     return
   fi
   fi
-  bash -c "while [ 1 ]; do /etc/netclient/netclient checkin -n all >> ${LOG_FILE} 2>&1;sleep 15;\
-           if [ $(ls -l ${LOG_FILE}|awk '{print $5}') -gt 10240000 ];then tar zcf "${LOG_FILE}.tar" -C / "tmp/netclient.logs"  && > ${LOG_FILE};fi;done &"
+  /bin/sh -c "while [ 1 ]; do netclient daemon >> ${LOG_FILE} 2>&1;sleep 15;\
+           if [ $(ls -l ${LOG_FILE}|awk '{print $5}') -gt 10240000 ];then tar zcf "${LOG_FILE}.tar" -C / "tmp/netclient.logs"  && > $LOG_FILE;fi;done &"
   echo "start"
   echo "start"
 }
 }
 
 
 stop() {
 stop() {
-  local PIDS=($(ps -e|grep "netclient checkin -n all"|grep -v grep|awk '{print $1}'))
-  for i in "${PIDS[@]}"; do
-    kill $i
-  done
+  local PID=$(ps|grep "netclient daemon"|grep -v grep|awk '{print $1}')
+  if [ "${PID}" ];then
+    kill ${PID}
+  fi
   echo "stop"
   echo "stop"
 }
 }
 
 
 status() {
 status() {
-  local PIDS=($(ps -e|grep "netclient checkin -n all"|grep -v grep|awk '{print $1}'))
-  if [ ${PIDS} ];then
-    echo -e "netclient[${PIDS}] is running \n"
+  local PID=$(ps|grep "netclient daemon"|grep -v grep|awk '{print $1}')
+  if [ "${PID}" ];then
+    echo -e "netclient[${PID}] is running \n"
   else
   else
     echo -e "netclient is not running \n"
     echo -e "netclient is not running \n"
   fi
   fi

+ 11 - 0
servercfg/serverconf.go

@@ -427,6 +427,17 @@ func GetPublicIP() (string, error) {
 	var err error
 	var err error
 
 
 	iplist := []string{"https://ip.server.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
 	iplist := []string{"https://ip.server.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
+	publicIpService := os.Getenv("PUBLIC_IP_SERVICE")
+	if publicIpService != "" {
+		// prepend the user-specified service so it's checked first
+		iplist = append([]string{publicIpService}, iplist...)
+	} else if config.Config.Server.PublicIPService != "" {
+		publicIpService = config.Config.Server.PublicIPService
+
+		// prepend the user-specified service so it's checked first
+		iplist = append([]string{publicIpService}, iplist...)
+	}
+
 	for _, ipserver := range iplist {
 	for _, ipserver := range iplist {
 		client := &http.Client{
 		client := &http.Client{
 			Timeout: time.Second * 10,
 			Timeout: time.Second * 10,