Browse Source

Merge pull request #399 from gravitl/develop

Develop
Alex 3 years ago
parent
commit
8c88407391
100 changed files with 2531 additions and 1225 deletions
  1. 10 2
      .github/dependabot.yml
  2. 32 9
      README.md
  3. 183 0
      auth/auth.go
  4. 126 0
      auth/azure-ad.go
  5. 10 0
      auth/error.go
  6. 129 0
      auth/github.go
  7. 120 0
      auth/google.go
  8. 7 4
      compose/docker-compose.caddy.yml
  9. 8 5
      compose/docker-compose.nodns.yml
  10. 3 9
      compose/docker-compose.reference.yml
  11. 7 4
      compose/docker-compose.yml
  12. 35 32
      config/config.go
  13. 5 4
      controllers/authGrpc.go
  14. 4 4
      controllers/common.go
  15. 4 4
      controllers/controller.go
  16. 17 17
      controllers/dnsHttpController.go
  17. 13 14
      controllers/dnsHttpController_test.go
  18. 10 25
      controllers/extClientHttpController.go
  19. 12 12
      controllers/networkHttpController.go
  20. 2 1
      controllers/networkHttpController_test.go
  21. 5 5
      controllers/nodeGrpcController.go
  22. 27 23
      controllers/nodeHttpController.go
  23. 1 1
      controllers/nodeHttpController_test.go
  24. 5 4
      controllers/relay.go
  25. 2 2
      controllers/responseHttp.go
  26. 2 2
      controllers/serverHttpController.go
  27. 43 219
      controllers/userHttpController.go
  28. 46 45
      controllers/userHttpController_test.go
  29. 6 2
      database/database.go
  30. BIN
      docs/_build/doctrees/about.doctree
  31. BIN
      docs/_build/doctrees/api.doctree
  32. BIN
      docs/_build/doctrees/architecture.doctree
  33. BIN
      docs/_build/doctrees/client-installation.doctree
  34. BIN
      docs/_build/doctrees/conduct.doctree
  35. BIN
      docs/_build/doctrees/environment.pickle
  36. BIN
      docs/_build/doctrees/external-clients.doctree
  37. BIN
      docs/_build/doctrees/getting-started.doctree
  38. BIN
      docs/_build/doctrees/index.doctree
  39. BIN
      docs/_build/doctrees/install.doctree
  40. BIN
      docs/_build/doctrees/license.doctree
  41. BIN
      docs/_build/doctrees/oauth.doctree
  42. BIN
      docs/_build/doctrees/quick-start-nginx.doctree
  43. BIN
      docs/_build/doctrees/quick-start.doctree
  44. BIN
      docs/_build/doctrees/server-installation.doctree
  45. BIN
      docs/_build/doctrees/support.doctree
  46. BIN
      docs/_build/doctrees/troubleshoot.doctree
  47. BIN
      docs/_build/doctrees/usage.doctree
  48. 1 1
      docs/_build/html/.buildinfo
  49. BIN
      docs/_build/html/_images/extclient5.png
  50. BIN
      docs/_build/html/_images/oauth1.png
  51. BIN
      docs/_build/html/_images/oauth2.png
  52. BIN
      docs/_build/html/_images/oauth3.png
  53. 2 2
      docs/_build/html/_sources/architecture.rst.txt
  54. 1 1
      docs/_build/html/_sources/external-clients.rst.txt
  55. 11 0
      docs/_build/html/_sources/index.rst.txt
  56. 77 0
      docs/_build/html/_sources/oauth.rst.txt
  57. 8 1
      docs/_build/html/_sources/server-installation.rst.txt
  58. 20 63
      docs/_build/html/_static/basic.css
  59. 1 1
      docs/_build/html/_static/documentation_options.js
  60. 0 1
      docs/_build/html/_static/jquery.js
  61. 0 0
      docs/_build/html/_static/jquery.min.map
  62. 6 1
      docs/_build/html/_static/pygments.css
  63. 1 1
      docs/_build/html/_static/searchtools.js
  64. 6 1
      docs/_build/html/_static/stylesheets/application-fixes.css
  65. 3 4
      docs/_build/html/_static/underscore.js
  66. 10 10
      docs/_build/html/about.html
  67. 10 10
      docs/_build/html/api.html
  68. 13 13
      docs/_build/html/architecture.html
  69. 14 14
      docs/_build/html/client-installation.html
  70. 10 10
      docs/_build/html/conduct.html
  71. 39 11
      docs/_build/html/external-clients.html
  72. 38 10
      docs/_build/html/genindex.html
  73. 10 10
      docs/_build/html/getting-started.html
  74. 52 10
      docs/_build/html/index.html
  75. 10 10
      docs/_build/html/install.html
  76. 10 10
      docs/_build/html/license.html
  77. 929 0
      docs/_build/html/oauth.html
  78. BIN
      docs/_build/html/objects.inv
  79. 10 10
      docs/_build/html/quick-start-nginx.html
  80. 10 10
      docs/_build/html/quick-start.html
  81. 38 10
      docs/_build/html/search.html
  82. 0 0
      docs/_build/html/searchindex.js
  83. 51 20
      docs/_build/html/server-installation.html
  84. 10 10
      docs/_build/html/support.html
  85. 10 10
      docs/_build/html/troubleshoot.html
  86. 10 10
      docs/_build/html/usage.html
  87. 2 2
      docs/architecture.rst
  88. 1 1
      docs/conf.py
  89. 1 1
      docs/external-clients.rst
  90. BIN
      docs/images/oauth1.png
  91. BIN
      docs/images/oauth2.png
  92. BIN
      docs/images/oauth3.png
  93. 11 0
      docs/index.rst
  94. 77 0
      docs/oauth.rst
  95. 8 1
      docs/server-installation.rst
  96. 3 423
      functions/helpers.go
  97. 5 33
      functions/local.go
  98. 7 6
      go.mod
  99. 59 34
      go.sum
  100. 62 0
      logic/accesskeys.go

+ 10 - 2
.github/dependabot.yml

@@ -2,9 +2,17 @@
 
 version: 2
 updates:
-  # Enable version updates for go
+  # Enable version updates for netmaker
   - package-ecosystem: "gomod"
     directory: "/"
     # Check for updates every day (weekdays)
     schedule:
-      interval: "daily"
+      interval: "weekly"
+    target-branch: "develop"
+  # Enable version updates for netclient
+  - package-ecosystem: "gomod"
+    directory: "/netclient"
+    # Check for updates every day (weekdays)
+    schedule:
+      interval: "weekly"
+    target-branch: "develop"

+ 32 - 9
README.md

@@ -8,16 +8,16 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.8.4-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.8.5-informational?style=flat-square" />
   </a>
+  <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
+    <img src="https://img.shields.io/docker/pulls/gravitl/netmaker" />
+  </a>  
   <a href="https://discord.gg/zRb9Vfhk8A">
     <img src="https://img.shields.io/badge/community-discord-informational" />
   </a>
   <a href="https://github.com/gravitl/netmaker/graphs/contributors">
-    <img src="https://img.shields.io/github/commit-activity/w/gravitl/netmaker?color=blue" />
-  </a>
-  <a href="https://gravitl.com/resources">
-    <img src="https://img.shields.io/badge/learning-resources-9cf" />
+    <img src="https://img.shields.io/github/commit-activity/m/gravitl/netmaker?color=blue" />
   </a>
   <a href="https://twitter.com/intent/follow?screen_name=gravitlcorp">
     <img src="https://img.shields.io/twitter/follow/gravitlcorp?style=social" />
@@ -29,9 +29,8 @@
 
 # WireGuard® Automation from Homelab to Enterprise
 - [x] Peer-to-Peer Mesh Networks
-- [x] Site-to-Site Gateways
-- [x] Private DNS
 - [x] Kubernetes Multi-Cloud
+- [x] Private DNS
 - [x] Linux, Mac, Windows, iPhone, and Android
 
 # Get Started in 5 Minutes
@@ -40,13 +39,35 @@
 **For an HA install using helm on k8s, visit the [Helm Repo](https://github.com/gravitl/netmaker-helm/).**
 1. Get a cloud VM with Ubuntu 20.04 and a public IP.
 2. Open ports 443, 53, and 51821-51830/udp on the VM firewall and in cloud security settings.
-3. Run the script:
+3. Run the script **(see below for optional configurations)**:
 
 `sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/develop/scripts/nm-quick.sh | bash`
 
+Upon completion, the logs will display a script that can be used to automatically connect Linux and Mac devices. It will also display instructions for Windows, iPhone, and Android.
+
 <img src="./docs/images/install-server.gif" width="50%" /><img src="./docs/images/visit-website.gif" width="50%" />
 
-After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting-started-with-netmaker-a-wireguard-virtual-networking-platform-3d563fbd87f0) and [Getting Started](https://netmaker.readthedocs.io/en/master/getting-started.html) guide to begin setting up networks. Or, check out some of our other [Tutorials](https://gravitl.com/resources) for different use cases, including Kubernetes.
+After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting-started-with-netmaker-a-wireguard-virtual-networking-platform-3d563fbd87f0) and [Getting Started](https://netmaker.readthedocs.io/en/master/getting-started.html) guides to learn more about configuring networks. Or, check out some of our other [Tutorials](https://gravitl.com/resources) for different use cases, including Kubernetes.
+
+### Optional configurations
+
+**Deploy a "Hub-And-Spoke VPN" on the server**  
+*This will configure a standard VPN (non-meshed) for private internet access, with 10 clients (-c).*  
+`sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/develop/scripts/nm-quick.sh | bash -s -- -v true -c 7`  
+
+**Specify Domain sand Email**  
+*Make sure your wildcard domain is pointing towards the server ip.*  
+`sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/develop/scripts/nm-quick.sh | bash -s -- -d mynetmaker.domain.com -e [email protected]`  
+
+**Script Options**  
+```
+./nm-quick
+-d domain.example.com # specify a wildcard domain for netmaker to use (DNS must point to this server)
+-e [email protected] # specify your email (for SSL certificates)
+-m true # create a default 'mesh network' (on by default)
+-v false # create a default 'VPN network' (off by default)
+-c 7 # number of client configs to create (for VPN network, 5 by default)
+```
 
 # Why Netmaker + WireGuard?
 
@@ -64,6 +85,8 @@ After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting
 
 - [Business (Subscription)](https://gravitl.com/plans/business)
 
+- [Learning Resources](https://gravitl.com/resources)
+
 ## Disclaimer
  [WireGuard](https://wireguard.com/) is a registered trademark of Jason A. Donenfeld.
 

+ 183 - 0
auth/auth.go

@@ -0,0 +1,183 @@
+package auth
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/crypto/bcrypt"
+	"golang.org/x/oauth2"
+)
+
+// == consts ==
+const (
+	init_provider          = "initprovider"
+	get_user_info          = "getuserinfo"
+	handle_callback        = "handlecallback"
+	handle_login           = "handlelogin"
+	google_provider_name   = "google"
+	azure_ad_provider_name = "azure-ad"
+	github_provider_name   = "github"
+	verify_user            = "verifyuser"
+	auth_key               = "netmaker_auth"
+)
+
+var oauth_state_string = "netmaker-oauth-state" // should be set randomly each provider login
+var auth_provider *oauth2.Config
+
+func getCurrentAuthFunctions() map[string]interface{} {
+	var authInfo = servercfg.GetAuthProviderInfo()
+	var authProvider = authInfo[0]
+	switch authProvider {
+	case google_provider_name:
+		return google_functions
+	case azure_ad_provider_name:
+		return azure_ad_functions
+	case github_provider_name:
+		return github_functions
+	default:
+		return nil
+	}
+}
+
+// InitializeAuthProvider - initializes the auth provider if any is present
+func InitializeAuthProvider() string {
+	var functions = getCurrentAuthFunctions()
+	if functions == nil {
+		return ""
+	}
+	var _, err = fetchPassValue(logic.RandomString(64))
+	if err != nil {
+		logic.Log(err.Error(), 0)
+		return ""
+	}
+	var currentFrontendURL = servercfg.GetFrontendURL()
+	if currentFrontendURL == "" {
+		return ""
+	}
+	var authInfo = servercfg.GetAuthProviderInfo()
+	var serverConn = servercfg.GetAPIHost()
+	if strings.Contains(serverConn, "localhost") || strings.Contains(serverConn, "127.0.0.1") {
+		serverConn = "http://" + serverConn
+		logic.Log("localhost OAuth detected, proceeding with insecure http redirect: "+serverConn+")", 1)
+	} else {
+		serverConn = "https://" + serverConn
+		logic.Log("external OAuth detected, proceeding with https redirect: ("+serverConn+")", 1)
+	}
+
+	functions[init_provider].(func(string, string, string))(serverConn+"/api/oauth/callback", authInfo[1], authInfo[2])
+	return authInfo[0]
+}
+
+// HandleAuthCallback - handles oauth callback
+func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
+	if auth_provider == nil {
+		w.Header().Set("Content-Type", "text/html; charset=utf-8")
+		fmt.Fprintln(w, oauthNotConfigured)
+		return
+	}
+	var functions = getCurrentAuthFunctions()
+	if functions == nil {
+		return
+	}
+	functions[handle_callback].(func(http.ResponseWriter, *http.Request))(w, r)
+}
+
+// HandleAuthLogin - handles oauth login
+func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
+	if auth_provider == nil {
+		var referer = r.Header.Get("referer")
+		if referer != "" {
+			http.Redirect(w, r, referer+"?oauth=callback-error", http.StatusTemporaryRedirect)
+			return
+		}
+		w.Header().Set("Content-Type", "text/html; charset=utf-8")
+		fmt.Fprintln(w, oauthNotConfigured)
+		return
+	}
+	var functions = getCurrentAuthFunctions()
+	if functions == nil {
+		return
+	}
+	functions[handle_login].(func(http.ResponseWriter, *http.Request))(w, r)
+}
+
+// IsOauthUser - returns
+func IsOauthUser(user *models.User) error {
+	var currentValue, err = fetchPassValue("")
+	if err != nil {
+		return err
+	}
+	var bCryptErr = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentValue))
+	return bCryptErr
+}
+
+// == private methods ==
+
+func addUser(email string) error {
+	var hasAdmin, err = logic.HasAdmin()
+	if err != nil {
+		logic.Log("error checking for existence of admin user during OAuth login for "+email+", user not added", 1)
+		return err
+	} // generate random password to adapt to current model
+	var newPass, fetchErr = fetchPassValue("")
+	if fetchErr != nil {
+		return fetchErr
+	}
+	var newUser = models.User{
+		UserName: email,
+		Password: newPass,
+	}
+	if !hasAdmin { // must be first attempt, create an admin
+		if newUser, err = logic.CreateAdmin(newUser); err != nil {
+			logic.Log("error creating admin from user, "+email+", user not added", 1)
+		} else {
+			logic.Log("admin created from user, "+email+", was first user added", 0)
+		}
+	} else { // otherwise add to db as admin..?
+		// TODO: add ability to add users with preemptive permissions
+		newUser.IsAdmin = false
+		if newUser, err = logic.CreateUser(newUser); err != nil {
+			logic.Log("error creating user, "+email+", user not added", 1)
+		} else {
+			logic.Log("user created from, "+email+"", 0)
+		}
+	}
+	return nil
+}
+
+func fetchPassValue(newValue string) (string, error) {
+
+	type valueHolder struct {
+		Value string `json:"value" bson:"value"`
+	}
+	var b64NewValue = base64.StdEncoding.EncodeToString([]byte(newValue))
+	var newValueHolder = &valueHolder{
+		Value: b64NewValue,
+	}
+	var data, marshalErr = json.Marshal(newValueHolder)
+	if marshalErr != nil {
+		return "", marshalErr
+	}
+
+	var currentValue, err = logic.FetchAuthSecret(auth_key, string(data))
+	if err != nil {
+		return "", err
+	}
+	var unmarshErr = json.Unmarshal([]byte(currentValue), newValueHolder)
+	if unmarshErr != nil {
+		return "", unmarshErr
+	}
+
+	var b64CurrentValue, b64Err = base64.StdEncoding.DecodeString(newValueHolder.Value)
+	if b64Err != nil {
+		logic.Log("could not decode pass", 0)
+		return "", nil
+	}
+	return string(b64CurrentValue), nil
+}

+ 126 - 0
auth/azure-ad.go

@@ -0,0 +1,126 @@
+package auth
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/microsoft"
+)
+
+var azure_ad_functions = map[string]interface{}{
+	init_provider:   initAzureAD,
+	get_user_info:   getAzureUserInfo,
+	handle_callback: handleAzureCallback,
+	handle_login:    handleAzureLogin,
+	verify_user:     verifyAzureUser,
+}
+
+type azureOauthUser struct {
+	UserPrincipalName string `json:"userPrincipalName" bson:"userPrincipalName"`
+	AccessToken       string `json:"accesstoken" bson:"accesstoken"`
+}
+
+// == handle azure ad authentication here ==
+
+func initAzureAD(redirectURL string, clientID string, clientSecret string) {
+	auth_provider = &oauth2.Config{
+		RedirectURL:  redirectURL,
+		ClientID:     clientID,
+		ClientSecret: clientSecret,
+		Scopes:       []string{"User.Read"},
+		Endpoint:     microsoft.AzureADEndpoint(os.Getenv("AZURE_TENANT")),
+	}
+}
+
+func handleAzureLogin(w http.ResponseWriter, r *http.Request) {
+	oauth_state_string = logic.RandomString(16)
+	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	} else if auth_provider == nil {
+		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
+	}
+	var url = auth_provider.AuthCodeURL(oauth_state_string)
+	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
+}
+
+func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
+
+	var content, err = getAzureUserInfo(r.FormValue("state"), r.FormValue("code"))
+	if err != nil {
+		logic.Log("error when getting user info from azure: "+err.Error(), 1)
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+	_, err = logic.GetUser(content.UserPrincipalName)
+	if err != nil { // user must not exists, so try to make one
+		if err = addUser(content.UserPrincipalName); err != nil {
+			return
+		}
+	}
+	var newPass, fetchErr = fetchPassValue("")
+	if fetchErr != nil {
+		return
+	}
+	// send a netmaker jwt token
+	var authRequest = models.UserAuthParams{
+		UserName: content.UserPrincipalName,
+		Password: newPass,
+	}
+
+	var jwt, jwtErr = logic.VerifyAuthRequest(authRequest)
+	if jwtErr != nil {
+		logic.Log("could not parse jwt for user "+authRequest.UserName, 1)
+		return
+	}
+
+	logic.Log("completed azure OAuth sigin in for "+content.UserPrincipalName, 1)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"?login="+jwt+"&user="+content.UserPrincipalName, http.StatusPermanentRedirect)
+}
+
+func getAzureUserInfo(state string, code string) (*azureOauthUser, error) {
+	if state != oauth_state_string {
+		return nil, fmt.Errorf("invalid oauth state")
+	}
+	var token, err = auth_provider.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		return nil, fmt.Errorf("code exchange failed: %s", err.Error())
+	}
+	var data []byte
+	data, err = json.Marshal(token)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert token to json: %s", err.Error())
+	}
+	var httpReq, reqErr = http.NewRequest("GET", "https://graph.microsoft.com/v1.0/me", nil)
+	if reqErr != nil {
+		return nil, fmt.Errorf("failed to create request to GitHub")
+	}
+	httpReq.Header.Set("Authorization", "Bearer "+token.AccessToken)
+	response, err := http.DefaultClient.Do(httpReq)
+	if err != nil {
+		return nil, fmt.Errorf("failed getting user info: %s", err.Error())
+	}
+	defer response.Body.Close()
+	contents, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed reading response body: %s", err.Error())
+	}
+	var userInfo = &azureOauthUser{}
+	if err = json.Unmarshal(contents, userInfo); err != nil {
+		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
+	}
+	userInfo.AccessToken = string(data)
+	return userInfo, nil
+}
+
+func verifyAzureUser(token *oauth2.Token) bool {
+	return token.Valid()
+}

+ 10 - 0
auth/error.go

@@ -0,0 +1,10 @@
+package auth
+
+// == define error HTML here ==
+const oauthNotConfigured = `<!DOCTYPE html><html>
+<body>
+<h3>Your Netmaker server does not have OAuth configured.</h3>
+<p>Please visit the docs <a href="https://docs.netmaker.org/oauth.html" target="_blank" rel="noopener">here</a> to learn how to.</p>
+</body>
+</html>
+`

+ 129 - 0
auth/github.go

@@ -0,0 +1,129 @@
+package auth
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/github"
+)
+
+var github_functions = map[string]interface{}{
+	init_provider:   initGithub,
+	get_user_info:   getGithubUserInfo,
+	handle_callback: handleGithubCallback,
+	handle_login:    handleGithubLogin,
+	verify_user:     verifyGithubUser,
+}
+
+type githubOauthUser struct {
+	Login       string `json:"login" bson:"login"`
+	AccessToken string `json:"accesstoken" bson:"accesstoken"`
+}
+
+// == handle github authentication here ==
+
+func initGithub(redirectURL string, clientID string, clientSecret string) {
+	auth_provider = &oauth2.Config{
+		RedirectURL:  redirectURL,
+		ClientID:     clientID,
+		ClientSecret: clientSecret,
+		Scopes:       []string{},
+		Endpoint:     github.Endpoint,
+	}
+}
+
+func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
+	oauth_state_string = logic.RandomString(16)
+	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"?error=callback-error", http.StatusTemporaryRedirect)
+		return
+	} else if auth_provider == nil {
+		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
+	}
+	var url = auth_provider.AuthCodeURL(oauth_state_string)
+	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
+}
+
+func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
+
+	var content, err = getGithubUserInfo(r.URL.Query().Get("state"), r.URL.Query().Get("code"))
+	if err != nil {
+		logic.Log("error when getting user info from github: "+err.Error(), 1)
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+	_, err = logic.GetUser(content.Login)
+	if err != nil { // user must not exist, so try to make one
+		if err = addUser(content.Login); err != nil {
+			return
+		}
+	}
+	var newPass, fetchErr = fetchPassValue("")
+	if fetchErr != nil {
+		return
+	}
+	// send a netmaker jwt token
+	var authRequest = models.UserAuthParams{
+		UserName: content.Login,
+		Password: newPass,
+	}
+
+	var jwt, jwtErr = logic.VerifyAuthRequest(authRequest)
+	if jwtErr != nil {
+		logic.Log("could not parse jwt for user "+authRequest.UserName, 1)
+		return
+	}
+
+	logic.Log("completed github OAuth sigin in for "+content.Login, 1)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"?login="+jwt+"&user="+content.Login, http.StatusPermanentRedirect)
+}
+
+func getGithubUserInfo(state string, code string) (*githubOauthUser, error) {
+	if state != oauth_state_string {
+		return nil, fmt.Errorf("invalid OAuth state")
+	}
+	var token, err = auth_provider.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		return nil, fmt.Errorf("code exchange failed: %s", err.Error())
+	}
+	if !token.Valid() {
+		return nil, fmt.Errorf("GitHub code exchange yielded invalid token")
+	}
+	var data []byte
+	data, err = json.Marshal(token)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert token to json: %s", err.Error())
+	}
+	var httpClient = &http.Client{}
+	var httpReq, reqErr = http.NewRequest("GET", "https://api.github.com/user", nil)
+	if reqErr != nil {
+		return nil, fmt.Errorf("failed to create request to GitHub")
+	}
+	httpReq.Header.Set("Authorization", "token "+token.AccessToken)
+	response, err := httpClient.Do(httpReq)
+	if err != nil {
+		return nil, fmt.Errorf("failed getting user info: %s", err.Error())
+	}
+	defer response.Body.Close()
+	contents, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed reading response body: %s", err.Error())
+	}
+	var userInfo = &githubOauthUser{}
+	if err = json.Unmarshal(contents, userInfo); err != nil {
+		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
+	}
+	userInfo.AccessToken = string(data)
+	return userInfo, nil
+}
+
+func verifyGithubUser(token *oauth2.Token) bool {
+	return token.Valid()
+}

+ 120 - 0
auth/google.go

@@ -0,0 +1,120 @@
+package auth
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+)
+
+var google_functions = map[string]interface{}{
+	init_provider:   initGoogle,
+	get_user_info:   getGoogleUserInfo,
+	handle_callback: handleGoogleCallback,
+	handle_login:    handleGoogleLogin,
+	verify_user:     verifyGoogleUser,
+}
+
+type googleOauthUser struct {
+	Email       string `json:"email" bson:"email"`
+	AccessToken string `json:"accesstoken" bson:"accesstoken"`
+}
+
+// == handle google authentication here ==
+
+func initGoogle(redirectURL string, clientID string, clientSecret string) {
+	auth_provider = &oauth2.Config{
+		RedirectURL:  redirectURL,
+		ClientID:     clientID,
+		ClientSecret: clientSecret,
+		Scopes:       []string{"https://www.googleapis.com/auth/userinfo.email"},
+		Endpoint:     google.Endpoint,
+	}
+}
+
+func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
+	oauth_state_string = logic.RandomString(16)
+	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	} else if auth_provider == nil {
+		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
+	}
+	var url = auth_provider.AuthCodeURL(oauth_state_string)
+	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
+}
+
+func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
+
+	var content, err = getGoogleUserInfo(r.FormValue("state"), r.FormValue("code"))
+	if err != nil {
+		logic.Log("error when getting user info from google: "+err.Error(), 1)
+		http.Redirect(w, r, servercfg.GetFrontendURL()+"?oauth=callback-error", http.StatusTemporaryRedirect)
+		return
+	}
+	_, err = logic.GetUser(content.Email)
+	if err != nil { // user must not exists, so try to make one
+		if err = addUser(content.Email); err != nil {
+			return
+		}
+	}
+	var newPass, fetchErr = fetchPassValue("")
+	if fetchErr != nil {
+		return
+	}
+	// send a netmaker jwt token
+	var authRequest = models.UserAuthParams{
+		UserName: content.Email,
+		Password: newPass,
+	}
+
+	var jwt, jwtErr = logic.VerifyAuthRequest(authRequest)
+	if jwtErr != nil {
+		logic.Log("could not parse jwt for user "+authRequest.UserName, 1)
+		return
+	}
+
+	logic.Log("completed google OAuth sigin in for "+content.Email, 1)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
+}
+
+func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) {
+	if state != oauth_state_string {
+		return nil, fmt.Errorf("invalid OAuth state")
+	}
+	var token, err = auth_provider.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		return nil, fmt.Errorf("code exchange failed: %s", err.Error())
+	}
+	var data []byte
+	data, err = json.Marshal(token)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert token to json: %s", err.Error())
+	}
+	response, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
+	if err != nil {
+		return nil, fmt.Errorf("failed getting user info: %s", err.Error())
+	}
+	defer response.Body.Close()
+	contents, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed reading response body: %s", err.Error())
+	}
+	var userInfo = &googleOauthUser{}
+	if err = json.Unmarshal(contents, userInfo); err != nil {
+		return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
+	}
+	userInfo.AccessToken = string(data)
+	return userInfo, nil
+}
+
+func verifyGoogleUser(token *oauth2.Token) bool {
+	return token.Valid()
+}

+ 7 - 4
compose/docker-compose.caddy.yml

@@ -3,16 +3,15 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.8.4
+    image: gravitl/netmaker:v0.8.5
     volumes:
-      - /etc/netclient/config:/etc/netclient/config
       - dnsconfig:/root/config/dnsconfig
       - /usr/bin/wg:/usr/bin/wg
       - sqldata:/root/data
     cap_add: 
       - NET_ADMIN
     restart: always
-    network_mode: host
+    privileged: true
     environment:
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -29,11 +28,15 @@ services:
       SERVER_GRPC_WIREGUARD: "off"
       CORS_ALLOWED_ORIGIN: "*"
       DATABASE: "sqlite"
+    ports:
+      - "51821-51830:51821-51830/udp"
+      - "8081:8081"
+      - "50051:50051"
   netmaker-ui:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.8
+    image: gravitl/netmaker-ui:v0.8.5
     links:
       - "netmaker:api"
     ports:

+ 8 - 5
compose/docker-compose.nodns.yml

@@ -3,15 +3,14 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.8.4
+    image: gravitl/netmaker:v0.8.5
     volumes:
-      - /etc/netclient/config:/etc/netclient/config
       - /usr/bin/wg:/usr/bin/wg
       - sqldata:/root/data
     cap_add: 
       - NET_ADMIN
     restart: always
-    network_mode: host
+    privileged: true
     environment:
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -28,11 +27,15 @@ services:
       SERVER_GRPC_WIREGUARD: "off"
       CORS_ALLOWED_ORIGIN: "*"
       DATABASE: "sqlite"
+    ports:
+      - "51821-51830:51821-51830/udp"
+      - "8081:8081"
+      - "50051:50051"
   netmaker-ui:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.8
+    image: gravitl/netmaker-ui:v0.8.5
     links:
       - "netmaker:api"
     ports:
@@ -53,4 +56,4 @@ services:
 volumes:
   caddy_data: {}
   caddy_conf: {}
-  sqldata: {}
+  sqldata: {}

+ 3 - 9
compose/docker-compose.reference.yml

@@ -11,19 +11,12 @@ services:
     container_name: netmaker
     depends_on:
       - rqlite
-    image: gravitl/netmaker:v0.7
-    volumes: # Volume mounts necessary for CLIENT_MODE to control netclient, wireguard, and networking on host (except dnsconfig, which is where dns config files are stored for use by CoreDNS)
-      - ./:/local
-      - /etc/netclient:/etc/netclient
+    image: gravitl/netmaker:v0.8.5
+    volumes: # Volume mounts necessary for CLIENT_MODE to control wireguard networking on host (except dnsconfig, which is where dns config files are stored for use by CoreDNS)
       - dnsconfig:/root/config/dnsconfig # Netmaker writes Corefile to this location, which gets mounted by CoreDNS for DNS configuration.
       - /usr/bin/wg:/usr/bin/wg
-      - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
-      - /run/systemd/system:/run/systemd/system
-      - /etc/systemd/system:/etc/systemd/system
-      - /sys/fs/cgroup:/sys/fs/cgroup
     cap_add: # Necessary for CLIENT_MODE. Should be removed if turned off. 
       - NET_ADMIN
-      - SYS_MODULE
     restart: always
     network_mode: host # Necessary for CLIENT_MODE. Should be removed if turned off, but then need to add port mappings
     environment:
@@ -32,6 +25,7 @@ services:
       SERVER_GRPC_HOST: "127.0.0.1" # Overrides SERVER_HOST if set. Useful for making HTTP and GRPC available via different interfaces/networks.
       API_PORT: 8081 # The HTTP API port for Netmaker. Used for API calls / communication from front end. If changed, need to change port of BACKEND_URL for netmaker-ui.
       GRPC_PORT: 50051 # The GRPC port for Netmaker. Used for communications from nodes.
+      CLIENT_MODE: "on" # on if netmaker should run its own client, off if not.
       MASTER_KEY: "secretkey" # The admin master key for accessing the API. Change this in any production installation.
       CORS_ALLOWED_ORIGIN: "*" # The "allowed origin" for API requests. Change to restrict where API requests can come from.
       REST_BACKEND: "on" # Enables the REST backend (API running on API_PORT at SERVER_HTTP_HOST). Change to "off" to turn off.

+ 7 - 4
compose/docker-compose.yml

@@ -3,16 +3,15 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.8.4
+    image: gravitl/netmaker:v0.8.5
     volumes:
-      - /etc/netclient/config:/etc/netclient/config
       - dnsconfig:/root/config/dnsconfig
       - /usr/bin/wg:/usr/bin/wg
       - sqldata:/root/data
     cap_add: 
       - NET_ADMIN
     restart: always
-    network_mode: host
+    privileged: true
     environment:
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -29,11 +28,15 @@ services:
       SERVER_GRPC_WIREGUARD: "off"
       CORS_ALLOWED_ORIGIN: "*"
       DATABASE: "sqlite"
+    ports:
+      - "51821-51830:51821-51830/udp"
+      - "8081:8081"
+      - "50051:50051"
   netmaker-ui:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.8
+    image: gravitl/netmaker-ui:v0.8.5
     links:
       - "netmaker:api"
     ports:

+ 35 - 32
config/config.go

@@ -30,49 +30,52 @@ var Config *EnvironmentConfig
 // EnvironmentConfig :
 type EnvironmentConfig struct {
 	Server ServerConfig `yaml:"server"`
-	SQL SQLConfig `yaml:"sql"`
+	SQL    SQLConfig    `yaml:"sql"`
 }
 
 // ServerConfig :
 type ServerConfig struct {
-	CoreDNSAddr          string `yaml:"corednsaddr"`
-	APIConnString        string `yaml:"apiconn"`
-	APIHost              string `yaml:"apihost"`
-	APIPort              string `yaml:"apiport"`
-	GRPCConnString       string `yaml:"grpcconn"`
-	GRPCHost             string `yaml:"grpchost"`
-	GRPCPort             string `yaml:"grpcport"`
-	GRPCSecure           string `yaml:"grpcsecure"`
-	MasterKey            string `yaml:"masterkey"`
-	AllowedOrigin        string `yaml:"allowedorigin"`
-	NodeID        string `yaml:"nodeid"`
-	RestBackend          string `yaml:"restbackend"`
-	AgentBackend         string `yaml:"agentbackend"`
-	ClientMode           string `yaml:"clientmode"`
-	DNSMode              string `yaml:"dnsmode"`
-	SplitDNS             string `yaml:"splitdns"`
-	DisableRemoteIPCheck string `yaml:"disableremoteipcheck"`
-	DisableDefaultNet    string `yaml:"disabledefaultnet"`
-	GRPCSSL              string `yaml:"grpcssl"`
-	Version              string `yaml:"version"`
-	SQLConn              string `yaml:"sqlconn"`
-	Platform             string `yaml:"platform"`
-	Database             string `yaml:database`
-	CheckinInterval      string `yaml:checkininterval`
-	DefaultNodeLimit     int32  `yaml:"defaultnodelimit"`
-	Verbosity            int32  `yaml:"verbosity"`
+	CoreDNSAddr           string `yaml:"corednsaddr"`
+	APIConnString         string `yaml:"apiconn"`
+	APIHost               string `yaml:"apihost"`
+	APIPort               string `yaml:"apiport"`
+	GRPCConnString        string `yaml:"grpcconn"`
+	GRPCHost              string `yaml:"grpchost"`
+	GRPCPort              string `yaml:"grpcport"`
+	GRPCSecure            string `yaml:"grpcsecure"`
+	MasterKey             string `yaml:"masterkey"`
+	AllowedOrigin         string `yaml:"allowedorigin"`
+	NodeID                string `yaml:"nodeid"`
+	RestBackend           string `yaml:"restbackend"`
+	AgentBackend          string `yaml:"agentbackend"`
+	ClientMode            string `yaml:"clientmode"`
+	DNSMode               string `yaml:"dnsmode"`
+	SplitDNS              string `yaml:"splitdns"`
+	DisableRemoteIPCheck  string `yaml:"disableremoteipcheck"`
+	DisableDefaultNet     string `yaml:"disabledefaultnet"`
+	GRPCSSL               string `yaml:"grpcssl"`
+	Version               string `yaml:"version"`
+	SQLConn               string `yaml:"sqlconn"`
+	Platform              string `yaml:"platform"`
+	Database              string `yaml:database`
+	CheckinInterval       string `yaml:checkininterval`
+	DefaultNodeLimit      int32  `yaml:"defaultnodelimit"`
+	Verbosity             int32  `yaml:"verbosity"`
 	ServerCheckinInterval int64  `yaml:"servercheckininterval"`
+	AuthProvider          string `yaml:"authprovider"`
+	ClientID              string `yaml:"clientid"`
+	ClientSecret          string `yaml:"clientsecret"`
+	FrontendURL           string `yaml:"frontendurl"`
 }
 
-
 // Generic SQL Config
 type SQLConfig struct {
-	Host string `yaml:"host"`
-	Port int32 `yaml:"port"`
+	Host     string `yaml:"host"`
+	Port     int32  `yaml:"port"`
 	Username string `yaml:"username"`
 	Password string `yaml:"password"`
-	DB string `yaml:"db"`
-	SSLMode string `yaml:"sslmode"`
+	DB       string `yaml:"db"`
+	SSLMode  string `yaml:"sslmode"`
 }
 
 //reading in the env file

+ 5 - 4
controllers/authGrpc.go

@@ -8,6 +8,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/functions"
 	nodepb "github.com/gravitl/netmaker/grpc"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"golang.org/x/crypto/bcrypt"
 	"google.golang.org/grpc"
@@ -68,7 +69,7 @@ func grpcAuthorize(ctx context.Context) error {
 
 	authToken := authHeader[0]
 
-	mac, network, err := functions.VerifyToken(authToken)
+	mac, network, err := logic.VerifyToken(authToken)
 	if err != nil {
 		return err
 	}
@@ -79,9 +80,9 @@ func grpcAuthorize(ctx context.Context) error {
 		return status.Errorf(codes.Unauthenticated, "Unauthorized. Network does not exist: "+network)
 	}
 	emptynode := models.Node{}
-	node, err := functions.GetNodeByMacAddress(network, mac)
+	node, err := logic.GetNodeByMacAddress(network, mac)
 	if database.IsEmptyRecord(err) {
-		if node, err = functions.GetDeletedNodeByMacAddress(network, mac); err == nil {
+		if node, err = logic.GetDeletedNodeByMacAddress(network, mac); err == nil {
 			if functions.RemoveDeletedNode(node.ID) {
 				return status.Errorf(codes.Unauthenticated, models.NODE_DELETE)
 			}
@@ -146,7 +147,7 @@ func (s *NodeServiceServer) Login(ctx context.Context, req *nodepb.Object) (*nod
 			return nil, err
 		} else {
 			//Create a new JWT for the node
-			tokenString, err := functions.CreateJWT(macaddress, result.Network)
+			tokenString, err := logic.CreateJWT(macaddress, result.Network)
 
 			if err != nil {
 				return nil, err

+ 4 - 4
controllers/common.go

@@ -5,8 +5,8 @@ import (
 	"strings"
 
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/dnslogic"
 	"github.com/gravitl/netmaker/functions"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 )
@@ -41,7 +41,7 @@ func DeleteNode(key string, exterminate bool) error {
 		return err
 	}
 	if servercfg.IsDNSMode() {
-		err = dnslogic.SetDNS()
+		err = logic.SetDNS()
 	}
 	return err
 }
@@ -60,7 +60,7 @@ func GetNode(macaddress string, network string) (models.Node, error) {
 
 	var node models.Node
 
-	key, err := functions.GetRecordKey(macaddress, network)
+	key, err := logic.GetRecordKey(macaddress, network)
 	if err != nil {
 		return node, err
 	}
@@ -75,7 +75,7 @@ func GetNode(macaddress string, network string) (models.Node, error) {
 	if err = json.Unmarshal([]byte(data), &node); err != nil {
 		return node, err
 	}
-	node.SetDefaults()
+	logic.SetNodeDefaults(&node)
 
 	return node, err
 }

+ 4 - 4
controllers/controller.go

@@ -10,6 +10,7 @@ import (
 
 	"github.com/gorilla/handlers"
 	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/servercfg"
 )
 
@@ -42,8 +43,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 			log.Println(err)
 		}
 	}()
-
-	log.Println("REST Server successfully started on port " + port + " (REST)")
+	logic.Log("REST Server successfully started on port "+port+" (REST)", 0)
 	c := make(chan os.Signal)
 
 	// Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
@@ -55,7 +55,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 	<-c
 
 	// After receiving CTRL+C Properly stop the server
-	log.Println("Stopping the REST server...")
+	logic.Log("Stopping the REST server...", 0)
 	srv.Shutdown(context.TODO())
-	log.Println("REST Server closed.")
+	logic.Log("REST Server closed.", 0)
 }

+ 17 - 17
controllers/dnsHttpController.go

@@ -7,8 +7,8 @@ import (
 	"github.com/go-playground/validator/v10"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/dnslogic"
 	"github.com/gravitl/netmaker/functions"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 )
 
@@ -59,12 +59,12 @@ func getAllDNS(w http.ResponseWriter, r *http.Request) {
 // GetAllDNS - gets all dns entries
 func GetAllDNS() ([]models.DNSEntry, error) {
 	var dns []models.DNSEntry
-	networks, err := models.GetNetworks()
+	networks, err := logic.GetNetworks()
 	if err != nil && !database.IsEmptyRecord(err) {
 		return []models.DNSEntry{}, err
 	}
 	for _, net := range networks {
-		netdns, err := dnslogic.GetDNS(net.NetID)
+		netdns, err := logic.GetDNS(net.NetID)
 		if err != nil {
 			return []models.DNSEntry{}, nil
 		}
@@ -105,7 +105,7 @@ func getCustomDNS(w http.ResponseWriter, r *http.Request) {
 	var dns []models.DNSEntry
 	var params = mux.Vars(r)
 
-	dns, err := dnslogic.GetCustomDNS(params["network"])
+	dns, err := logic.GetCustomDNS(params["network"])
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -121,7 +121,7 @@ func GetDNSEntryNum(domain string, network string) (int, error) {
 
 	num := 0
 
-	entries, err := dnslogic.GetDNS(network)
+	entries, err := logic.GetDNS(network)
 	if err != nil {
 		return 0, err
 	}
@@ -144,7 +144,7 @@ func getDNS(w http.ResponseWriter, r *http.Request) {
 	var dns []models.DNSEntry
 	var params = mux.Vars(r)
 
-	dns, err := dnslogic.GetDNS(params["network"])
+	dns, err := logic.GetDNS(params["network"])
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -174,7 +174,7 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
-	err = dnslogic.SetDNS()
+	err = logic.SetDNS()
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -229,7 +229,7 @@ func updateDNS(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
-	err = dnslogic.SetDNS()
+	err = logic.SetDNS()
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -252,7 +252,7 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 	}
 	entrytext := params["domain"] + "." + params["network"]
 	functions.PrintUserLog(models.NODE_SERVER_NAME, "deleted dns entry: "+entrytext, 1)
-	err = dnslogic.SetDNS()
+	err = logic.SetDNS()
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -267,7 +267,7 @@ func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
 	if err != nil {
 		return models.DNSEntry{}, err
 	}
-	key, err := functions.GetRecordKey(entry.Name, entry.Network)
+	key, err := logic.GetRecordKey(entry.Name, entry.Network)
 	if err != nil {
 		return models.DNSEntry{}, err
 	}
@@ -279,7 +279,7 @@ func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
 // GetDNSEntry - gets a DNS entry
 func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
 	var entry models.DNSEntry
-	key, err := functions.GetRecordKey(domain, network)
+	key, err := logic.GetRecordKey(domain, network)
 	if err != nil {
 		return entry, err
 	}
@@ -294,7 +294,7 @@ func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
 // UpdateDNS - updates DNS entry
 func UpdateDNS(dnschange models.DNSEntry, entry models.DNSEntry) (models.DNSEntry, error) {
 
-	key, err := functions.GetRecordKey(entry.Name, entry.Network)
+	key, err := logic.GetRecordKey(entry.Name, entry.Network)
 	if err != nil {
 		return entry, err
 	}
@@ -304,7 +304,7 @@ func UpdateDNS(dnschange models.DNSEntry, entry models.DNSEntry) (models.DNSEntr
 	if dnschange.Address != "" {
 		entry.Address = dnschange.Address
 	}
-	newkey, err := functions.GetRecordKey(entry.Name, entry.Network)
+	newkey, err := logic.GetRecordKey(entry.Name, entry.Network)
 
 	err = database.DeleteRecord(database.DNS_TABLE_NAME, key)
 	if err != nil {
@@ -318,7 +318,7 @@ func UpdateDNS(dnschange models.DNSEntry, entry models.DNSEntry) (models.DNSEntr
 
 // DeleteDNS - deletes a DNS entry
 func DeleteDNS(domain string, network string) error {
-	key, err := functions.GetRecordKey(domain, network)
+	key, err := logic.GetRecordKey(domain, network)
 	if err != nil {
 		return err
 	}
@@ -330,7 +330,7 @@ func pushDNS(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
 
-	err := dnslogic.SetDNS()
+	err := logic.SetDNS()
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
@@ -351,7 +351,7 @@ func ValidateDNSCreate(entry models.DNSEntry) error {
 	})
 
 	_ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
-		_, err := functions.GetParentNetwork(entry.Network)
+		_, err := logic.GetParentNetwork(entry.Network)
 		return err == nil
 	})
 
@@ -378,7 +378,7 @@ func ValidateDNSUpdate(change models.DNSEntry, entry models.DNSEntry) error {
 		return err == nil && num == 0
 	})
 	_ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
-		_, err := functions.GetParentNetwork(change.Network)
+		_, err := logic.GetParentNetwork(change.Network)
 		if err != nil {
 			functions.PrintUserLog("", err.Error(), 0)
 		}

+ 13 - 14
controllers/dnsHttpController_test.go

@@ -6,7 +6,6 @@ import (
 	"testing"
 
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/dnslogic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
@@ -68,33 +67,33 @@ func TestGetCustomDNS(t *testing.T) {
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
-		dns, err := dnslogic.GetCustomDNS("skynet")
+		dns, err := logic.GetCustomDNS("skynet")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.DNSEntry(nil), dns)
 	})
 	t.Run("NoNodes", func(t *testing.T) {
 		createNet()
-		dns, err := dnslogic.GetCustomDNS("skynet")
+		dns, err := logic.GetCustomDNS("skynet")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.DNSEntry(nil), dns)
 	})
 	t.Run("NodeExists", func(t *testing.T) {
 		createTestNode()
-		dns, err := dnslogic.GetCustomDNS("skynet")
+		dns, err := logic.GetCustomDNS("skynet")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, 0, len(dns))
 	})
 	t.Run("EntryExist", func(t *testing.T) {
 		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
 		CreateDNS(entry)
-		dns, err := dnslogic.GetCustomDNS("skynet")
+		dns, err := logic.GetCustomDNS("skynet")
 		assert.Nil(t, err)
 		assert.Equal(t, 1, len(dns))
 	})
 	t.Run("MultipleEntries", func(t *testing.T) {
 		entry := models.DNSEntry{"10.0.0.4", "host4", "skynet"}
 		CreateDNS(entry)
-		dns, err := dnslogic.GetCustomDNS("skynet")
+		dns, err := logic.GetCustomDNS("skynet")
 		assert.Nil(t, err)
 		assert.Equal(t, 2, len(dns))
 	})
@@ -125,7 +124,7 @@ func TestGetDNS(t *testing.T) {
 	deleteAllNetworks()
 	createNet()
 	t.Run("NoEntries", func(t *testing.T) {
-		dns, err := dnslogic.GetDNS("skynet")
+		dns, err := logic.GetDNS("skynet")
 		assert.Nil(t, err)
 		assert.Nil(t, dns)
 	})
@@ -133,7 +132,7 @@ func TestGetDNS(t *testing.T) {
 		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
 		_, err := CreateDNS(entry)
 		assert.Nil(t, err)
-		dns, err := dnslogic.GetDNS("skynet")
+		dns, err := logic.GetDNS("skynet")
 		t.Log(dns)
 		assert.Nil(t, err)
 		assert.NotNil(t, dns)
@@ -143,7 +142,7 @@ func TestGetDNS(t *testing.T) {
 	t.Run("NodeExists", func(t *testing.T) {
 		deleteAllDNS(t)
 		createTestNode()
-		dns, err := dnslogic.GetDNS("skynet")
+		dns, err := logic.GetDNS("skynet")
 		assert.Nil(t, err)
 		assert.NotNil(t, dns)
 		assert.Equal(t, "skynet", dns[0].Network)
@@ -152,7 +151,7 @@ func TestGetDNS(t *testing.T) {
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
 		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
 		_, err := CreateDNS(entry)
-		dns, err := dnslogic.GetDNS("skynet")
+		dns, err := logic.GetDNS("skynet")
 		t.Log(dns)
 		assert.Nil(t, err)
 		assert.NotNil(t, dns)
@@ -178,7 +177,7 @@ func TestSetDNS(t *testing.T) {
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
-		err := dnslogic.SetDNS()
+		err := logic.SetDNS()
 		assert.Nil(t, err)
 		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
@@ -187,7 +186,7 @@ func TestSetDNS(t *testing.T) {
 	})
 	t.Run("NoEntries", func(t *testing.T) {
 		createNet()
-		err := dnslogic.SetDNS()
+		err := logic.SetDNS()
 		assert.Nil(t, err)
 		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
@@ -196,7 +195,7 @@ func TestSetDNS(t *testing.T) {
 	})
 	t.Run("NodeExists", func(t *testing.T) {
 		createTestNode()
-		err := dnslogic.SetDNS()
+		err := logic.SetDNS()
 		assert.Nil(t, err)
 		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
@@ -208,7 +207,7 @@ func TestSetDNS(t *testing.T) {
 	t.Run("EntryExists", func(t *testing.T) {
 		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
 		CreateDNS(entry)
-		err := dnslogic.SetDNS()
+		err := logic.SetDNS()
 		assert.Nil(t, err)
 		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)

+ 10 - 25
controllers/extClientHttpController.go

@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"math/rand"
 	"net/http"
 	"strconv"
 	"time"
@@ -31,7 +30,7 @@ func extClientHandlers(r *mux.Router) {
 }
 
 func checkIngressExists(network string, macaddress string) bool {
-	node, err := functions.GetNodeByMacAddress(network, macaddress)
+	node, err := logic.GetNodeByMacAddress(network, macaddress)
 	if err != nil {
 		return false
 	}
@@ -134,7 +133,7 @@ func getExtClient(w http.ResponseWriter, r *http.Request) {
 // GetExtClient - gets a single ext client on a network
 func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 	var extclient models.ExtClient
-	key, err := functions.GetRecordKey(clientid, network)
+	key, err := logic.GetRecordKey(clientid, network)
 	if err != nil {
 		return extclient, err
 	}
@@ -161,14 +160,14 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	gwnode, err := functions.GetNodeByMacAddress(client.Network, client.IngressGatewayID)
+	gwnode, err := logic.GetNodeByMacAddress(client.Network, client.IngressGatewayID)
 	if err != nil {
 		functions.PrintUserLog(r.Header.Get("user"), "Could not retrieve Ingress Gateway Node "+client.IngressGatewayID, 1)
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
 
-	network, err := functions.GetParentNetwork(client.Network)
+	network, err := logic.GetParentNetwork(client.Network)
 	if err != nil {
 		functions.PrintUserLog(r.Header.Get("user"), "Could not retrieve Ingress Gateway Network "+client.Network, 1)
 		returnErrorResponse(w, r, formatError(err, "internal"))
@@ -180,7 +179,7 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	}
 	gwendpoint := gwnode.Endpoint + ":" + strconv.Itoa(int(gwnode.ListenPort))
 	newAllowedIPs := network.AddressRange
-	if egressGatewayRanges, err := client.GetEgressRangesOnNetwork(); err == nil {
+	if egressGatewayRanges, err := logic.GetEgressRangesOnNetwork(&client); err == nil {
 		for _, egressGatewayRange := range egressGatewayRanges {
 			newAllowedIPs += "," + egressGatewayRange
 		}
@@ -253,7 +252,7 @@ func CreateExtClient(extclient models.ExtClient) error {
 	}
 
 	if extclient.Address == "" {
-		newAddress, err := functions.UniqueAddress(extclient.Network)
+		newAddress, err := logic.UniqueAddress(extclient.Network)
 		if err != nil {
 			return err
 		}
@@ -266,7 +265,7 @@ func CreateExtClient(extclient models.ExtClient) error {
 
 	extclient.LastModified = time.Now().Unix()
 
-	key, err := functions.GetRecordKey(extclient.ClientID, extclient.Network)
+	key, err := logic.GetRecordKey(extclient.ClientID, extclient.Network)
 	if err != nil {
 		return err
 	}
@@ -301,7 +300,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	var extclient models.ExtClient
 	extclient.Network = networkName
 	extclient.IngressGatewayID = macaddress
-	node, err := functions.GetNodeByMacAddress(networkName, macaddress)
+	node, err := logic.GetNodeByMacAddress(networkName, macaddress)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -330,7 +329,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	var oldExtClient models.ExtClient
 	_ = json.NewDecoder(r.Body).Decode(&newExtClient)
 
-	key, err := functions.GetRecordKey(params["clientid"], params["network"])
+	key, err := logic.GetRecordKey(params["clientid"], params["network"])
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -368,7 +367,7 @@ func UpdateExtClient(newclientid string, network string, client models.ExtClient
 
 // DeleteExtClient - deletes an existing ext client
 func DeleteExtClient(network string, clientid string) error {
-	key, err := functions.GetRecordKey(clientid, network)
+	key, err := logic.GetRecordKey(clientid, network)
 	if err != nil {
 		return err
 	}
@@ -413,17 +412,3 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 		"Deleted extclient client "+params["clientid"]+" from network "+params["network"], 1)
 	returnSuccessResponse(w, r, params["clientid"]+" deleted.")
 }
-
-// StringWithCharset - returns a random string in a charset
-func StringWithCharset(length int, charset string) string {
-	b := make([]byte, length)
-	for i := range b {
-		b[i] = charset[seededRand.Intn(len(charset))]
-	}
-	return string(b)
-}
-
-const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-
-var seededRand *rand.Rand = rand.New(
-	rand.NewSource(time.Now().UnixNano()))

+ 12 - 12
controllers/networkHttpController.go

@@ -83,7 +83,7 @@ func SecurityCheck(reqAdmin bool, netname string, token string) (error, []string
 	isMasterAuthenticated := authenticateMaster(authToken)
 	username := ""
 	if !hasBearer || !isMasterAuthenticated {
-		userName, networks, isadmin, err := functions.VerifyUserToken(authToken)
+		userName, networks, isadmin, err := logic.VerifyUserToken(authToken)
 		username = userName
 		if err != nil {
 			return errors.New("error verifying user token"), nil, username
@@ -133,14 +133,14 @@ func getNetworks(w http.ResponseWriter, r *http.Request) {
 	allnetworks := []models.Network{}
 	err := errors.New("Networks Error")
 	if networksSlice[0] == ALL_NETWORK_ACCESS {
-		allnetworks, err = models.GetNetworks()
+		allnetworks, err = logic.GetNetworks()
 		if err != nil && !database.IsEmptyRecord(err) {
 			returnErrorResponse(w, r, formatError(err, "internal"))
 			return
 		}
 	} else {
 		for _, network := range networksSlice {
-			netObject, parentErr := functions.GetParentNetwork(network)
+			netObject, parentErr := logic.GetParentNetwork(network)
 			if parentErr == nil {
 				allnetworks = append(allnetworks, netObject)
 			}
@@ -228,7 +228,7 @@ func KeyUpdate(netname string) (models.Network, error) {
 func AlertNetwork(netid string) error {
 
 	var network models.Network
-	network, err := functions.GetParentNetwork(netid)
+	network, err := logic.GetParentNetwork(netid)
 	if err != nil {
 		return err
 	}
@@ -249,7 +249,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var network models.Network
 	netname := params["networkname"]
-	network, err := functions.GetParentNetwork(netname)
+	network, err := logic.GetParentNetwork(netname)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -260,21 +260,21 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
-	rangeupdate, localrangeupdate, err := network.Update(&newNetwork)
+	rangeupdate, localrangeupdate, err := logic.UpdateNetwork(&network, &newNetwork)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
 
 	if rangeupdate {
-		err = functions.UpdateNetworkNodeAddresses(network.NetID)
+		err = logic.UpdateNetworkNodeAddresses(network.NetID)
 		if err != nil {
 			returnErrorResponse(w, r, formatError(err, "internal"))
 			return
 		}
 	}
 	if localrangeupdate {
-		err = functions.UpdateNetworkLocalAddresses(network.NetID)
+		err = logic.UpdateNetworkLocalAddresses(network.NetID)
 		if err != nil {
 			returnErrorResponse(w, r, formatError(err, "internal"))
 			return
@@ -290,7 +290,7 @@ func updateNetworkNodeLimit(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var network models.Network
 	netname := params["networkname"]
-	network, err := functions.GetParentNetwork(netname)
+	network, err := logic.GetParentNetwork(netname)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -390,7 +390,7 @@ func CreateNetwork(network models.Network) error {
 	network.SetNetworkLastModified()
 	network.KeyUpdateTimeStamp = time.Now().Unix()
 
-	err := network.Validate(false)
+	err := logic.ValidateNetwork(&network, false)
 	if err != nil {
 		//returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return err
@@ -425,7 +425,7 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) {
 	var accesskey models.AccessKey
 	//start here
 	netname := params["networkname"]
-	network, err := functions.GetParentNetwork(netname)
+	network, err := logic.GetParentNetwork(netname)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -604,7 +604,7 @@ func deleteAccessKey(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 func DeleteKey(keyname, netname string) error {
-	network, err := functions.GetParentNetwork(netname)
+	network, err := logic.GetParentNetwork(netname)
 	if err != nil {
 		return err
 	}

+ 2 - 1
controllers/networkHttpController_test.go

@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 )
@@ -333,7 +334,7 @@ func TestValidateNetworkUpdate(t *testing.T) {
 
 func deleteAllNetworks() {
 	deleteAllNodes()
-	nets, _ := models.GetNetworks()
+	nets, _ := logic.GetNetworks()
 	for _, net := range nets {
 		DeleteNetwork(net.NetID)
 	}

+ 5 - 5
controllers/nodeGrpcController.go

@@ -35,7 +35,7 @@ func (s *NodeServiceServer) ReadNode(ctx context.Context, req *nodepb.Object) (*
 	if err != nil {
 		return nil, err
 	}
-	node.Update(&node)
+	logic.UpdateNode(&node, &node)
 	response := &nodepb.Object{
 		Data: string(nodeData),
 		Type: nodepb.NODE_TYPE,
@@ -55,8 +55,8 @@ func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.Object)
 
 	//Check to see if key is valid
 	//TODO: Triple inefficient!!! This is the third call to the DB we make for networks
-	validKey := functions.IsKeyValid(node.Network, node.AccessKey)
-	network, err := functions.GetParentNetwork(node.Network)
+	validKey := logic.IsKeyValid(node.Network, node.AccessKey)
+	network, err := logic.GetParentNetwork(node.Network)
 	if err != nil {
 		return nil, err
 	}
@@ -99,11 +99,11 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.Object)
 	macaddress := newnode.MacAddress
 	networkName := newnode.Network
 
-	node, err := functions.GetNodeByMacAddress(networkName, macaddress)
+	node, err := logic.GetNodeByMacAddress(networkName, macaddress)
 	if err != nil {
 		return nil, err
 	}
-	err = node.Update(&newnode)
+	err = logic.UpdateNode(&node, &newnode)
 	if err != nil {
 		return nil, err
 	}

+ 27 - 23
controllers/nodeHttpController.go

@@ -9,7 +9,6 @@ import (
 
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/dnslogic"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
@@ -107,7 +106,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 				return
 			} else {
 				//Create a new JWT for the node
-				tokenString, _ := functions.CreateJWT(authRequest.MacAddress, result.Network)
+				tokenString, _ := logic.CreateJWT(authRequest.MacAddress, result.Network)
 
 				if tokenString == "" {
 					errorResponse.Code = http.StatusBadRequest
@@ -193,7 +192,7 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha
 			//TODO: There's probably a better way of dealing with the "master token"/master password. Plz Help.
 			var isAuthorized = false
 			var macaddress = ""
-			username, networks, isadmin, errN := functions.VerifyUserToken(authToken)
+			username, networks, isadmin, errN := logic.VerifyUserToken(authToken)
 			isnetadmin := isadmin
 			if errN == nil && isadmin {
 				macaddress = "mastermac"
@@ -221,7 +220,7 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha
 					if isnetadmin {
 						isAuthorized = true
 					} else {
-						node, err := functions.GetNodeByMacAddress(params["network"], macaddress)
+						node, err := logic.GetNodeByMacAddress(params["network"], macaddress)
 						if err != nil {
 							errorResponse = models.ErrorResponse{
 								Code: http.StatusUnauthorized, Message: "W1R3: Missing Auth Token.",
@@ -285,14 +284,14 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 //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) {
 	w.Header().Set("Content-Type", "application/json")
-	user, err := functions.GetUser(r.Header.Get("user"))
+	user, err := logic.GetUser(r.Header.Get("user"))
 	if err != nil && r.Header.Get("ismasterkey") != "yes" {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
 	var nodes []models.Node
 	if user.IsAdmin || r.Header.Get("ismasterkey") == "yes" {
-		nodes, err = models.GetAllNodes()
+		nodes, err = logic.GetAllNodes()
 		if err != nil {
 			returnErrorResponse(w, r, formatError(err, "internal"))
 			return
@@ -392,7 +391,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 
 	node.Network = networkName
 
-	network, err := node.GetNetwork()
+	network, err := logic.GetNetworkByNode(&node)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -400,7 +399,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 
 	//Check to see if key is valid
 	//TODO: Triple inefficient!!! This is the third call to the DB we make for networks
-	validKey := functions.IsKeyValid(networkName, node.AccessKey)
+	validKey := logic.IsKeyValid(networkName, node.AccessKey)
 
 	if !validKey {
 		//Check to see if network will allow manual sign up
@@ -441,8 +440,9 @@ func uncordonNode(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode("SUCCESS")
 }
 
+// UncordonNode - approves a node to join a network
 func UncordonNode(network, macaddress string) (models.Node, error) {
-	node, err := functions.GetNodeByMacAddress(network, macaddress)
+	node, err := logic.GetNodeByMacAddress(network, macaddress)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -453,7 +453,7 @@ func UncordonNode(network, macaddress string) (models.Node, error) {
 	if err != nil {
 		return node, err
 	}
-	key, err := functions.GetRecordKey(node.MacAddress, node.Network)
+	key, err := logic.GetRecordKey(node.MacAddress, node.Network)
 	if err != nil {
 		return node, err
 	}
@@ -483,8 +483,9 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(node)
 }
 
+// CreateEgressGateway - creates an egress gateway
 func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
-	node, err := functions.GetNodeByMacAddress(gateway.NetID, gateway.NodeID)
+	node, err := logic.GetNodeByMacAddress(gateway.NetID, gateway.NodeID)
 	if node.OS == "windows" || node.OS == "macos" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for egress gateways")
 	}
@@ -515,7 +516,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 			postDownCmd = node.PostDown + "; " + postDownCmd
 		}
 	}
-	key, err := functions.GetRecordKey(gateway.NodeID, gateway.NetID)
+	key, err := logic.GetRecordKey(gateway.NodeID, gateway.NetID)
 	if err != nil {
 		return node, err
 	}
@@ -565,9 +566,10 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(node)
 }
 
+// DeleteEgressGateway - deletes egress from node
 func DeleteEgressGateway(network, macaddress string) (models.Node, error) {
 
-	node, err := functions.GetNodeByMacAddress(network, macaddress)
+	node, err := logic.GetNodeByMacAddress(network, macaddress)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -582,7 +584,7 @@ func DeleteEgressGateway(network, macaddress string) (models.Node, error) {
 	}
 	node.SetLastModified()
 	node.PullChanges = "yes"
-	key, err := functions.GetRecordKey(node.MacAddress, node.Network)
+	key, err := logic.GetRecordKey(node.MacAddress, node.Network)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -615,9 +617,10 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(node)
 }
 
+// CreateIngressGateway - creates an ingress gateway
 func CreateIngressGateway(netid string, macaddress string) (models.Node, error) {
 
-	node, err := functions.GetNodeByMacAddress(netid, macaddress)
+	node, err := logic.GetNodeByMacAddress(netid, macaddress)
 	if node.OS == "windows" || node.OS == "macos" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for ingress gateways")
 	}
@@ -626,7 +629,7 @@ func CreateIngressGateway(netid string, macaddress string) (models.Node, error)
 		return models.Node{}, err
 	}
 
-	network, err := functions.GetParentNetwork(netid)
+	network, err := logic.GetParentNetwork(netid)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -649,7 +652,7 @@ func CreateIngressGateway(netid string, macaddress string) (models.Node, error)
 	node.PostDown = postDownCmd
 	node.PullChanges = "yes"
 	node.UDPHolePunch = "no"
-	key, err := functions.GetRecordKey(node.MacAddress, node.Network)
+	key, err := logic.GetRecordKey(node.MacAddress, node.Network)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -679,13 +682,14 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(node)
 }
 
+// DeleteIngressGateway - deletes an ingress gateway
 func DeleteIngressGateway(networkName string, macaddress string) (models.Node, error) {
 
-	node, err := functions.GetNodeByMacAddress(networkName, macaddress)
+	node, err := logic.GetNodeByMacAddress(networkName, macaddress)
 	if err != nil {
 		return models.Node{}, err
 	}
-	network, err := functions.GetParentNetwork(networkName)
+	network, err := logic.GetParentNetwork(networkName)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -700,7 +704,7 @@ func DeleteIngressGateway(networkName string, macaddress string) (models.Node, e
 	node.IngressGatewayRange = ""
 	node.PullChanges = "yes"
 
-	key, err := functions.GetRecordKey(node.MacAddress, node.Network)
+	key, err := logic.GetRecordKey(node.MacAddress, node.Network)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -723,7 +727,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 
 	var node models.Node
 	//start here
-	node, err := functions.GetNodeByMacAddress(params["network"], params["macaddress"])
+	node, err := logic.GetNodeByMacAddress(params["network"], params["macaddress"])
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -749,7 +753,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 			}
 		}
 	}
-	err = node.Update(&newNode)
+	err = logic.UpdateNode(&node, &newNode)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -762,7 +766,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	}
 
 	if servercfg.IsDNSMode() {
-		err = dnslogic.SetDNS()
+		err = logic.SetDNS()
 	}
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))

+ 1 - 1
controllers/nodeHttpController_test.go

@@ -148,7 +148,7 @@ func TestValidateEgressGateway(t *testing.T) {
 ////func TestUpdateNode(t *testing.T) {
 ////}
 func deleteAllNodes() {
-	nodes, _ := models.GetAllNodes()
+	nodes, _ := logic.GetAllNodes()
 	for _, node := range nodes {
 		key := node.MacAddress + "###" + node.Network
 		DeleteNode(key, true)

+ 5 - 4
controllers/relay.go

@@ -9,6 +9,7 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/functions"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 )
 
@@ -35,7 +36,7 @@ func createRelay(w http.ResponseWriter, r *http.Request) {
 
 // CreateRelay - creates a relay
 func CreateRelay(relay models.RelayRequest) (models.Node, error) {
-	node, err := functions.GetNodeByMacAddress(relay.NetID, relay.NodeID)
+	node, err := logic.GetNodeByMacAddress(relay.NetID, relay.NodeID)
 	if node.OS == "windows" || node.OS == "macos" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for relay")
 	}
@@ -49,7 +50,7 @@ func CreateRelay(relay models.RelayRequest) (models.Node, error) {
 	node.IsRelay = "yes"
 	node.RelayAddrs = relay.RelayAddrs
 
-	key, err := functions.GetRecordKey(relay.NodeID, relay.NetID)
+	key, err := logic.GetRecordKey(relay.NodeID, relay.NetID)
 	if err != nil {
 		return node, err
 	}
@@ -147,7 +148,7 @@ func UpdateRelay(network string, oldAddrs []string, newAddrs []string) {
 // DeleteRelay - deletes a relay
 func DeleteRelay(network, macaddress string) (models.Node, error) {
 
-	node, err := functions.GetNodeByMacAddress(network, macaddress)
+	node, err := logic.GetNodeByMacAddress(network, macaddress)
 	if err != nil {
 		return models.Node{}, err
 	}
@@ -160,7 +161,7 @@ func DeleteRelay(network, macaddress string) (models.Node, error) {
 	node.RelayAddrs = []string{}
 	node.SetLastModified()
 	node.PullChanges = "yes"
-	key, err := functions.GetRecordKey(node.MacAddress, node.Network)
+	key, err := logic.GetRecordKey(node.MacAddress, node.Network)
 	if err != nil {
 		return models.Node{}, err
 	}

+ 2 - 2
controllers/responseHttp.go

@@ -2,9 +2,9 @@ package controller
 
 import (
 	"encoding/json"
-	"fmt"
 	"net/http"
 
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 )
 
@@ -48,7 +48,7 @@ func returnErrorResponse(response http.ResponseWriter, request *http.Request, er
 	if err != nil {
 		panic(err)
 	}
-	fmt.Println(errorMessage)
+	logic.Log("processed request error: "+errorMessage.Message, 1)
 	response.Header().Set("Content-Type", "application/json")
 	response.WriteHeader(errorMessage.Code)
 	response.Write(jsonResponse)

+ 2 - 2
controllers/serverHttpController.go

@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	"github.com/gorilla/mux"
-	"github.com/gravitl/netmaker/functions"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
@@ -42,7 +42,7 @@ func securityCheckServer(adminonly bool, next http.Handler) http.HandlerFunc {
 		}
 		//all endpoints here require master so not as complicated
 		//still might not be a good  way of doing this
-		user, _, isadmin, err := functions.VerifyUserToken(authToken)
+		user, _, isadmin, err := logic.VerifyUserToken(authToken)
 		errorResponse = models.ErrorResponse{
 			Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
 		}

+ 43 - 219
controllers/userHttpController.go

@@ -7,12 +7,12 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/go-playground/validator/v10"
 	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/auth"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/functions"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
-	"golang.org/x/crypto/bcrypt"
 )
 
 func userHandlers(r *mux.Router) {
@@ -21,11 +21,14 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods("POST")
 	r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods("POST")
 	r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT")
+	r.HandleFunc("/api/users/networks/{username}", authorizeUserAdm(http.HandlerFunc(updateUserNetworks))).Methods("PUT")
 	r.HandleFunc("/api/users/{username}/adm", authorizeUserAdm(http.HandlerFunc(updateUserAdm))).Methods("PUT")
 	r.HandleFunc("/api/users/{username}", authorizeUserAdm(http.HandlerFunc(createUser))).Methods("POST")
 	r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE")
 	r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET")
 	r.HandleFunc("/api/users", authorizeUserAdm(http.HandlerFunc(getUsers))).Methods("GET")
+	r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods("GET")
+	r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods("GET")
 }
 
 // Node authenticates using its password and retrieves a JWT for authorization.
@@ -46,7 +49,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 		return
 	}
 
-	jwt, err := VerifyAuthRequest(authRequest)
+	jwt, err := logic.VerifyAuthRequest(authRequest)
 	if err != nil {
 		returnErrorResponse(response, request, formatError(err, "badrequest"))
 		return
@@ -79,35 +82,6 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 	response.Write(successJSONResponse)
 }
 
-// VerifyAuthRequest - verifies an auth request
-func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
-	var result models.User
-	if authRequest.UserName == "" {
-		return "", errors.New("username can't be empty")
-	} else if authRequest.Password == "" {
-		return "", errors.New("password can't be empty")
-	}
-	//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved).
-	record, err := database.FetchRecord(database.USERS_TABLE_NAME, authRequest.UserName)
-	if err != nil {
-		return "", errors.New("incorrect credentials")
-	}
-	if err = json.Unmarshal([]byte(record), &result); err != nil {
-		return "", errors.New("incorrect credentials")
-	}
-
-	// compare password from request to stored password in database
-	// might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text...
-	// TODO: Consider a way of hashing the password client side before sending, or using certificates
-	if err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password)); err != nil {
-		return "", errors.New("incorrect credentials")
-	}
-
-	//Create a new JWT for the node
-	tokenString, _ := functions.CreateUserJWT(authRequest.UserName, result.Networks, result.IsAdmin)
-	return tokenString, nil
-}
-
 // The middleware for most requests to the API
 // They all pass  through here first
 // This will validate the JWT (or check for master token)
@@ -164,7 +138,7 @@ func ValidateUserToken(token string, user string, adminonly bool) error {
 		return errors.New("Missing Auth Token.")
 	}
 
-	username, _, isadmin, err := functions.VerifyUserToken(authToken)
+	username, _, isadmin, err := logic.VerifyUserToken(authToken)
 	if err != nil {
 		return errors.New("Error Verifying Auth Token")
 	}
@@ -181,37 +155,11 @@ func ValidateUserToken(token string, user string, adminonly bool) error {
 	return nil
 }
 
-// HasAdmin - checks if server has an admin
-func HasAdmin() (bool, error) {
-
-	collection, err := database.FetchRecords(database.USERS_TABLE_NAME)
-	if err != nil {
-		if database.IsEmptyRecord(err) {
-			return false, nil
-		} else {
-			return true, err
-
-		}
-	}
-	for _, value := range collection { // filter for isadmin true
-		var user models.User
-		err = json.Unmarshal([]byte(value), &user)
-		if err != nil {
-			continue
-		}
-		if user.IsAdmin {
-			return true, nil
-		}
-	}
-
-	return false, err
-}
-
 func hasAdmin(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
 
-	hasadmin, err := HasAdmin()
+	hasadmin, err := logic.HasAdmin()
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
@@ -221,20 +169,6 @@ func hasAdmin(w http.ResponseWriter, r *http.Request) {
 
 }
 
-// GetUser - gets a user
-func GetUser(username string) (models.ReturnUser, error) {
-
-	var user models.ReturnUser
-	record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
-	if err != nil {
-		return user, err
-	}
-	if err = json.Unmarshal([]byte(record), &user); err != nil {
-		return models.ReturnUser{}, err
-	}
-	return user, err
-}
-
 // GetUserInternal - gets an internal user
 func GetUserInternal(username string) (models.User, error) {
 
@@ -249,30 +183,6 @@ func GetUserInternal(username string) (models.User, error) {
 	return user, err
 }
 
-// GetUsers - gets users
-func GetUsers() ([]models.ReturnUser, error) {
-
-	var users []models.ReturnUser
-
-	collection, err := database.FetchRecords(database.USERS_TABLE_NAME)
-
-	if err != nil {
-		return users, err
-	}
-
-	for _, value := range collection {
-
-		var user models.ReturnUser
-		err = json.Unmarshal([]byte(value), &user)
-		if err != nil {
-			continue // get users
-		}
-		users = append(users, user)
-	}
-
-	return users, err
-}
-
 // Get an individual node. Nothin fancy here folks.
 func getUser(w http.ResponseWriter, r *http.Request) {
 	// set header.
@@ -280,7 +190,7 @@ func getUser(w http.ResponseWriter, r *http.Request) {
 
 	var params = mux.Vars(r)
 	usernameFetched := params["username"]
-	user, err := GetUser(usernameFetched)
+	user, err := logic.GetUser(usernameFetched)
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
@@ -295,7 +205,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
 
-	users, err := GetUsers()
+	users, err := logic.GetUsers()
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
@@ -306,42 +216,6 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(users)
 }
 
-// CreateUser - creates a user
-func CreateUser(user models.User) (models.User, error) {
-	// check if user exists
-	if _, err := GetUser(user.UserName); err == nil {
-		return models.User{}, errors.New("user exists")
-	}
-	err := ValidateUser("create", user)
-	if err != nil {
-		return models.User{}, err
-	}
-
-	// encrypt that password so we never see it again
-	hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
-	if err != nil {
-		return user, err
-	}
-	// set password to encrypted password
-	user.Password = string(hash)
-
-	tokenString, _ := functions.CreateUserJWT(user.UserName, user.Networks, user.IsAdmin)
-
-	if tokenString == "" {
-		// returnErrorResponse(w, r, errorResponse)
-		return user, err
-	}
-
-	// connect db
-	data, err := json.Marshal(&user)
-	if err != nil {
-		return user, err
-	}
-	err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME)
-
-	return user, err
-}
-
 func createAdmin(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -349,7 +223,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
 	// get node from body of request
 	_ = json.NewDecoder(r.Body).Decode(&admin)
 
-	admin, err := CreateAdmin(admin)
+	admin, err := logic.CreateAdmin(admin)
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
@@ -359,18 +233,6 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(admin)
 }
 
-func CreateAdmin(admin models.User) (models.User, error) {
-	hasadmin, err := HasAdmin()
-	if err != nil {
-		return models.User{}, err
-	}
-	if hasadmin {
-		return models.User{}, errors.New("admin user already exists")
-	}
-	admin.IsAdmin = true
-	return CreateUser(admin)
-}
-
 func createUser(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -378,7 +240,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
 	// get node from body of request
 	_ = json.NewDecoder(r.Body).Decode(&user)
 
-	user, err := CreateUser(user)
+	user, err := logic.CreateUser(user)
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
@@ -388,50 +250,32 @@ func createUser(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(user)
 }
 
-// UpdateUser - updates a given user
-func UpdateUser(userchange models.User, user models.User) (models.User, error) {
-	//check if user exists
-	if _, err := GetUser(user.UserName); err != nil {
-		return models.User{}, err
-	}
-
-	err := ValidateUser("update", userchange)
+func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	var user models.User
+	// start here
+	username := params["username"]
+	user, err := GetUserInternal(username)
 	if err != nil {
-		return models.User{}, err
-	}
-
-	queryUser := user.UserName
-
-	if userchange.UserName != "" {
-		user.UserName = userchange.UserName
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
 	}
-	if len(userchange.Networks) > 0 {
-		user.Networks = userchange.Networks
+	var userchange models.User
+	// we decode our body request params
+	err = json.NewDecoder(r.Body).Decode(&userchange)
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
 	}
-	if userchange.Password != "" {
-		// encrypt that password so we never see it again
-		hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
 
-		if err != nil {
-			return userchange, err
-		}
-		// set password to encrypted password
-		userchange.Password = string(hash)
-
-		user.Password = userchange.Password
-	}
-	if err = database.DeleteRecord(database.USERS_TABLE_NAME, queryUser); err != nil {
-		return models.User{}, err
-	}
-	data, err := json.Marshal(&user)
+	err = logic.UpdateUserNetworks(userchange.Networks, userchange.IsAdmin, &user)
 	if err != nil {
-		return models.User{}, err
-	}
-	if err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME); err != nil {
-		return models.User{}, err
+		returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
 	}
-	functions.PrintUserLog(models.NODE_SERVER_NAME, "updated user "+queryUser, 1)
-	return user, nil
+	functions.PrintUserLog(username, "status was updated", 1)
+	json.NewEncoder(w).Encode(user)
 }
 
 func updateUser(w http.ResponseWriter, r *http.Request) {
@@ -445,6 +289,10 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
+	if auth.IsOauthUser(&user) == nil {
+		returnErrorResponse(w, r, formatError(fmt.Errorf("can not update user info for oauth user %s", username), "forbidden"))
+		return
+	}
 	var userchange models.User
 	// we decode our body request params
 	err = json.NewDecoder(r.Body).Decode(&userchange)
@@ -453,7 +301,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	userchange.Networks = nil
-	user, err = UpdateUser(userchange, user)
+	user, err = logic.UpdateUser(userchange, user)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
@@ -473,6 +321,10 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
+	if auth.IsOauthUser(&user) != nil {
+		returnErrorResponse(w, r, formatError(fmt.Errorf("can not update user info for oauth user"), "forbidden"))
+		return
+	}
 	var userchange models.User
 	// we decode our body request params
 	err = json.NewDecoder(r.Body).Decode(&userchange)
@@ -480,7 +332,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
-	user, err = UpdateUser(userchange, user)
+	user, err = logic.UpdateUser(userchange, user)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
@@ -489,20 +341,6 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(user)
 }
 
-// DeleteUser - deletes a given user
-func DeleteUser(user string) (bool, error) {
-
-	if userRecord, err := database.FetchRecord(database.USERS_TABLE_NAME, user); err != nil || len(userRecord) == 0 {
-		return false, errors.New("user does not exist")
-	}
-
-	err := database.DeleteRecord(database.USERS_TABLE_NAME, user)
-	if err != nil {
-		return false, err
-	}
-	return true, nil
-}
-
 func deleteUser(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
@@ -511,7 +349,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 
 	username := params["username"]
-	success, err := DeleteUser(username)
+	success, err := logic.DeleteUser(username)
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
@@ -524,17 +362,3 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 	functions.PrintUserLog(username, "was deleted", 1)
 	json.NewEncoder(w).Encode(params["username"] + " deleted.")
 }
-
-// ValidateUser - validates a user model
-func ValidateUser(operation string, user models.User) error {
-
-	v := validator.New()
-	err := v.Struct(user)
-
-	if err != nil {
-		for _, e := range err.(validator.ValidationErrors) {
-			fmt.Println(e)
-		}
-	}
-	return err
-}

+ 46 - 45
controllers/userHttpController_test.go

@@ -4,52 +4,53 @@ import (
 	"testing"
 
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 )
 
 func deleteAllUsers() {
-	users, _ := GetUsers()
+	users, _ := logic.GetUsers()
 	for _, user := range users {
-		DeleteUser(user.UserName)
+		logic.DeleteUser(user.UserName)
 	}
 }
 
 func TestHasAdmin(t *testing.T) {
 	//delete all current users
 	database.InitializeDatabase()
-	users, _ := GetUsers()
+	users, _ := logic.GetUsers()
 	for _, user := range users {
-		success, err := DeleteUser(user.UserName)
+		success, err := logic.DeleteUser(user.UserName)
 		assert.Nil(t, err)
 		assert.True(t, success)
 	}
 	t.Run("NoUser", func(t *testing.T) {
-		found, err := HasAdmin()
+		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
 		assert.False(t, found)
 	})
 	t.Run("No admin user", func(t *testing.T) {
 		var user = models.User{"noadmin", "password", nil, false}
-		_, err := CreateUser(user)
+		_, err := logic.CreateUser(user)
 		assert.Nil(t, err)
-		found, err := HasAdmin()
+		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
 		assert.False(t, found)
 	})
 	t.Run("admin user", func(t *testing.T) {
 		var user = models.User{"admin", "password", nil, true}
-		_, err := CreateUser(user)
+		_, err := logic.CreateUser(user)
 		assert.Nil(t, err)
-		found, err := HasAdmin()
+		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
 		assert.True(t, found)
 	})
 	t.Run("multiple admins", func(t *testing.T) {
 		var user = models.User{"admin1", "password", nil, true}
-		_, err := CreateUser(user)
+		_, err := logic.CreateUser(user)
 		assert.Nil(t, err)
-		found, err := HasAdmin()
+		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
 		assert.True(t, found)
 	})
@@ -60,12 +61,12 @@ func TestCreateUser(t *testing.T) {
 	deleteAllUsers()
 	user := models.User{"admin", "password", nil, true}
 	t.Run("NoUser", func(t *testing.T) {
-		admin, err := CreateUser(user)
+		admin, err := logic.CreateUser(user)
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
 	})
 	t.Run("UserExists", func(t *testing.T) {
-		_, err := CreateUser(user)
+		_, err := logic.CreateUser(user)
 		assert.NotNil(t, err)
 		assert.EqualError(t, err, "user exists")
 	})
@@ -78,14 +79,14 @@ func TestCreateAdmin(t *testing.T) {
 	t.Run("NoAdmin", func(t *testing.T) {
 		user.UserName = "admin"
 		user.Password = "password"
-		admin, err := CreateAdmin(user)
+		admin, err := logic.CreateAdmin(user)
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
 	})
 	t.Run("AdminExists", func(t *testing.T) {
 		user.UserName = "admin2"
 		user.Password = "password1"
-		admin, err := CreateAdmin(user)
+		admin, err := logic.CreateAdmin(user)
 		assert.EqualError(t, err, "admin user already exists")
 		assert.Equal(t, admin, models.User{})
 	})
@@ -95,14 +96,14 @@ func TestDeleteUser(t *testing.T) {
 	database.InitializeDatabase()
 	deleteAllUsers()
 	t.Run("NonExistent User", func(t *testing.T) {
-		deleted, err := DeleteUser("admin")
+		deleted, err := logic.DeleteUser("admin")
 		assert.EqualError(t, err, "user does not exist")
 		assert.False(t, deleted)
 	})
 	t.Run("Existing User", func(t *testing.T) {
 		user := models.User{"admin", "password", nil, true}
-		CreateUser(user)
-		deleted, err := DeleteUser("admin")
+		logic.CreateUser(user)
+		deleted, err := logic.DeleteUser("admin")
 		assert.Nil(t, err)
 		assert.True(t, deleted)
 	})
@@ -114,44 +115,44 @@ func TestValidateUser(t *testing.T) {
 	t.Run("Valid Create", func(t *testing.T) {
 		user.UserName = "admin"
 		user.Password = "validpass"
-		err := ValidateUser("create", user)
+		err := logic.ValidateUser(user)
 		assert.Nil(t, err)
 	})
 	t.Run("Valid Update", func(t *testing.T) {
 		user.UserName = "admin"
 		user.Password = "password"
-		err := ValidateUser("update", user)
+		err := logic.ValidateUser(user)
 		assert.Nil(t, err)
 	})
 	t.Run("Invalid UserName", func(t *testing.T) {
 		t.Skip()
 		user.UserName = "*invalid"
-		err := ValidateUser("create", user)
+		err := logic.ValidateUser(user)
 		assert.Error(t, err)
 		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	t.Run("Short UserName", func(t *testing.T) {
 		t.Skip()
 		user.UserName = "1"
-		err := ValidateUser("create", user)
+		err := logic.ValidateUser(user)
 		assert.NotNil(t, err)
 		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	t.Run("Empty UserName", func(t *testing.T) {
 		t.Skip()
 		user.UserName = ""
-		err := ValidateUser("create", user)
+		err := logic.ValidateUser(user)
 		assert.EqualError(t, err, "some string")
 		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	t.Run("EmptyPassword", func(t *testing.T) {
 		user.Password = ""
-		err := ValidateUser("create", user)
+		err := logic.ValidateUser(user)
 		assert.EqualError(t, err, "Key: 'User.Password' Error:Field validation for 'Password' failed on the 'required' tag")
 	})
 	t.Run("ShortPassword", func(t *testing.T) {
 		user.Password = "123"
-		err := ValidateUser("create", user)
+		err := logic.ValidateUser(user)
 		assert.EqualError(t, err, "Key: 'User.Password' Error:Field validation for 'Password' failed on the 'min' tag")
 	})
 }
@@ -160,14 +161,14 @@ func TestGetUser(t *testing.T) {
 	database.InitializeDatabase()
 	deleteAllUsers()
 	t.Run("NonExistantUser", func(t *testing.T) {
-		admin, err := GetUser("admin")
+		admin, err := logic.GetUser("admin")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, "", admin.UserName)
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 		user := models.User{"admin", "password", nil, true}
-		CreateUser(user)
-		admin, err := GetUser("admin")
+		logic.CreateUser(user)
+		admin, err := logic.GetUser("admin")
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
 	})
@@ -183,7 +184,7 @@ func TestGetUserInternal(t *testing.T) {
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 		user := models.User{"admin", "password", nil, true}
-		CreateUser(user)
+		logic.CreateUser(user)
 		admin, err := GetUserInternal("admin")
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
@@ -194,21 +195,21 @@ func TestGetUsers(t *testing.T) {
 	database.InitializeDatabase()
 	deleteAllUsers()
 	t.Run("NonExistantUser", func(t *testing.T) {
-		admin, err := GetUsers()
+		admin, err := logic.GetUsers()
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 		user := models.User{"admin", "password", nil, true}
-		CreateUser(user)
-		admins, err := GetUsers()
+		logic.CreateUser(user)
+		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admins[0].UserName)
 	})
 	t.Run("MulipleUsers", func(t *testing.T) {
 		user := models.User{"user", "password", nil, true}
-		CreateUser(user)
-		admins, err := GetUsers()
+		logic.CreateUser(user)
+		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		for _, u := range admins {
 			if u.UserName == "admin" {
@@ -227,14 +228,14 @@ func TestUpdateUser(t *testing.T) {
 	user := models.User{"admin", "password", nil, true}
 	newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true}
 	t.Run("NonExistantUser", func(t *testing.T) {
-		admin, err := UpdateUser(newuser, user)
+		admin, err := logic.UpdateUser(newuser, user)
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, "", admin.UserName)
 	})
 
 	t.Run("UserExists", func(t *testing.T) {
-		CreateUser(user)
-		admin, err := UpdateUser(newuser, user)
+		logic.CreateUser(user)
+		admin, err := logic.UpdateUser(newuser, user)
 		assert.Nil(t, err)
 		assert.Equal(t, newuser.UserName, admin.UserName)
 	})
@@ -271,43 +272,43 @@ func TestVerifyAuthRequest(t *testing.T) {
 	t.Run("EmptyUserName", func(t *testing.T) {
 		authRequest.UserName = ""
 		authRequest.Password = "Password"
-		jwt, err := VerifyAuthRequest(authRequest)
+		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "username can't be empty")
 	})
 	t.Run("EmptyPassword", func(t *testing.T) {
 		authRequest.UserName = "admin"
 		authRequest.Password = ""
-		jwt, err := VerifyAuthRequest(authRequest)
+		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "password can't be empty")
 	})
 	t.Run("NonExistantUser", func(t *testing.T) {
 		authRequest.UserName = "admin"
 		authRequest.Password = "password"
-		jwt, err := VerifyAuthRequest(authRequest)
+		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "incorrect credentials")
 	})
 	t.Run("Non-Admin", func(t *testing.T) {
 		user := models.User{"nonadmin", "somepass", nil, false}
-		CreateUser(user)
+		logic.CreateUser(user)
 		authRequest := models.UserAuthParams{"nonadmin", "somepass"}
-		jwt, err := VerifyAuthRequest(authRequest)
+		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.NotNil(t, jwt)
 		assert.Nil(t, err)
 	})
 	t.Run("WrongPassword", func(t *testing.T) {
 		user := models.User{"admin", "password", nil, false}
-		CreateUser(user)
+		logic.CreateUser(user)
 		authRequest := models.UserAuthParams{"admin", "badpass"}
-		jwt, err := VerifyAuthRequest(authRequest)
+		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "incorrect credentials")
 	})
 	t.Run("Success", func(t *testing.T) {
 		authRequest := models.UserAuthParams{"admin", "password"}
-		jwt, err := VerifyAuthRequest(authRequest)
+		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Nil(t, err)
 		assert.NotNil(t, jwt)
 	})

+ 6 - 2
database/database.go

@@ -39,6 +39,9 @@ const SERVERCONF_TABLE_NAME = "serverconf"
 // DATABASE_FILENAME - database file name
 const DATABASE_FILENAME = "netmaker.db"
 
+// GENERATED_TABLE_NAME - stores server generated k/v
+const GENERATED_TABLE_NAME = "generated"
+
 // == ERROR CONSTS ==
 
 // NO_RECORD - no singular result found
@@ -87,11 +90,11 @@ func getCurrentDB() map[string]interface{} {
 }
 
 func InitializeDatabase() error {
-	log.Println("connecting to", servercfg.GetDB())
+	log.Println("[netmaker] connecting to", servercfg.GetDB())
 	tperiod := time.Now().Add(10 * time.Second)
 	for {
 		if err := getCurrentDB()[INIT_DB].(func() error)(); err != nil {
-			log.Println("unable to connect to db, retrying . . .")
+			log.Println("[netmaker] unable to connect to db, retrying . . .")
 			if time.Now().After(tperiod) {
 				return err
 			}
@@ -114,6 +117,7 @@ func createTables() {
 	createTable(INT_CLIENTS_TABLE_NAME)
 	createTable(PEERS_TABLE_NAME)
 	createTable(SERVERCONF_TABLE_NAME)
+	createTable(GENERATED_TABLE_NAME)
 }
 
 func createTable(tableName string) error {

BIN
docs/_build/doctrees/about.doctree


BIN
docs/_build/doctrees/api.doctree


BIN
docs/_build/doctrees/architecture.doctree


BIN
docs/_build/doctrees/client-installation.doctree


BIN
docs/_build/doctrees/conduct.doctree


BIN
docs/_build/doctrees/environment.pickle


BIN
docs/_build/doctrees/external-clients.doctree


BIN
docs/_build/doctrees/getting-started.doctree


BIN
docs/_build/doctrees/index.doctree


BIN
docs/_build/doctrees/install.doctree


BIN
docs/_build/doctrees/license.doctree


BIN
docs/_build/doctrees/oauth.doctree


BIN
docs/_build/doctrees/quick-start-nginx.doctree


BIN
docs/_build/doctrees/quick-start.doctree


BIN
docs/_build/doctrees/server-installation.doctree


BIN
docs/_build/doctrees/support.doctree


BIN
docs/_build/doctrees/troubleshoot.doctree


BIN
docs/_build/doctrees/usage.doctree


+ 1 - 1
docs/_build/html/.buildinfo

@@ -1,4 +1,4 @@
 # Sphinx build info version 1
 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
-config: c42328d03e8a69bc128d18c18d0c8114
+config: 99bfa40b6c844b896e1a7ae8e49d7971
 tags: 645f666f9bcd5a90fca523b33c5a78b7

BIN
docs/_build/html/_images/extclient5.png


BIN
docs/_build/html/_images/oauth1.png


BIN
docs/_build/html/_images/oauth2.png


BIN
docs/_build/html/_images/oauth3.png


+ 2 - 2
docs/_build/html/_sources/architecture.rst.txt

@@ -104,7 +104,7 @@ If running in daemon mode, on a periodic basis (systemd timer), the netclient pe
 The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.
 
 
-Datavase (sqlite, rqlite, postgres)
+Database (sqlite, rqlite, postgres)
 -------------------------------------
 
 As of v0.8, Netmaker uses sqlite by default as a database. It can also use PostgreSQL, or rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users. 
@@ -191,4 +191,4 @@ To manage DNS (optional), the node must have systemd-resolved. Systems that have
 Limitations
 =============
 
-Install limitations mostly include platform-specific dependencies. A failed netclient install should display information about which command is failing, or which libraries are missing. This can often be solved via machine upgrade, installing missing dependencies, or setting kernel headers on the machine for WireGuard (e.x.: `Installing Kernel Headers on Debian <https://stackoverflow.com/questions/62356581/wireguard-vpn-how-to-fix-operation-not-supported-if-it-worked-before>`_) 
+Install limitations mostly include platform-specific dependencies. A failed netclient install should display information about which command is failing, or which libraries are missing. This can often be solved via machine upgrade, installing missing dependencies, or setting kernel headers on the machine for WireGuard (e.x.: `Installing Kernel Headers on Debian <https://stackoverflow.com/questions/62356581/wireguard-vpn-how-to-fix-operation-not-supported-if-it-worked-before>`_) 

+ 1 - 1
docs/_build/html/_sources/external-clients.rst.txt

@@ -64,7 +64,7 @@ Configuring DNS for Ext Clients (OPTIONAL)
 If you wish to have a DNS field on your ext clients conf, simply edit the network field as shown below to 1.1.1.1 or 8.8.8.8 for example.
 If you do not want DNS on your ext client conf files, simply leave it blank.
 
-.. image:: images/exclient5.png
+.. image:: images/extclient5.png
    :width: 80%
    :alt: Gateway
    :align: center

+ 11 - 0
docs/_build/html/_sources/index.rst.txt

@@ -93,6 +93,17 @@ A detailed guide to installing the Netmaker server (API, DB, UI, DNS), and confi
    
    server-installation
 
+Oauth Configuration
+--------------------
+
+A simple guide to configuring OAuth for Netmaker.
+
+.. toctree::
+   :maxdepth: 2
+   
+   oauth
+
+
 Client Installation
 --------------------
 

+ 77 - 0
docs/_build/html/_sources/oauth.rst.txt

@@ -0,0 +1,77 @@
+===================
+Integrating OAuth
+====================
+
+Introduction
+==============
+
+As of v0.8.5, Netmaker offers integration with the following OAuth providers: 
+
+- GitHub
+- Google
+- Microsoft Azure AD
+
+By integrating with an OAuth provider, your Netmaker users can log in via the provider, rather than the default simple auth.
+
+Configuring your provider
+===========================
+
+In order to use OAuth, configure your OAuth provider (GitHub, Google, Azure AD).
+
+You must configure your provider to use the Netmaker Dashboard URI dashboard.<netmaker.base.domain> as the origin URL.
+
+For example: `https://dashboard.netmaker.mydomain.com`
+
+You must configure your provider to use the Netmaker API URI redirect route with the following format: https://api.<netmaker base domain>/api/oauth2/callback.
+
+For example: `https://api.netmaker.mydomain.com/api/oauth2/callback`
+
+General provider instructions can be found with the following links:
+
+Instructions for GitHub: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#github-auth-provider
+Instructions for Google: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#google-auth-provider
+Instructions for Microsoft Azure AD: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#microsoft-azure-ad-provider 
+
+Configuring Netmaker
+======================
+
+After you have configured your OAuth provider, take note of the CLIENT_ID and CLIENT_SECRET.
+
+Next, Configure Netmaker with the following environment variables. If any are left blank, OAuth will fail.
+
+.. code-block::
+
+    AUTH_PROVIDER="<azure-ad|github|google>"
+    CLIENT_ID="<client id of your oauth provider>"
+    CLIENT_SECRET="<client secret of your oauth provider>"
+    SERVER_API_CONN_STRING="https://<your-netmaker-api-domain>"
+    FRONTEND_URL="https://<your-netmaker-dashboard-domain>"
+
+
+After restarting your server, the Netmaker logs will indicate if the OAuth provider was successfully initialized.
+
+Once successful, users can click the key symbol on the login page to sign-in with your configured OAuth provider.
+
+.. image:: images/oauth1.png
+   :width: 80%
+   :alt: Login Oauth
+   :align: center
+
+Configuring User Permissions
+===============================
+
+All users logging in will have zero permissions on first sign-in. An admin must configure all user permissions.
+
+Admins must navigate to the "Users" screen to configure permissions.
+
+For each user, an admin must specify which networks that user has access to configure. Additionally, an Admin can elevate a user to Admin permissions.
+
+.. image:: images/oauth2.png
+   :width: 80%
+   :alt: Edit User
+   :align: center
+
+.. image:: images/oauth3.png
+   :width: 80%
+   :alt: Edit User 2
+   :align: center

+ 8 - 1
docs/_build/html/_sources/server-installation.rst.txt

@@ -159,7 +159,9 @@ DNS Mode Setup
 
 If you plan on running the server in DNS Mode, know that a `CoreDNS Server <https://coredns.io/manual/toc/>`_ will be installed. CoreDNS is a light-weight, fast, and easy-to-configure DNS server. It is recommended to bind CoreDNS to port 53 of the host system, and it will do so by default. The clients will expect the nameserver to be on port 53, and many systems have issues resolving a different port.
 
-However, on your host system (for Netmaker), this may conflict with an existing process. On linux systems running systemd-resolved, there is likely a service consuming port 53. The below steps will disable systemd-resolved, and replace it with a generic (e.g. Google) nameserver. Be warned that this may have consequences for any existing private DNS configuration. The following was tested on Ubuntu 20.04 and should be run prior to deploying the docker containers.
+However, on your host system (for Netmaker), this may conflict with an existing process. On linux systems running systemd-resolved, there is likely a service consuming port 53. The below steps will disable systemd-resolved, and replace it with a generic (e.g. Google) nameserver. Be warned that this may have consequences for any existing private DNS configuration. 
+
+With the latest docker-compose, it is not necessary to perform these steps. But if you are running the install and find that port 53 is blocked, you can perform the following steps, which were tested on Ubuntu 20.04 (these should be run prior to deploying the docker containers).
 
 .. code-block::
 
@@ -199,6 +201,11 @@ Assuming you have Docker and Docker Compose installed, you can just run the foll
   sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml
   docker-compose up -d`
 
+Traefik Proxy
+------------------------
+
+To install with Traefik, rather than Nginx or the default Caddy, check out this repo: https://github.com/bsherman/netmaker-traefik 
+
 
 No DNS - CoreDNS Disabled
 ----------------------------------------------

+ 20 - 63
docs/_build/html/_static/basic.css

@@ -130,7 +130,7 @@ ul.search li a {
     font-weight: bold;
 }
 
-ul.search li p.context {
+ul.search li div.context {
     color: #888;
     margin: 2px 0 0 30px;
     text-align: left;
@@ -508,63 +508,6 @@ table.hlist td {
     vertical-align: top;
 }
 
-/* -- object description styles --------------------------------------------- */
-
-.sig {
-	font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
-}
-
-.sig-name, code.descname {
-    background-color: transparent;
-    font-weight: bold;
-}
-
-.sig-name {
-	font-size: 1.1em;
-}
-
-code.descname {
-    font-size: 1.2em;
-}
-
-.sig-prename, code.descclassname {
-    background-color: transparent;
-}
-
-.optional {
-    font-size: 1.3em;
-}
-
-.sig-paren {
-    font-size: larger;
-}
-
-.sig-param.n {
-	font-style: italic;
-}
-
-/* C++ specific styling */
-
-.sig-inline.c-texpr,
-.sig-inline.cpp-texpr {
-	font-family: unset;
-}
-
-.sig.c   .k, .sig.c   .kt,
-.sig.cpp .k, .sig.cpp .kt {
-	color: #0033B3;
-}
-
-.sig.c   .m,
-.sig.cpp .m {
-	color: #1750EB;
-}
-
-.sig.c   .s, .sig.c   .sc,
-.sig.cpp .s, .sig.cpp .sc {
-	color: #067D17;
-}
-
 
 /* -- other body styles ----------------------------------------------------- */
 
@@ -691,6 +634,14 @@ dl.glossary dt {
     font-size: 1.1em;
 }
 
+.optional {
+    font-size: 1.3em;
+}
+
+.sig-paren {
+    font-size: larger;
+}
+
 .versionmodified {
     font-style: italic;
 }
@@ -820,11 +771,7 @@ div.code-block-caption code {
 table.highlighttable td.linenos,
 span.linenos,
 div.doctest > div.highlight span.gp {  /* gp: Generic.Prompt */
-  user-select: none;
-  -webkit-user-select: text; /* Safari fallback only */
-  -webkit-user-select: none; /* Chrome/Safari */
-  -moz-user-select: none; /* Firefox */
-  -ms-user-select: none; /* IE10+ */
+    user-select: none;
 }
 
 div.code-block-caption span.caption-number {
@@ -839,6 +786,16 @@ div.literal-block-wrapper {
     margin: 1em 0;
 }
 
+code.descname {
+    background-color: transparent;
+    font-weight: bold;
+    font-size: 1.2em;
+}
+
+code.descclassname {
+    background-color: transparent;
+}
+
 code.xref, a code {
     background-color: transparent;
     font-weight: bold;

+ 1 - 1
docs/_build/html/_static/documentation_options.js

@@ -1,6 +1,6 @@
 var DOCUMENTATION_OPTIONS = {
     URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
-    VERSION: '0.8.4',
+    VERSION: '0.8.5',
     LANGUAGE: 'None',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',

File diff suppressed because it is too large
+ 0 - 1
docs/_build/html/_static/jquery.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/jquery.min.map


+ 6 - 1
docs/_build/html/_static/pygments.css

@@ -1,5 +1,10 @@
+pre { line-height: 125%; margin: 0; }
+td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
+span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
+td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
+span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
 .highlight .hll { background-color: #ffffcc }
-.highlight  { background: #f8f8f8; }
+.highlight { background: #f8f8f8; }
 .highlight .c { color: #008800; font-style: italic } /* Comment */
 .highlight .err { border: 1px solid #FF0000 } /* Error */
 .highlight .k { color: #AA22FF; font-weight: bold } /* Keyword */

+ 1 - 1
docs/_build/html/_static/searchtools.js

@@ -509,7 +509,7 @@ var Search = {
     var excerpt = ((start > 0) ? '...' : '') +
       $.trim(text.substr(start, 240)) +
       ((start + 240 - text.length) ? '...' : '');
-    var rv = $('<p class="context"></p>').text(excerpt);
+    var rv = $('<div class="context"></div>').text(excerpt);
     $.each(hlwords, function() {
       rv = rv.highlightText(this, 'highlighted');
     });

+ 6 - 1
docs/_build/html/_static/stylesheets/application-fixes.css

@@ -415,4 +415,9 @@ div.rendered_html table {
     display: block;
     margin-left: auto;
     margin-right: auto;
-}
+}
+
+/* GH 93 */
+dl.citation dt span.brackets {
+  margin-right: 0.3rem;
+}

File diff suppressed because it is too large
+ 3 - 4
docs/_build/html/_static/underscore.js


+ 10 - 10
docs/_build/html/about.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>About &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>About &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -884,7 +884,7 @@ If you’re familiar with AWS, it’s like a VPC but made up of arbitrary comput
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/api.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>API Reference &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>API Reference &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -1005,7 +1005,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 13 - 13
docs/_build/html/architecture.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Architecture &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Architecture &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -268,7 +268,7 @@
         </li>
         <li class="md-nav__item"><a href="#netclient" class="md-nav__link">Netclient</a>
         </li>
-        <li class="md-nav__item"><a href="#datavase-sqlite-rqlite-postgres" class="md-nav__link">Datavase (sqlite, rqlite, postgres)</a>
+        <li class="md-nav__item"><a href="#database-sqlite-rqlite-postgres" class="md-nav__link">Database (sqlite, rqlite, postgres)</a>
         </li>
         <li class="md-nav__item"><a href="#netmaker-ui" class="md-nav__link">Netmaker UI</a>
         </li>
@@ -831,7 +831,7 @@
         </li>
         <li class="md-nav__item"><a href="#netclient" class="md-nav__link">Netclient</a>
         </li>
-        <li class="md-nav__item"><a href="#datavase-sqlite-rqlite-postgres" class="md-nav__link">Datavase (sqlite, rqlite, postgres)</a>
+        <li class="md-nav__item"><a href="#database-sqlite-rqlite-postgres" class="md-nav__link">Database (sqlite, rqlite, postgres)</a>
         </li>
         <li class="md-nav__item"><a href="#netmaker-ui" class="md-nav__link">Netmaker UI</a>
         </li>
@@ -919,7 +919,7 @@
 <p>The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.</p>
 
 
-<h3 id="datavase-sqlite-rqlite-postgres">Datavase (sqlite, rqlite, postgres)<a class="headerlink" href="#datavase-sqlite-rqlite-postgres" title="Permalink to this headline">¶</a></h3>
+<h3 id="database-sqlite-rqlite-postgres">Database (sqlite, rqlite, postgres)<a class="headerlink" href="#database-sqlite-rqlite-postgres" title="Permalink to this headline">¶</a></h3>
 <p>As of v0.8, Netmaker uses sqlite by default as a database. It can also use PostgreSQL, or rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users.</p>
 <p>Additional database support (besides sqlite and rqlite) is very easy to implement for special use cases. Netmaker uses simple key value lookups to run the networks, and the database was designed to be extensible, so support for key-value stores and other SQL-based databases can be achieved by changing a single file.</p>
 
@@ -1046,7 +1046,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 14 - 14
docs/_build/html/client-installation.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Client Installation &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Client Installation &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -964,15 +964,15 @@
    <span class="l l-Scalar l-Scalar-Plain">--key value, -k value                Access Key for signing up machine with Netmaker server during initial 'add'. [$NETCLIENT_ACCESSKEY]</span>
    <span class="l l-Scalar l-Scalar-Plain">--token value, -t value              Access Token for signing up machine with Netmaker server during initial 'add'. [$NETCLIENT_ACCESSTOKEN]</span>
    <span class="l l-Scalar l-Scalar-Plain">--localrange value                   Local Range if network is local, for instance 192.168.1.0/24. [$NETCLIENT_LOCALRANGE]</span>
-   <span class="nt">--dns value                          Sets private dns if 'on'. Ignores if 'off'. Will retrieve from network if unset. (default</span><span class="p">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_DNS]</span>
+   <span class="l l-Scalar l-Scalar-Plain">--dns value                          Sets private dns if 'on'. Ignores if 'off'. Will retrieve from network if unset. (default</span><span class="p p-Indicator">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_DNS]</span>
    <span class="l l-Scalar l-Scalar-Plain">--islocal value                      Sets endpoint to local address if 'yes'. Ignores if 'no'. Will retrieve from network if unset. [$NETCLIENT_IS_LOCAL]</span>
    <span class="l l-Scalar l-Scalar-Plain">--isdualstack value                  Sets ipv6 address if 'yes'. Ignores if 'no'. Will retrieve from network if unset. [$NETCLIENT_IS_DUALSTACK]</span>
    <span class="l l-Scalar l-Scalar-Plain">--udpholepunch value                 Turns on udp holepunching if 'yes'. Ignores if 'no'. Will retrieve from network if unset. [$NETCLIENT_UDP_HOLEPUNCH]</span>
-   <span class="nt">--ipforwarding value                 Sets ip forwarding on if 'on'. Ignores if 'off'. On by default. (default</span><span class="p">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_IPFORWARDING]</span>
+   <span class="l l-Scalar l-Scalar-Plain">--ipforwarding value                 Sets ip forwarding on if 'on'. Ignores if 'off'. On by default. (default</span><span class="p p-Indicator">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_IPFORWARDING]</span>
    <span class="l l-Scalar l-Scalar-Plain">--postup value                       Sets PostUp command for WireGuard. [$NETCLIENT_POSTUP]</span>
    <span class="l l-Scalar l-Scalar-Plain">--postdown value                     Sets PostDown command for WireGuard. [$NETCLIENT_POSTDOWN]</span>
-   <span class="nt">--daemon value                       Installs daemon if 'on'. Ignores if 'off'. On by default. (default</span><span class="p">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_DAEMON]</span>
-   <span class="nt">--roaming value                      Checks for IP changes if 'on'. Ignores if 'off'. On by default. (default</span><span class="p">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_ROAMING]</span>
+   <span class="l l-Scalar l-Scalar-Plain">--daemon value                       Installs daemon if 'on'. Ignores if 'off'. On by default. (default</span><span class="p p-Indicator">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_DAEMON]</span>
+   <span class="l l-Scalar l-Scalar-Plain">--roaming value                      Checks for IP changes if 'on'. Ignores if 'off'. On by default. (default</span><span class="p p-Indicator">:</span> <span class="s">"on"</span><span class="l l-Scalar l-Scalar-Plain">) [$NETCLIENT_ROAMING]</span>
    <span class="l l-Scalar l-Scalar-Plain">--help, -h                           show help (default</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">false)</span>
 </pre></div>
 </div>
@@ -1109,7 +1109,7 @@ If a key is provided (-k), then a token is unnecessary, but grpc, server, ports,
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/conduct.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Code of Conduct &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Code of Conduct &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -927,7 +927,7 @@ available <a class="reference external" href="https://contributor-covenant.org/v
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 39 - 11
docs/_build/html/external-clients.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>External Clients &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>External Clients &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -485,6 +485,34 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html" class="md-nav__link">Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -851,7 +879,7 @@
 <h2 id="configuring-dns-for-ext-clients-optional">Configuring DNS for Ext Clients (OPTIONAL)<a class="headerlink" href="#configuring-dns-for-ext-clients-optional" title="Permalink to this headline">¶</a></h2>
 <p>If you wish to have a DNS field on your ext clients conf, simply edit the network field as shown below to 1.1.1.1 or 8.8.8.8 for example.
 If you do not want DNS on your ext client conf files, simply leave it blank.</p>
-<a class="reference internal image-reference" href="images/exclient5.png"><img alt="Gateway" class="align-center" src="images/exclient5.png" style="width: 80%;"/></a>
+<a class="reference internal image-reference" href="_images/extclient5.png"><img alt="Gateway" class="align-center" src="_images/extclient5.png" style="width: 80%;"/></a>
 <p>Important to note, your client automatically adds egress gateway ranges (if any on the same network) to it’s allowed IPs.</p>
 
 
@@ -903,7 +931,7 @@ If you do not want DNS on your ext client conf files, simply leave it blank.</p>
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 38 - 10
docs/_build/html/genindex.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Index &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Index &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -79,7 +79,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -101,7 +101,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -165,7 +165,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -177,13 +177,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -483,6 +483,34 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html" class="md-nav__link">Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -795,7 +823,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/getting-started.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Getting Started &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Getting Started &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -940,7 +940,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 52 - 10
docs/_build/html/index.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Welcome to the Netmaker Documentation &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Welcome to the Netmaker Documentation &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -80,7 +80,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="#" title="Netmaker 0.8.4 documentation"
+        <a href="#" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -102,7 +102,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -166,7 +166,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="#" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="#" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -178,13 +178,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="#" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="#" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="#"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -484,6 +484,34 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html" class="md-nav__link">Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -772,6 +800,8 @@
         </li>
         <li class="md-nav__item"><a href="#server-installation" class="md-nav__link">Server Installation</a>
         </li>
+        <li class="md-nav__item"><a href="#oauth-configuration" class="md-nav__link">Oauth Configuration</a>
+        </li>
         <li class="md-nav__item"><a href="#client-installation" class="md-nav__link">Client Installation</a>
         </li>
         <li class="md-nav__item"><a href="#external-clients" class="md-nav__link">External Clients</a>
@@ -903,6 +933,18 @@
 </div>
 
 
+<h2 id="oauth-configuration">Oauth Configuration<a class="headerlink" href="#oauth-configuration" title="Permalink to this headline">¶</a></h2>
+<p>A simple guide to configuring OAuth for Netmaker.</p>
+<div class="toctree-wrapper compound">
+<ul>
+<li class="toctree-l1"><a class="reference internal" href="oauth.html">Introduction</a></li>
+<li class="toctree-l1"><a class="reference internal" href="oauth.html#configuring-your-provider">Configuring your provider</a></li>
+<li class="toctree-l1"><a class="reference internal" href="oauth.html#configuring-netmaker">Configuring Netmaker</a></li>
+<li class="toctree-l1"><a class="reference internal" href="oauth.html#configuring-user-permissions">Configuring User Permissions</a></li>
+</ul>
+</div>
+
+
 <h2 id="client-installation">Client Installation<a class="headerlink" href="#client-installation" title="Permalink to this headline">¶</a></h2>
 <p>A detailed guide to installing the Netmaker agent (netclient) on devices and configuration options.</p>
 <div class="toctree-wrapper compound">
@@ -1048,7 +1090,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/install.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Install &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Install &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -835,7 +835,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/license.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>License &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>License &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -80,7 +80,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -102,7 +102,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -166,7 +166,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -178,13 +178,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -816,7 +816,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 929 - 0
docs/_build/html/oauth.html

@@ -0,0 +1,929 @@
+
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta name="viewport" content="width=device-width,initial-scale=1">
+  <meta http-equiv="x-ua-compatible" content="ie=edge">
+  <meta name="lang:clipboard.copy" content="Copy to clipboard">
+  <meta name="lang:clipboard.copied" content="Copied to clipboard">
+  <meta name="lang:search.language" content="en">
+  <meta name="lang:search.pipeline.stopwords" content="True">
+  <meta name="lang:search.pipeline.trimmer" content="True">
+  <meta name="lang:search.result.none" content="No matching documents">
+  <meta name="lang:search.result.one" content="1 matching document">
+  <meta name="lang:search.result.other" content="# matching documents">
+  <meta name="lang:search.tokenizer" content="[\s\-]+">
+
+  
+    <link href="https://fonts.gstatic.com/" rel="preconnect" crossorigin>
+    <link href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500,700|Roboto:300,400,400i,700&display=fallback" rel="stylesheet">
+
+    <style>
+      body,
+      input {
+        font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif
+      }
+
+      code,
+      kbd,
+      pre {
+        font-family: "Roboto Mono", "Courier New", Courier, monospace
+      }
+    </style>
+  
+
+  <link rel="stylesheet" href="_static/stylesheets/application.css"/>
+  <link rel="stylesheet" href="_static/stylesheets/application-palette.css"/>
+  <link rel="stylesheet" href="_static/stylesheets/application-fixes.css"/>
+  
+  <link rel="stylesheet" href="_static/fonts/material-icons.css"/>
+  
+  <meta name="theme-color" content="#3f51b5">
+  <script src="_static/javascripts/modernizr.js"></script>
+  
+  
+  
+    <title>Introduction &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <script src="_static/jquery.js"></script>
+    <script src="_static/underscore.js"></script>
+    <script src="_static/doctools.js"></script>
+    <link rel="author" title="About these documents" href="about.html" />
+    <link rel="index" title="Index" href="genindex.html" />
+    <link rel="search" title="Search" href="search.html" />
+    <link rel="next" title="Client Installation" href="client-installation.html" />
+    <link rel="prev" title="Advanced Server Installation" href="server-installation.html" />
+  
+   
+
+  </head>
+  <body dir=ltr
+        data-md-color-primary=indigo data-md-color-accent=light-blue>
+  
+  <svg class="md-svg">
+    <defs data-children-count="0">
+      
+      <svg xmlns="http://www.w3.org/2000/svg" width="416" height="448" viewBox="0 0 416 448" id="__github"><path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19T128 352t-18.125-8.5-10.75-19T96 304t3.125-20.5 10.75-19T128 256t18.125 8.5 10.75 19T160 304zm160 0q0 10-3.125 20.5t-10.75 19T288 352t-18.125-8.5-10.75-19T256 304t3.125-20.5 10.75-19T288 256t18.125 8.5 10.75 19T320 304zm40 0q0-30-17.25-51T296 232q-10.25 0-48.75 5.25Q229.5 240 208 240t-39.25-2.75Q130.75 232 120 232q-29.5 0-46.75 21T56 304q0 22 8 38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0 37.25-1.75t35-7.375 30.5-15 20.25-25.75T360 304zm56-44q0 51.75-15.25 82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5T212 416q-19.5 0-35.5-.75t-36.875-3.125-38.125-7.5-34.25-12.875T37 371.5t-21.5-28.75Q0 312 0 260q0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25 30.875Q171.5 96 212 96q37 0 70 8 26.25-20.5 46.75-30.25T376 64q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34 99.5z"/></svg>
+      
+    </defs>
+  </svg>
+  
+  <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer">
+  <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search">
+  <label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
+  <a href="#oauth" tabindex="1" class="md-skip"> Skip to content </a>
+  <header class="md-header" data-md-component="header">
+  <nav class="md-header-nav md-grid">
+    <div class="md-flex navheader">
+      <div class="md-flex__cell md-flex__cell--shrink">
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
+           class="md-header-nav__button md-logo">
+          
+            <i class="md-icon">&#xe869</i>
+          
+        </a>
+      </div>
+      <div class="md-flex__cell md-flex__cell--shrink">
+        <label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label>
+      </div>
+      <div class="md-flex__cell md-flex__cell--stretch">
+        <div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
+          <span class="md-header-nav__topic">Netmaker Docs</span>
+          <span class="md-header-nav__topic"> Introduction </span>
+        </div>
+      </div>
+      <div class="md-flex__cell md-flex__cell--shrink">
+        <label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
+        
+<div class="md-search" data-md-component="search" role="dialog">
+  <label class="md-search__overlay" for="__search"></label>
+  <div class="md-search__inner" role="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
+      <input type="text" class="md-search__input" name="q" placeholder="Search"
+             autocapitalize="off" autocomplete="off" spellcheck="false"
+             data-md-component="query" data-md-state="active">
+      <label class="md-icon md-search__icon" for="__search"></label>
+      <button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
+        &#xE5CD;
+      </button>
+    </form>
+    <div class="md-search__output">
+      <div class="md-search__scrollwrap" data-md-scrollfix>
+        <div class="md-search-result" data-md-component="result">
+          <div class="md-search-result__meta">
+            Type to start searching
+          </div>
+          <ol class="md-search-result__list"></ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+      </div>
+      
+        <div class="md-flex__cell md-flex__cell--shrink">
+          <div class="md-header-nav__source">
+            <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
+
+    <div class="md-source__icon">
+      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" width="28" height="28">
+        <use xlink:href="#__github" width="24" height="24"></use>
+      </svg>
+    </div>
+  
+  <div class="md-source__repository">
+    Netmaker
+  </div>
+</a>
+          </div>
+        </div>
+      
+      
+  
+  <script src="_static/javascripts/version_dropdown.js"></script>
+  <script>
+    var json_loc = ""versions.json"",
+        target_loc = "../",
+        text = "Versions";
+    $( document ).ready( add_version_dropdown(json_loc, target_loc, text));
+  </script>
+  
+
+    </div>
+  </nav>
+</header>
+
+  
+  <div class="md-container">
+    
+    
+    
+  <nav class="md-tabs" data-md-component="tabs">
+    <div class="md-tabs__inner md-grid">
+      <ul class="md-tabs__list">
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
+      </ul>
+    </div>
+  </nav>
+    <main class="md-main">
+      <div class="md-main__inner md-grid" data-md-component="container">
+        
+          <div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
+            <div class="md-sidebar__scrollwrap">
+              <div class="md-sidebar__inner">
+                <nav class="md-nav md-nav--primary" data-md-level="0">
+  <label class="md-nav__title md-nav__title--site" for="__drawer">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
+      
+        <i class="md-icon">&#xe869</i>
+      
+    </a>
+    <a href="index.html"
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
+  </label>
+    <div class="md-nav__source">
+      <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
+
+    <div class="md-source__icon">
+      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" width="28" height="28">
+        <use xlink:href="#__github" width="24" height="24"></use>
+      </svg>
+    </div>
+  
+  <div class="md-source__repository">
+    Netmaker
+  </div>
+</a>
+    </div>
+  
+  
+
+  
+  <ul class="md-nav__list">
+    <li class="md-nav__item">
+    
+    
+      <a href="about.html" class="md-nav__link">About</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="about.html#what-is-netmaker" class="md-nav__link">What is Netmaker?</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="about.html#how-does-netmaker-work" class="md-nav__link">How Does Netmaker Work?</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="about.html#use-cases-for-netmaker" class="md-nav__link">Use Cases for Netmaker</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="architecture.html" class="md-nav__link">Architecture</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="architecture.html#core-concepts" class="md-nav__link">Core Concepts</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="architecture.html#components" class="md-nav__link">Components</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="architecture.html#technical-process" class="md-nav__link">Technical Process</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="architecture.html#compatible-systems-for-netclient" class="md-nav__link">Compatible Systems for Netclient</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="architecture.html#limitations" class="md-nav__link">Limitations</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html" class="md-nav__link">Quick Install</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html#introduction" class="md-nav__link">Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html#prerequisites" class="md-nav__link">0. Prerequisites</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html#prepare-dns" class="md-nav__link">1. Prepare DNS</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html#install-dependencies" class="md-nav__link">2. Install Dependencies</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html#open-firewall" class="md-nav__link">3. Open Firewall</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start.html#install-netmaker" class="md-nav__link">4. Install Netmaker</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="getting-started.html" class="md-nav__link">Getting Started</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="getting-started.html#setup" class="md-nav__link">Setup</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="getting-started.html#deploy-nodes" class="md-nav__link">Deploy Nodes</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="getting-started.html#manage-nodes" class="md-nav__link">Manage Nodes</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="getting-started.html#uninstalling-the-netclient" class="md-nav__link">Uninstalling the netclient</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="getting-started.html#uninstalling-netmaker" class="md-nav__link">Uninstalling Netmaker</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start-nginx.html" class="md-nav__link">Install with Nginx (depreciated)</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start-nginx.html#introduction" class="md-nav__link">0. Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start-nginx.html#prerequisites" class="md-nav__link">1. Prerequisites</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start-nginx.html#install-dependencies" class="md-nav__link">2. Install Dependencies</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start-nginx.html#prepare-vm" class="md-nav__link">3. Prepare VM</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="quick-start-nginx.html#install-netmaker" class="md-nav__link">4. Install Netmaker</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html" class="md-nav__link">Advanced Server Installation</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#system-compatibility" class="md-nav__link">System Compatibility</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#server-configuration-reference" class="md-nav__link">Server Configuration Reference</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#dns-mode-setup" class="md-nav__link">DNS Mode Setup</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#docker-compose-install" class="md-nav__link">Docker Compose Install</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#linux-install-without-docker" class="md-nav__link">Linux Install without Docker</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#kubernetes-install" class="md-nav__link">Kubernetes Install</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+    <input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="__toc">
+    <label class="md-nav__link md-nav__link--active" for="__toc"> Introduction </label>
+    
+      <a href="#" class="md-nav__link md-nav__link--active">Introduction</a>
+      
+        
+<nav class="md-nav md-nav--secondary">
+    <label class="md-nav__title" for="__toc">Contents</label>
+  <ul class="md-nav__list" data-md-scrollfix="">
+        <li class="md-nav__item"><a href="#oauth--page-root" class="md-nav__link">Introduction</a>
+        </li>
+        <li class="md-nav__item"><a href="#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+        </li>
+        <li class="md-nav__item"><a href="#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+        </li>
+        <li class="md-nav__item"><a href="#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+        </li>
+  </ul>
+</nav>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html" class="md-nav__link">Client Installation</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#introduction-to-netclient" class="md-nav__link">Introduction to Netclient</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#notes-on-windows" class="md-nav__link">Notes on Windows</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#modes-and-system-compatibility" class="md-nav__link">Modes and System Compatibility</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#prerequisites" class="md-nav__link">Prerequisites</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#configuration" class="md-nav__link">Configuration</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#installation" class="md-nav__link">Installation</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="client-installation.html#managing-netclient" class="md-nav__link">Managing Netclient</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="external-clients.html" class="md-nav__link">External Clients</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="external-clients.html#introduction" class="md-nav__link">Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="external-clients.html#configuring-an-ingress-gateway" class="md-nav__link">Configuring an Ingress Gateway</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="external-clients.html#adding-clients-to-a-gateway" class="md-nav__link">Adding Clients to a Gateway</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="external-clients.html#configuring-dns-for-ext-clients-optional" class="md-nav__link">Configuring DNS for Ext Clients (OPTIONAL)</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="usage.html" class="md-nav__link">Using Netmaker</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="usage.html#external-tutorials" class="md-nav__link">External Tutorials</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="api.html" class="md-nav__link">API Reference</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="api.html#api-usage" class="md-nav__link">API Usage</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="api.html#authentication" class="md-nav__link">Authentication</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="api.html#format-of-calls-for-curl" class="md-nav__link">Format of Calls for Curl</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="api.html#api-documentation" class="md-nav__link">API Documentation</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="troubleshoot.html" class="md-nav__link">Troubleshooting</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="troubleshoot.html#common-issues" class="md-nav__link">Common Issues</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="troubleshoot.html#server" class="md-nav__link">Server</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="troubleshoot.html#ui" class="md-nav__link">UI</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="troubleshoot.html#netclient" class="md-nav__link">Netclient</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="troubleshoot.html#coredns" class="md-nav__link">CoreDNS</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="support.html" class="md-nav__link">Support</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="support.html#faq" class="md-nav__link">FAQ</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="support.html#contact" class="md-nav__link">Contact</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html" class="md-nav__link">Code of Conduct</a>
+      <ul class="md-nav__list"> 
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html#our-pledge" class="md-nav__link">Our Pledge</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html#our-standards" class="md-nav__link">Our Standards</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html#our-responsibilities" class="md-nav__link">Our Responsibilities</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html#scope" class="md-nav__link">Scope</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html#enforcement" class="md-nav__link">Enforcement</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="conduct.html#attribution" class="md-nav__link">Attribution</a>
+      
+    
+    </li></ul>
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="license.html" class="md-nav__link">License</a>
+      
+    
+    </li>
+  </ul>
+  
+
+</nav>
+              </div>
+            </div>
+          </div>
+          <div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
+            <div class="md-sidebar__scrollwrap">
+              <div class="md-sidebar__inner">
+                
+<nav class="md-nav md-nav--secondary">
+    <label class="md-nav__title" for="__toc">Contents</label>
+  <ul class="md-nav__list" data-md-scrollfix="">
+        <li class="md-nav__item"><a href="#oauth--page-root" class="md-nav__link">Introduction</a>
+        </li>
+        <li class="md-nav__item"><a href="#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+        </li>
+        <li class="md-nav__item"><a href="#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+        </li>
+        <li class="md-nav__item"><a href="#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+        </li>
+  </ul>
+</nav>
+              </div>
+            </div>
+          </div>
+        
+        <div class="md-content">
+          <article class="md-content__inner md-typeset" role="main">
+            
+  
+<h1 id="oauth--page-root">Introduction<a class="headerlink" href="#oauth--page-root" title="Permalink to this headline">¶</a></h1>
+<p>As of v0.8.5, Netmaker offers integration with the following OAuth providers:</p>
+<ul class="simple">
+<li><p>GitHub</p></li>
+<li><p>Google</p></li>
+<li><p>Microsoft Azure AD</p></li>
+</ul>
+<p>By integrating with an OAuth provider, your Netmaker users can log in via the provider, rather than the default simple auth.</p>
+
+
+<h1 id="configuring-your-provider">Configuring your provider<a class="headerlink" href="#configuring-your-provider" title="Permalink to this headline">¶</a></h1>
+<p>In order to use OAuth, configure your OAuth provider (GitHub, Google, Azure AD).</p>
+<p>You must configure your provider to use the Netmaker Dashboard URI dashboard.&lt;netmaker.base.domain&gt; as the origin URL.</p>
+<p>For example: <cite>https://dashboard.netmaker.mydomain.com</cite></p>
+<p>You must configure your provider to use the Netmaker API URI redirect route with the following format: <a class="reference external" href="https://api">https://api</a>.&lt;netmaker base domain&gt;/api/oauth2/callback.</p>
+<p>For example: <cite>https://api.netmaker.mydomain.com/api/oauth2/callback</cite></p>
+<p>General provider instructions can be found with the following links:</p>
+<p>Instructions for GitHub: <a class="reference external" href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#github-auth-provider">https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#github-auth-provider</a>
+Instructions for Google: <a class="reference external" href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#google-auth-provider">https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#google-auth-provider</a>
+Instructions for Microsoft Azure AD: <a class="reference external" href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#microsoft-azure-ad-provider">https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#microsoft-azure-ad-provider</a></p>
+
+
+<h1 id="configuring-netmaker">Configuring Netmaker<a class="headerlink" href="#configuring-netmaker" title="Permalink to this headline">¶</a></h1>
+<p>After you have configured your OAuth provider, take note of the CLIENT_ID and CLIENT_SECRET.</p>
+<p>Next, Configure Netmaker with the following environment variables. If any are left blank, OAuth will fail.</p>
+<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">AUTH_PROVIDER</span><span class="o">=</span><span class="s2">"&lt;azure-ad|github|google&gt;"</span>
+<span class="n">CLIENT_ID</span><span class="o">=</span><span class="s2">"&lt;client id of your oauth provider&gt;"</span>
+<span class="n">CLIENT_SECRET</span><span class="o">=</span><span class="s2">"&lt;client secret of your oauth provider&gt;"</span>
+<span class="n">SERVER_API_CONN_STRING</span><span class="o">=</span><span class="s2">"https://&lt;your-netmaker-api-domain&gt;"</span>
+<span class="n">FRONTEND_URL</span><span class="o">=</span><span class="s2">"https://&lt;your-netmaker-dashboard-domain&gt;"</span>
+</pre></div>
+</div>
+<p>After restarting your server, the Netmaker logs will indicate if the OAuth provider was successfully initialized.</p>
+<p>Once successful, users can click the key symbol on the login page to sign-in with your configured OAuth provider.</p>
+<a class="reference internal image-reference" href="_images/oauth1.png"><img alt="Login Oauth" class="align-center" src="_images/oauth1.png" style="width: 80%;"/></a>
+
+
+<h1 id="configuring-user-permissions">Configuring User Permissions<a class="headerlink" href="#configuring-user-permissions" title="Permalink to this headline">¶</a></h1>
+<p>All users logging in will have zero permissions on first sign-in. An admin must configure all user permissions.</p>
+<p>Admins must navigate to the “Users” screen to configure permissions.</p>
+<p>For each user, an admin must specify which networks that user has access to configure. Additionally, an Admin can elevate a user to Admin permissions.</p>
+<a class="reference internal image-reference" href="_images/oauth2.png"><img alt="Edit User" class="align-center" src="_images/oauth2.png" style="width: 80%;"/></a>
+<a class="reference internal image-reference" href="_images/oauth3.png"><img alt="Edit User 2" class="align-center" src="_images/oauth3.png" style="width: 80%;"/></a>
+
+
+
+          </article>
+        </div>
+      </div>
+    </main>
+  </div>
+  <footer class="md-footer">
+    <div class="md-footer-nav">
+      <nav class="md-footer-nav__inner md-grid">
+          
+            <a href="server-installation.html" title="Advanced Server Installation"
+               class="md-flex md-footer-nav__link md-footer-nav__link--prev"
+               rel="prev">
+              <div class="md-flex__cell md-flex__cell--shrink">
+                <i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
+              </div>
+              <div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
+                <span class="md-flex__ellipsis">
+                  <span
+                      class="md-footer-nav__direction"> Previous </span> Advanced Server Installation </span>
+              </div>
+            </a>
+          
+          
+            <a href="client-installation.html" title="Client Installation"
+               class="md-flex md-footer-nav__link md-footer-nav__link--next"
+               rel="next">
+            <div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title"><span
+                class="md-flex__ellipsis"> <span
+                class="md-footer-nav__direction"> Next </span> Client Installation </span>
+            </div>
+            <div class="md-flex__cell md-flex__cell--shrink"><i
+                class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
+            </div>
+          
+        </a>
+        
+      </nav>
+    </div>
+    <div class="md-footer-meta md-typeset">
+      <div class="md-footer-meta__inner md-grid">
+        <div class="md-footer-copyright">
+          <div class="md-footer-copyright__highlight">
+              &#169; Copyright 2021, Alex Feiszli.
+              
+          </div>
+            Created using
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
+             and
+            <a href="https://github.com/bashtage/sphinx-material/">Material for
+              Sphinx</a>
+        </div>
+      </div>
+    </div>
+  </footer>
+  <script src="_static/javascripts/application.js"></script>
+  <script>app.initialize({version: "1.0.4", url: {base: ".."}})</script>
+  </body>
+</html>

BIN
docs/_build/html/objects.inv


+ 10 - 10
docs/_build/html/quick-start-nginx.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Install with Nginx (depreciated) &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Install with Nginx (depreciated) &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -1049,7 +1049,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/quick-start.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Quick Install &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Quick Install &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -988,7 +988,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 38 - 10
docs/_build/html/search.html

@@ -46,11 +46,11 @@
   
   
   
-    <title>Search &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
+    <title>Search &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
     
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -85,7 +85,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -107,7 +107,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="#" method="GET" name="search">
+    <form class="md-search__form" action="#" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -171,7 +171,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -183,13 +183,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -489,6 +489,34 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html" class="md-nav__link">Introduction</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-your-provider" class="md-nav__link">Configuring your provider</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-netmaker" class="md-nav__link">Configuring Netmaker</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="oauth.html#configuring-user-permissions" class="md-nav__link">Configuring User Permissions</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -806,7 +834,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/searchindex.js


File diff suppressed because it is too large
+ 51 - 20
docs/_build/html/server-installation.html


+ 10 - 10
docs/_build/html/support.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Support &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Support &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -909,7 +909,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/troubleshoot.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Troubleshooting &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Troubleshooting &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -956,7 +956,7 @@ You can also see the current WireGuard configuration with <code class="docutils
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 10 - 10
docs/_build/html/usage.html

@@ -46,10 +46,10 @@
   
   
   
-    <title>Using Netmaker &#8212; Netmaker 0.8.4 documentation</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
-    <link rel="stylesheet" type="text/css" href="_static/material.css" />
-    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <title>Using Netmaker &#8212; Netmaker 0.8.5 documentation</title>
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    <link rel="stylesheet" href="_static/material.css" type="text/css" />
+    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.4 documentation"
+        <a href="index.html" title="Netmaker 0.8.5 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -103,7 +103,7 @@
 <div class="md-search" data-md-component="search" role="dialog">
   <label class="md-search__overlay" for="__search"></label>
   <div class="md-search__inner" role="search">
-    <form class="md-search__form" action="search.html" method="GET" name="search">
+    <form class="md-search__form" action="search.html" method="get" name="search">
       <input type="text" class="md-search__input" name="q" placeholder="Search"
              autocapitalize="off" autocomplete="off" spellcheck="false"
              data-md-component="query" data-md-state="active">
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.5 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.5 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.5 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -875,7 +875,7 @@
               
           </div>
             Created using
-            <a href="http://www.sphinx-doc.org/">Sphinx</a> 4.0.2.
+            <a href="http://www.sphinx-doc.org/">Sphinx</a> 3.5.4.
              and
             <a href="https://github.com/bashtage/sphinx-material/">Material for
               Sphinx</a>

+ 2 - 2
docs/architecture.rst

@@ -104,7 +104,7 @@ If running in daemon mode, on a periodic basis (systemd timer), the netclient pe
 The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.
 
 
-Datavase (sqlite, rqlite, postgres)
+Database (sqlite, rqlite, postgres)
 -------------------------------------
 
 As of v0.8, Netmaker uses sqlite by default as a database. It can also use PostgreSQL, or rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users. 
@@ -191,4 +191,4 @@ To manage DNS (optional), the node must have systemd-resolved. Systems that have
 Limitations
 =============
 
-Install limitations mostly include platform-specific dependencies. A failed netclient install should display information about which command is failing, or which libraries are missing. This can often be solved via machine upgrade, installing missing dependencies, or setting kernel headers on the machine for WireGuard (e.x.: `Installing Kernel Headers on Debian <https://stackoverflow.com/questions/62356581/wireguard-vpn-how-to-fix-operation-not-supported-if-it-worked-before>`_) 
+Install limitations mostly include platform-specific dependencies. A failed netclient install should display information about which command is failing, or which libraries are missing. This can often be solved via machine upgrade, installing missing dependencies, or setting kernel headers on the machine for WireGuard (e.x.: `Installing Kernel Headers on Debian <https://stackoverflow.com/questions/62356581/wireguard-vpn-how-to-fix-operation-not-supported-if-it-worked-before>`_) 

+ 1 - 1
docs/conf.py

@@ -22,7 +22,7 @@ copyright = '2021, Alex Feiszli'
 author = 'Alex Feiszli'
 
 # The full version, including alpha/beta/rc tags
-release = '0.8.4'
+release = '0.8.5'
 
 
 # -- General configuration ---------------------------------------------------

+ 1 - 1
docs/external-clients.rst

@@ -64,7 +64,7 @@ Configuring DNS for Ext Clients (OPTIONAL)
 If you wish to have a DNS field on your ext clients conf, simply edit the network field as shown below to 1.1.1.1 or 8.8.8.8 for example.
 If you do not want DNS on your ext client conf files, simply leave it blank.
 
-.. image:: images/exclient5.png
+.. image:: images/extclient5.png
    :width: 80%
    :alt: Gateway
    :align: center

BIN
docs/images/oauth1.png


BIN
docs/images/oauth2.png


BIN
docs/images/oauth3.png


+ 11 - 0
docs/index.rst

@@ -93,6 +93,17 @@ A detailed guide to installing the Netmaker server (API, DB, UI, DNS), and confi
    
    server-installation
 
+Oauth Configuration
+--------------------
+
+A simple guide to configuring OAuth for Netmaker.
+
+.. toctree::
+   :maxdepth: 2
+   
+   oauth
+
+
 Client Installation
 --------------------
 

+ 77 - 0
docs/oauth.rst

@@ -0,0 +1,77 @@
+===================
+Integrating OAuth
+====================
+
+Introduction
+==============
+
+As of v0.8.5, Netmaker offers integration with the following OAuth providers: 
+
+- GitHub
+- Google
+- Microsoft Azure AD
+
+By integrating with an OAuth provider, your Netmaker users can log in via the provider, rather than the default simple auth.
+
+Configuring your provider
+===========================
+
+In order to use OAuth, configure your OAuth provider (GitHub, Google, Azure AD).
+
+You must configure your provider to use the Netmaker Dashboard URI dashboard.<netmaker.base.domain> as the origin URL.
+
+For example: `https://dashboard.netmaker.mydomain.com`
+
+You must configure your provider to use the Netmaker API URI redirect route with the following format: https://api.<netmaker base domain>/api/oauth2/callback.
+
+For example: `https://api.netmaker.mydomain.com/api/oauth2/callback`
+
+General provider instructions can be found with the following links:
+
+Instructions for GitHub: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#github-auth-provider
+Instructions for Google: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#google-auth-provider
+Instructions for Microsoft Azure AD: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#microsoft-azure-ad-provider 
+
+Configuring Netmaker
+======================
+
+After you have configured your OAuth provider, take note of the CLIENT_ID and CLIENT_SECRET.
+
+Next, Configure Netmaker with the following environment variables. If any are left blank, OAuth will fail.
+
+.. code-block::
+
+    AUTH_PROVIDER="<azure-ad|github|google>"
+    CLIENT_ID="<client id of your oauth provider>"
+    CLIENT_SECRET="<client secret of your oauth provider>"
+    SERVER_API_CONN_STRING="https://<your-netmaker-api-domain>"
+    FRONTEND_URL="https://<your-netmaker-dashboard-domain>"
+
+
+After restarting your server, the Netmaker logs will indicate if the OAuth provider was successfully initialized.
+
+Once successful, users can click the key symbol on the login page to sign-in with your configured OAuth provider.
+
+.. image:: images/oauth1.png
+   :width: 80%
+   :alt: Login Oauth
+   :align: center
+
+Configuring User Permissions
+===============================
+
+All users logging in will have zero permissions on first sign-in. An admin must configure all user permissions.
+
+Admins must navigate to the "Users" screen to configure permissions.
+
+For each user, an admin must specify which networks that user has access to configure. Additionally, an Admin can elevate a user to Admin permissions.
+
+.. image:: images/oauth2.png
+   :width: 80%
+   :alt: Edit User
+   :align: center
+
+.. image:: images/oauth3.png
+   :width: 80%
+   :alt: Edit User 2
+   :align: center

+ 8 - 1
docs/server-installation.rst

@@ -159,7 +159,9 @@ DNS Mode Setup
 
 If you plan on running the server in DNS Mode, know that a `CoreDNS Server <https://coredns.io/manual/toc/>`_ will be installed. CoreDNS is a light-weight, fast, and easy-to-configure DNS server. It is recommended to bind CoreDNS to port 53 of the host system, and it will do so by default. The clients will expect the nameserver to be on port 53, and many systems have issues resolving a different port.
 
-However, on your host system (for Netmaker), this may conflict with an existing process. On linux systems running systemd-resolved, there is likely a service consuming port 53. The below steps will disable systemd-resolved, and replace it with a generic (e.g. Google) nameserver. Be warned that this may have consequences for any existing private DNS configuration. The following was tested on Ubuntu 20.04 and should be run prior to deploying the docker containers.
+However, on your host system (for Netmaker), this may conflict with an existing process. On linux systems running systemd-resolved, there is likely a service consuming port 53. The below steps will disable systemd-resolved, and replace it with a generic (e.g. Google) nameserver. Be warned that this may have consequences for any existing private DNS configuration. 
+
+With the latest docker-compose, it is not necessary to perform these steps. But if you are running the install and find that port 53 is blocked, you can perform the following steps, which were tested on Ubuntu 20.04 (these should be run prior to deploying the docker containers).
 
 .. code-block::
 
@@ -199,6 +201,11 @@ Assuming you have Docker and Docker Compose installed, you can just run the foll
   sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml
   docker-compose up -d`
 
+Traefik Proxy
+------------------------
+
+To install with Traefik, rather than Nginx or the default Caddy, check out this repo: https://github.com/bsherman/netmaker-traefik 
+
 
 No DNS - CoreDNS Disabled
 ----------------------------------------------

+ 3 - 423
functions/helpers.go

@@ -1,21 +1,15 @@
-//TODO: Consider restructuring  this file/folder    "github.com/gorilla/handlers"
-
-//It may make more sense to split into different files and not call it "helpers"
-
 package functions
 
 import (
-	"encoding/base64"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"log"
 	"math/rand"
-	"net"
 	"strings"
 	"time"
 
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 )
@@ -59,20 +53,6 @@ func ParseIntClient(value string) (models.IntClient, error) {
 //Takes in an arbitrary field and value for field and checks to see if any other
 //node has that value for the same field within the network
 
-// GetUser - gets a user
-func GetUser(username string) (models.User, error) {
-
-	var user models.User
-	record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
-	if err != nil {
-		return user, err
-	}
-	if err = json.Unmarshal([]byte(record), &user); err != nil {
-		return models.User{}, err
-	}
-	return user, err
-}
-
 // SliceContains - sees if a slice contains something
 func SliceContains(slice []string, item string) bool {
 	set := make(map[string]struct{}, len(slice))
@@ -84,60 +64,6 @@ func SliceContains(slice []string, item string) bool {
 	return ok
 }
 
-// CreateServerToken - creates a server token
-func CreateServerToken(netID string) (string, error) {
-	var network models.Network
-	var accesskey models.AccessKey
-
-	network, err := GetParentNetwork(netID)
-	if err != nil {
-		return "", err
-	}
-
-	var accessToken models.AccessToken
-	servervals := models.ServerConfig{}
-	if servercfg.GetPlatform() == "Kubernetes" {
-		log.Println("server on kubernetes")
-		servervals = models.ServerConfig{
-			APIConnString:  servercfg.GetPodIP() + ":" + servercfg.GetAPIPort(),
-			GRPCConnString: servercfg.GetPodIP() + ":" + servercfg.GetGRPCPort(),
-			GRPCSSL:        "off",
-		}
-	} else {
-		log.Println("server on linux")
-		servervals = models.ServerConfig{
-			APIConnString:   "127.0.0.1:" + servercfg.GetAPIPort(),
-			GRPCConnString:  "127.0.0.1:" + servercfg.GetGRPCPort(),
-			GRPCSSL:         "off",
-			CheckinInterval: servercfg.GetCheckinInterval(),
-		}
-	}
-	log.Println("APIConnString:", servervals.APIConnString)
-	log.Println("GRPCConnString:", servervals.GRPCConnString)
-	log.Println("GRPCSSL:", servervals.GRPCSSL)
-	accessToken.ServerConfig = servervals
-	accessToken.ClientConfig.Network = netID
-	accessToken.ClientConfig.Key = GenKey()
-
-	accesskey.Name = GenKeyName()
-	accesskey.Value = accessToken.ClientConfig.Key
-	accesskey.Uses = 1
-	tokenjson, err := json.Marshal(accessToken)
-	if err != nil {
-		return accesskey.AccessString, err
-	}
-	accesskey.AccessString = base64.StdEncoding.EncodeToString([]byte(tokenjson))
-	log.Println("accessstring:", accesskey.AccessString)
-	network.AccessKeys = append(network.AccessKeys, accesskey)
-	if data, err := json.Marshal(network); err != nil {
-		return "", err
-	} else {
-		database.Insert(netID, string(data), database.NETWORKS_TABLE_NAME)
-	}
-
-	return accesskey.AccessString, nil
-}
-
 // GetPeersList - gets peers for given network
 func GetPeersList(networkName string) ([]models.PeersResponse, error) {
 
@@ -214,51 +140,6 @@ func NetworkExists(name string) (bool, error) {
 	return len(network) > 0, nil
 }
 
-// GetRecordKey - get record key
-func GetRecordKey(id string, network string) (string, error) {
-	if id == "" || network == "" {
-		return "", errors.New("unable to get record key")
-	}
-	return id + "###" + network, nil
-}
-
-// UpdateNetworkNodeAddresses - updates network node addresses
-func UpdateNetworkNodeAddresses(networkName string) error {
-
-	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != 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 {
-			ipaddr, iperr := UniqueAddress(networkName)
-			if iperr != nil {
-				fmt.Println("error in node  address assignment!")
-				return iperr
-			}
-
-			node.Address = ipaddr
-			node.PullChanges = "yes"
-			data, err := json.Marshal(&node)
-			if err != nil {
-				return err
-			}
-			node.SetID()
-			database.Insert(node.ID, string(data), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
 // NetworkNodesUpdateAction - updates action of network nodes
 func NetworkNodesUpdateAction(networkName string, action string) error {
 
@@ -325,51 +206,12 @@ func NetworkNodesUpdatePullChanges(networkName string) error {
 	return nil
 }
 
-// UpdateNetworkLocalAddresses - updates network localaddresses
-func UpdateNetworkLocalAddresses(networkName string) error {
-
-	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
-
-	if err != nil {
-		return err
-	}
-
-	for _, value := range collection {
-
-		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 {
-			ipaddr, iperr := UniqueAddress(networkName)
-			if iperr != nil {
-				fmt.Println("error in node  address assignment!")
-				return iperr
-			}
-
-			node.Address = ipaddr
-			newNodeData, err := json.Marshal(&node)
-			if err != nil {
-				fmt.Println("error in node  address assignment!")
-				return err
-			}
-			node.SetID()
-			database.Insert(node.ID, string(newNodeData), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
 // IsNetworkDisplayNameUnique - checks if network display name unique
 func IsNetworkDisplayNameUnique(name string) (bool, error) {
 
 	isunique := true
 
-	dbs, err := models.GetNetworks()
+	dbs, err := logic.GetNetworks()
 	if err != nil {
 		return database.IsEmptyRecord(err), err
 	}
@@ -417,38 +259,10 @@ func GetNetworkNonServerNodeCount(networkName string) (int, error) {
 	return count, nil
 }
 
-//Checks to see if access key is valid
-//Does so by checking against all keys and seeing if any have the same value
-//may want to hash values before comparing...consider this
-//TODO: No error handling!!!!
-
-// IsKeyValid - check if key is valid
-func IsKeyValid(networkname string, keyvalue string) bool {
-
-	network, _ := GetParentNetwork(networkname)
-	var key models.AccessKey
-	foundkey := false
-	isvalid := false
-
-	for i := len(network.AccessKeys) - 1; i >= 0; i-- {
-		currentkey := network.AccessKeys[i]
-		if currentkey.Value == keyvalue {
-			key = currentkey
-			foundkey = true
-		}
-	}
-	if foundkey {
-		if key.Uses > 0 {
-			isvalid = true
-		}
-	}
-	return isvalid
-}
-
 // IsKeyValidGlobal - checks if a key is valid globally
 func IsKeyValidGlobal(keyvalue string) bool {
 
-	networks, _ := models.GetNetworks()
+	networks, _ := logic.GetNetworks()
 	var key models.AccessKey
 	foundkey := false
 	isvalid := false
@@ -478,43 +292,10 @@ func IsKeyValidGlobal(keyvalue string) bool {
 //Should probably just be GetNetwork. kind of a dumb name.
 //Used in contexts where it's not the Parent network.
 
-// GetParentNetwork - get parent network
-func GetParentNetwork(networkname string) (models.Network, error) {
-
-	var network models.Network
-	networkData, err := database.FetchRecord(database.NETWORKS_TABLE_NAME, networkname)
-	if err != nil {
-		return network, err
-	}
-	if err = json.Unmarshal([]byte(networkData), &network); err != nil {
-		return models.Network{}, err
-	}
-	return network, nil
-}
-
-// IsIpNet - checks if valid ip
-func IsIpNet(host string) bool {
-	return net.ParseIP(host) != nil
-}
-
 //Similar to above but checks if Cidr range is valid
 //At least this guy's got some print statements
 //still not good error handling
 
-// IsIpCIDR - IsIpCIDR
-func IsIpCIDR(host string) bool {
-
-	ip, ipnet, err := net.ParseCIDR(host)
-
-	if err != nil {
-		fmt.Println(err)
-		fmt.Println("Address Range is not valid!")
-		return false
-	}
-
-	return ip != nil && ipnet != nil
-}
-
 //This  checks to  make sure a network name is valid.
 //Switch to REGEX?
 
@@ -557,59 +338,6 @@ func NameInNodeCharSet(name string) bool {
 	return true
 }
 
-//This returns a node based on its mac address.
-//The mac address acts as the Unique ID for nodes.
-//Is this a dumb thing to do? I thought it was cool but maybe it's dumb.
-//It doesn't really provide a tangible benefit over a random ID
-
-// GetNodeByMacAddress - gets a node by mac address
-func GetNodeByMacAddress(network string, macaddress string) (models.Node, error) {
-
-	var node models.Node
-
-	key, err := GetRecordKey(macaddress, network)
-	if err != nil {
-		return node, err
-	}
-
-	record, err := database.FetchRecord(database.NODES_TABLE_NAME, key)
-	if err != nil {
-		return models.Node{}, err
-	}
-
-	if err = json.Unmarshal([]byte(record), &node); err != nil {
-		return models.Node{}, err
-	}
-
-	node.SetDefaults()
-
-	return node, nil
-}
-
-// GetDeletedNodeByMacAddress - get a deleted node
-func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node, error) {
-
-	var node models.Node
-
-	key, err := GetRecordKey(macaddress, network)
-	if err != nil {
-		return node, err
-	}
-
-	record, err := database.FetchRecord(database.DELETED_NODES_TABLE_NAME, key)
-	if err != nil {
-		return models.Node{}, err
-	}
-
-	if err = json.Unmarshal([]byte(record), &node); err != nil {
-		return models.Node{}, err
-	}
-
-	node.SetDefaults()
-
-	return node, nil
-}
-
 // RemoveDeletedNode - remove deleted node
 func RemoveDeletedNode(nodeid string) bool {
 	return database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, nodeid) == nil
@@ -668,82 +396,6 @@ func GetAllExtClients() ([]models.ExtClient, error) {
 	return extclients, nil
 }
 
-//This returns a unique address for a node to use
-//it iterates through the list of IP's in the subnet
-//and checks against all nodes to see if it's taken, until it finds one.
-//TODO: We do not handle a case where we run out of addresses.
-//We will need to handle that eventually
-
-// UniqueAddress - see if address is unique
-func UniqueAddress(networkName string) (string, error) {
-
-	var network models.Network
-	network, err := GetParentNetwork(networkName)
-	if err != nil {
-		fmt.Println("UniqueAddress encountered  an error")
-		return "666", err
-	}
-
-	offset := true
-	ip, ipnet, err := net.ParseCIDR(network.AddressRange)
-	if err != nil {
-		fmt.Println("UniqueAddress encountered  an error")
-		return "666", err
-	}
-	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
-		if offset {
-			offset = false
-			continue
-		}
-		if networkName == "comms" {
-			if IsIPUnique(networkName, ip.String(), database.INT_CLIENTS_TABLE_NAME, false) {
-				return ip.String(), err
-			}
-		} else {
-			if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, false) && IsIPUnique(networkName, ip.String(), database.EXT_CLIENT_TABLE_NAME, false) {
-				return ip.String(), err
-			}
-		}
-	}
-
-	//TODO
-	err1 := errors.New("ERROR: No unique addresses available. Check network subnet.")
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
-}
-
-// UniqueAddress6 - see if ipv6 address is unique
-func UniqueAddress6(networkName string) (string, error) {
-
-	var network models.Network
-	network, err := GetParentNetwork(networkName)
-	if err != nil {
-		fmt.Println("Network Not Found")
-		return "", err
-	}
-	if network.IsDualStack == "no" {
-		return "", nil
-	}
-
-	offset := true
-	ip, ipnet, err := net.ParseCIDR(network.AddressRange6)
-	if err != nil {
-		fmt.Println("UniqueAddress6 encountered  an error")
-		return "666", err
-	}
-	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
-		if offset {
-			offset = false
-			continue
-		}
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, true) {
-			return ip.String(), err
-		}
-	}
-	//TODO
-	err1 := errors.New("ERROR: No unique addresses available. Check network subnet.")
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
-}
-
 // GenKey - generates access key
 func GenKey() string {
 
@@ -781,68 +433,6 @@ func GenKeyName() string {
 	return "key" + string(b)
 }
 
-// IsIPUnique - checks if an IP is unique
-func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
-
-	isunique := true
-	collection, err := database.FetchRecords(tableName)
-
-	if err != nil {
-		return isunique
-	}
-
-	for _, value := range collection { // filter
-		var node models.Node
-		if err = json.Unmarshal([]byte(value), &node); err != nil {
-			continue
-		}
-		if isIpv6 {
-			if node.Address6 == ip && node.Network == network {
-				return false
-			}
-		} else {
-			if node.Address == ip && node.Network == network {
-				return false
-			}
-		}
-	}
-
-	return isunique
-}
-
-//called once key has been used by createNode
-//reduces value by one and deletes if necessary
-// DecrimentKey - decriments key uses
-func DecrimentKey(networkName string, keyvalue string) {
-
-	var network models.Network
-
-	network, err := GetParentNetwork(networkName)
-	if err != nil {
-		return
-	}
-
-	for i := len(network.AccessKeys) - 1; i >= 0; i-- {
-
-		currentkey := network.AccessKeys[i]
-		if currentkey.Value == keyvalue {
-			network.AccessKeys[i].Uses--
-			if network.AccessKeys[i].Uses < 1 {
-				network.AccessKeys = append(network.AccessKeys[:i],
-					network.AccessKeys[i+1:]...)
-				break
-			}
-		}
-	}
-
-	if newNetworkData, err := json.Marshal(&network); err != nil {
-		PrintUserLog(models.NODE_SERVER_NAME, "failed to decrement key", 2)
-		return
-	} else {
-		database.Insert(network.NetID, string(newNetworkData), database.NETWORKS_TABLE_NAME)
-	}
-}
-
 // DeleteKey - deletes a key
 func DeleteKey(network models.Network, i int) {
 
@@ -855,13 +445,3 @@ func DeleteKey(network models.Network, i int) {
 		database.Insert(network.NetID, string(networkData), database.NETWORKS_TABLE_NAME)
 	}
 }
-
-// Inc - increments an IP
-func Inc(ip net.IP) {
-	for j := len(ip) - 1; j >= 0; j-- {
-		ip[j]++
-		if ip[j] > 0 {
-			break
-		}
-	}
-}

+ 5 - 33
functions/local.go

@@ -1,10 +1,12 @@
 package functions
 
 import (
-	"io/ioutil"
 	"os"
+
+	"github.com/gravitl/netmaker/logic"
 )
 
+// FileExists - checks if file exists
 func FileExists(f string) bool {
 	info, err := os.Stat(f)
 	if os.IsNotExist(err) {
@@ -13,6 +15,7 @@ func FileExists(f string) bool {
 	return !info.IsDir()
 }
 
+// SetDNSDir - sets the dns directory of the system
 func SetDNSDir() error {
 	dir, err := os.Getwd()
 	if err != nil {
@@ -27,7 +30,7 @@ func SetDNSDir() error {
 	}
 	_, err = os.Stat(dir + "/config/dnsconfig/Corefile")
 	if os.IsNotExist(err) {
-		err = SetCorefile(".")
+		err = logic.SetCorefile(".")
 		if err != nil {
 			PrintUserLog("", err.Error(), 0)
 		}
@@ -41,34 +44,3 @@ func SetDNSDir() error {
 	}
 	return nil
 }
-
-func SetCorefile(domains string) error {
-	dir, err := os.Getwd()
-	if err != nil {
-		return err
-	}
-	_, err = os.Stat(dir + "/config/dnsconfig")
-	if os.IsNotExist(err) {
-		os.Mkdir(dir+"/config/dnsconfig", 744)
-	} else if err != nil {
-		PrintUserLog("", "couldnt find or create /config/dnsconfig", 0)
-		return err
-	}
-
-	corefile := domains + ` {
-    reload 15s
-    hosts /root/dnsconfig/netmaker.hosts {
-	fallthrough	
-    }
-    forward . 8.8.8.8 8.8.4.4
-    log
-}
-`
-	corebytes := []byte(corefile)
-
-	err = ioutil.WriteFile(dir+"/config/dnsconfig/Corefile", corebytes, 0644)
-	if err != nil {
-		return err
-	}
-	return err
-}

+ 7 - 6
go.mod

@@ -3,26 +3,27 @@ module github.com/gravitl/netmaker
 go 1.15
 
 require (
-	github.com/go-playground/validator/v10 v10.5.0
+	github.com/go-playground/validator/v10 v10.9.0
 	github.com/golang-jwt/jwt/v4 v4.0.0
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/lib/pq v1.10.3
-	github.com/mattn/go-sqlite3 v1.14.8
+	github.com/mattn/go-sqlite3 v1.14.9
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-	github.com/stretchr/testify v1.6.1
+	github.com/stretchr/testify v1.7.0
 	github.com/txn2/txeh v1.3.0
 	github.com/urfave/cli/v2 v2.3.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect
+	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
 	golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e // indirect
 	golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210913210325-91d1988e44de
 	google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 // indirect
-	google.golang.org/grpc v1.35.0
-	google.golang.org/protobuf v1.26.0
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
+	google.golang.org/grpc v1.41.0
+	google.golang.org/protobuf v1.27.1
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )

+ 59 - 34
go.sum

@@ -1,13 +1,15 @@
-cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -15,33 +17,35 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
-github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
+github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
 github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -49,6 +53,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -61,14 +66,13 @@ github.com/google/go-cmp v0.5.2/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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -80,13 +84,21 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c
 github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
 github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
 github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
-github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
 github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
-github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
+github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
 github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
 github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
@@ -106,9 +118,14 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCL
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
@@ -121,25 +138,23 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/txn2/txeh v1.3.0 h1:vnbv63htVMZCaQgLqVBxKvj2+HHHFUzNW7I183zjg3E=
 github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw8=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -153,12 +168,14 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -168,10 +185,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -183,6 +202,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -197,13 +217,12 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99 h1:ZEXtoJu1S0ie/EmdYnjY3CqaCCZxnldL+K1ftMITD2Q=
@@ -212,7 +231,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 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=
@@ -227,6 +245,7 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 h1:HPkKL4eEh/nemF/FRzYMrFsAh1ZPm5t8NqKBI/Ejlg0=
 google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -234,8 +253,10 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -247,14 +268,18 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 62 - 0
logic/accesskeys.go

@@ -0,0 +1,62 @@
+package logic
+
+import (
+	"encoding/json"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+)
+
+// DecrimentKey - decriments key uses
+func DecrimentKey(networkName string, keyvalue string) {
+
+	var network models.Network
+
+	network, err := GetParentNetwork(networkName)
+	if err != nil {
+		return
+	}
+
+	for i := len(network.AccessKeys) - 1; i >= 0; i-- {
+
+		currentkey := network.AccessKeys[i]
+		if currentkey.Value == keyvalue {
+			network.AccessKeys[i].Uses--
+			if network.AccessKeys[i].Uses < 1 {
+				network.AccessKeys = append(network.AccessKeys[:i],
+					network.AccessKeys[i+1:]...)
+				break
+			}
+		}
+	}
+
+	if newNetworkData, err := json.Marshal(&network); err != nil {
+		Log("failed to decrement key", 2)
+		return
+	} else {
+		database.Insert(network.NetID, string(newNetworkData), database.NETWORKS_TABLE_NAME)
+	}
+}
+
+// IsKeyValid - check if key is valid
+func IsKeyValid(networkname string, keyvalue string) bool {
+
+	network, _ := GetParentNetwork(networkname)
+	var key models.AccessKey
+	foundkey := false
+	isvalid := false
+
+	for i := len(network.AccessKeys) - 1; i >= 0; i-- {
+		currentkey := network.AccessKeys[i]
+		if currentkey.Value == keyvalue {
+			key = currentkey
+			foundkey = true
+		}
+	}
+	if foundkey {
+		if key.Uses > 0 {
+			isvalid = true
+		}
+	}
+	return isvalid
+}

Some files were not shown because too many files changed in this diff