Browse Source

Merge pull request #2630 from gravitl/release-v0.21.1

v0.21.1
Abhishek K 1 year ago
parent
commit
fb4647978a
76 changed files with 1555 additions and 922 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 1 1
      .github/workflows/branchtest.yml
  3. 4 4
      .github/workflows/docker-builder.yml
  4. 10 10
      .github/workflows/publish-docker.yml
  5. 4 4
      .github/workflows/test.yml
  6. 1 1
      Dockerfile
  7. 1 1
      Dockerfile-quick
  8. 9 2
      README.md
  9. 2 0
      auth/auth.go
  10. 4 1
      cli/cmd/host/delete.go
  11. 5 1
      cli/cmd/host/update.go
  12. 4 1
      cli/cmd/node/delete.go
  13. 0 1
      cli/cmd/node/flags.go
  14. 0 22
      cli/cmd/node/uncordon.go
  15. 0 2
      cli/cmd/node/update.go
  16. 2 2
      cli/functions/host.go
  17. 2 7
      cli/functions/node.go
  18. 1 1
      compose/docker-compose.netclient.yml
  19. 0 1
      compose/docker-compose.yml
  20. 59 56
      config/config.go
  21. 4 2
      controllers/dns.go
  22. 2 2
      controllers/dns_test.go
  23. 138 65
      controllers/docs.go
  24. 65 19
      controllers/enrollmentkeys.go
  25. 7 10
      controllers/ext_client.go
  26. 3 1
      controllers/files.go
  27. 37 18
      controllers/hosts.go
  28. 1 1
      controllers/legacy.go
  29. 15 4
      controllers/migrate.go
  30. 2 2
      controllers/network.go
  31. 30 21
      controllers/node.go
  32. 39 20
      controllers/server.go
  33. 33 7
      controllers/user.go
  34. 0 5
      docker/Caddyfile
  35. 0 8
      docker/Caddyfile-pro
  36. 7 7
      go.mod
  37. 14 14
      go.sum
  38. 1 1
      k8s/client/netclient-daemonset.yaml
  39. 1 1
      k8s/client/netclient.yaml
  40. 1 1
      k8s/server/netmaker-ui.yaml
  41. 6 0
      logic/auth.go
  42. 1 1
      logic/dns.go
  43. 28 1
      logic/extpeers.go
  44. 22 2
      logic/gateway.go
  45. 12 10
      logic/hosts.go
  46. 1 1
      logic/jwts.go
  47. 4 5
      logic/nodes.go
  48. 8 9
      logic/peers.go
  49. 4 0
      logic/relay.go
  50. 1 1
      logic/timer.go
  51. 0 1
      logic/wireguard.go
  52. 6 1
      logic/zombie.go
  53. 1 1
      main.go
  54. 31 3
      migrate/migrate.go
  55. 27 24
      models/api_host.go
  56. 0 3
      models/api_node.go
  57. 47 40
      models/host.go
  58. 21 25
      models/node.go
  59. 27 32
      models/structs.go
  60. 1 10
      mq/handlers.go
  61. 1 1
      mq/publishers.go
  62. 1 1
      pro/controllers/users.go
  63. 4 0
      pro/initialize.go
  64. 14 7
      pro/license.go
  65. 28 5
      pro/logic/relays.go
  66. 76 0
      pro/remote_access_client.go
  67. 11 7
      pro/types.go
  68. 9 2
      pro/util.go
  69. 8 4
      release.md
  70. 4 0
      scripts/netmaker.default.env
  71. 0 113
      scripts/nm-certs.sh
  72. 1 6
      scripts/nm-quick.sh
  73. 1 1
      scripts/nm-upgrade-0-17-1-to-0-19-0.sh
  74. 1 1
      scripts/nm-upgrade.sh
  75. 21 73
      servercfg/serverconf.go
  76. 617 204
      swagger.yml

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

@@ -31,6 +31,7 @@ body:
       label: Version
       description: What version are you running?
       options:
+        - v0.21.1
         - v0.21.0
         - v0.20.6
         - v0.20.5

+ 1 - 1
.github/workflows/branchtest.yml

@@ -25,7 +25,7 @@ jobs:
       netclientbranch: ${{ steps.getbranch.outputs.netclientbranch }}
     steps:
       - name: checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           repository: gravitl/netclient
           ref: develop

+ 4 - 4
.github/workflows/docker-builder.yml

@@ -11,16 +11,16 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: SetUp Buildx
-      uses: docker/setup-buildx-action@v2
+      uses: docker/setup-buildx-action@v3
     - name: Login to Dockerhub
-      uses: docker/login-action@v2
+      uses: docker/login-action@v3
       with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
     - name: Build and push to docker hub
-      uses: docker/build-push-action@v3
+      uses: docker/build-push-action@v5
       with:
         context: .
         push: true

+ 10 - 10
.github/workflows/publish-docker.yml

@@ -29,22 +29,22 @@ jobs:
             echo "TAG=${TAG}" >> $GITHUB_ENV
       -
         name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       -
         name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
+        uses: docker/setup-qemu-action@v3
       -
         name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v2
+        uses: docker/setup-buildx-action@v3
       -
         name: Login to DockerHub
-        uses: docker/login-action@v2
+        uses: docker/login-action@v3
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
       -
         name: Build and push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v5
         with:
           context: .
           platforms: linux/amd64, linux/arm64, linux/arm/v7
@@ -69,22 +69,22 @@ jobs:
             echo "TAG=${TAG}" >> $GITHUB_ENV
       -
         name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       -
         name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
+        uses: docker/setup-qemu-action@v3
       -
         name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v2
+        uses: docker/setup-buildx-action@v3
       - 
         name: Login to DockerHub
-        uses: docker/login-action@v2
+        uses: docker/login-action@v3
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
       -
         name: Build and push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v5
         with:
           context: .
           platforms: linux/amd64, linux/arm64

+ 4 - 4
.github/workflows/test.yml

@@ -11,7 +11,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Setup Go
         uses: actions/setup-go@v4
         with:
@@ -25,7 +25,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Setup go
         uses: actions/setup-go@v4
         with:
@@ -42,7 +42,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Setup Go
         uses: actions/setup-go@v4
         with:
@@ -62,7 +62,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Setup Go
         uses: actions/setup-go@v4
         with:

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ COPY . .
 
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
-FROM alpine:3.18.3
+FROM alpine:3.18.4
 
 # add a c lib
 # set the working directory

+ 1 - 1
Dockerfile-quick

@@ -1,5 +1,5 @@
 #first stage - builder
-FROM alpine:3.18.3
+FROM alpine:3.18.4
 ARG version 
 WORKDIR /app
 COPY ./netmaker /root/netmaker

+ 9 - 2
README.md

@@ -16,7 +16,7 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.21.0-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.21.1-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?label=downloads" />
@@ -113,4 +113,11 @@ After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting
 
 ## License
 
-Netmaker's source code and all artifacts in this repository are freely available. All versions are published under the Server Side Public License (SSPL), version 1, which can be found here: [LICENSE.txt](./LICENSE.txt).
+Netmaker's source code and all artifacts in this repository are freely available.
+All content that resides under the "pro/" directory of this repository, if that
+directory exists, is licensed under the license defined in "pro/LICENSE".
+All third party components incorporated into the Netmaker Software are licensed
+under the original license provided by the owner of the applicable component.
+Content outside of the above mentioned directories or restrictions above is
+available under the "Apache Version 2.0" license as defined below.
+All details for the licenses used can be found here: [LICENSE.md](./LICENSE.md).

+ 2 - 0
auth/auth.go

@@ -136,6 +136,8 @@ func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
 //
 //			Security:
 //	  		oauth
+//			Responses:
+//			200:  okResponse
 func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
 	if auth_provider == nil {
 		handleOauthNotConfigured(w)

+ 4 - 1
cli/cmd/host/delete.go

@@ -5,16 +5,19 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var force bool
+
 var hostDeleteCmd = &cobra.Command{
 	Use:   "delete HostID",
 	Args:  cobra.ExactArgs(1),
 	Short: "Delete a host",
 	Long:  `Delete a host`,
 	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.DeleteHost(args[0]))
+		functions.PrettyPrint(functions.DeleteHost(args[0], force))
 	},
 }
 
 func init() {
 	rootCmd.AddCommand(hostDeleteCmd)
+	hostDeleteCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "delete even if part of network(s)")
 }

+ 5 - 1
cli/cmd/host/update.go

@@ -5,9 +5,10 @@ import (
 	"log"
 	"os"
 
+	"github.com/spf13/cobra"
+
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
-	"github.com/spf13/cobra"
 )
 
 var (
@@ -18,6 +19,7 @@ var (
 	mtu             int
 	isStatic        bool
 	isDefault       bool
+	keepAlive       int
 )
 
 var hostUpdateCmd = &cobra.Command{
@@ -43,6 +45,7 @@ var hostUpdateCmd = &cobra.Command{
 			apiHost.MTU = mtu
 			apiHost.IsStatic = isStatic
 			apiHost.IsDefault = isDefault
+			apiHost.PersistentKeepalive = keepAlive
 		}
 		functions.PrettyPrint(functions.UpdateHost(args[0], apiHost))
 	},
@@ -54,6 +57,7 @@ func init() {
 	hostUpdateCmd.Flags().StringVar(&name, "name", "", "Host name")
 	hostUpdateCmd.Flags().IntVar(&listenPort, "listen_port", 0, "Listen port of the host")
 	hostUpdateCmd.Flags().IntVar(&mtu, "mtu", 0, "Host MTU size")
+	hostUpdateCmd.Flags().IntVar(&keepAlive, "keep_alive", 0, "Interval (seconds) in which packets are sent to keep connections open with peers")
 	hostUpdateCmd.Flags().BoolVar(&isStatic, "static", false, "Make Host Static ?")
 	hostUpdateCmd.Flags().BoolVar(&isDefault, "default", false, "Make Host Default ?")
 	rootCmd.AddCommand(hostUpdateCmd)

+ 4 - 1
cli/cmd/node/delete.go

@@ -5,16 +5,19 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var force bool
+
 var nodeDeleteCmd = &cobra.Command{
 	Use:   "delete [NETWORK NAME] [NODE ID]",
 	Args:  cobra.ExactArgs(2),
 	Short: "Delete a Node",
 	Long:  `Delete a Node`,
 	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.DeleteNode(args[0], args[1]))
+		functions.PrettyPrint(functions.DeleteNode(args[0], args[1], force))
 	},
 }
 
 func init() {
 	rootCmd.AddCommand(nodeDeleteCmd)
+	nodeDeleteCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "force delete a node")
 }

+ 0 - 1
cli/cmd/node/flags.go

@@ -11,7 +11,6 @@ var (
 	name                   string
 	postUp                 string
 	postDown               string
-	keepAlive              int
 	relayedNodes           string
 	egressGatewayRanges    string
 	expirationDateTime     int

+ 0 - 22
cli/cmd/node/uncordon.go

@@ -1,22 +0,0 @@
-package node
-
-import (
-	"fmt"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var nodeUncordonCmd = &cobra.Command{
-	Use:   "uncordon [NETWORK NAME] [NODE ID]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Get a node by ID",
-	Long:  `Get a node by ID`,
-	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Println(*functions.UncordonNode(args[0], args[1]))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(nodeUncordonCmd)
-}

+ 0 - 2
cli/cmd/node/update.go

@@ -34,7 +34,6 @@ var nodeUpdateCmd = &cobra.Command{
 			node.Address = address
 			node.Address6 = address6
 			node.LocalAddress = localAddress
-			node.PersistentKeepalive = int32(keepAlive)
 			if relayedNodes != "" {
 				node.RelayedNodes = strings.Split(relayedNodes, ",")
 			}
@@ -61,7 +60,6 @@ func init() {
 	nodeUpdateCmd.Flags().StringVar(&name, "name", "", "Node name")
 	nodeUpdateCmd.Flags().StringVar(&postUp, "post_up", "", "Commands to run after node is up `;` separated")
 	nodeUpdateCmd.Flags().StringVar(&postDown, "post_down", "", "Commands to run after node is down `;` separated")
-	nodeUpdateCmd.Flags().IntVar(&keepAlive, "keep_alive", 0, "Interval in which packets are sent to keep connections open with peers")
 	nodeUpdateCmd.Flags().StringVar(&relayedNodes, "relayed_nodes", "", "relayed nodes if node acts as a relay")
 	nodeUpdateCmd.Flags().StringVar(&egressGatewayRanges, "egress_addrs", "", "Addresses for egressing traffic if node acts as an egress")
 	nodeUpdateCmd.Flags().IntVar(&expirationDateTime, "expiry", 0, "UNIX timestamp after which node will lose access to the network")

+ 2 - 2
cli/functions/host.go

@@ -17,8 +17,8 @@ func GetHosts() *[]models.ApiHost {
 }
 
 // DeleteHost - delete a host
-func DeleteHost(hostID string) *models.ApiHost {
-	return request[models.ApiHost](http.MethodDelete, "/api/hosts/"+hostID, nil)
+func DeleteHost(hostID string, force bool) *models.ApiHost {
+	return request[models.ApiHost](http.MethodDelete, fmt.Sprintf("/api/hosts/%s?force=%t", hostID, force), nil)
 }
 
 // UpdateHost - update a host

+ 2 - 7
cli/functions/node.go

@@ -27,8 +27,8 @@ func UpdateNode(networkName, nodeID string, node *models.ApiNode) *models.ApiNod
 }
 
 // DeleteNode - delete a node
-func DeleteNode(networkName, nodeID string) *models.SuccessResponse {
-	return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s", networkName, nodeID), nil)
+func DeleteNode(networkName, nodeID string, force bool) *models.SuccessResponse {
+	return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s?force=%t", networkName, nodeID, force), nil)
 }
 
 // CreateEgress - turn a node into an egress
@@ -52,8 +52,3 @@ func CreateIngress(networkName, nodeID string, failover bool) *models.ApiNode {
 func DeleteIngress(networkName, nodeID string) *models.ApiNode {
 	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleteingress", networkName, nodeID), nil)
 }
-
-// UncordonNode - uncordon a node
-func UncordonNode(networkName, nodeID string) *string {
-	return request[string](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/approve", networkName, nodeID), nil)
-}

+ 1 - 1
compose/docker-compose.netclient.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netclient:
     container_name: netclient
-    image: 'gravitl/netclient:v0.21.0'
+    image: 'gravitl/netclient:v0.21.1'
     hostname: netmaker-1
     network_mode: host
     restart: on-failure

+ 0 - 1
compose/docker-compose.yml

@@ -53,7 +53,6 @@ services:
       - "host.docker.internal:host-gateway"
     volumes:
       - ./Caddyfile:/etc/caddy/Caddyfile
-      - ./certs:/root/certs
       - caddy_data:/data
       - caddy_conf:/config
     ports:

+ 59 - 56
config/config.go

@@ -7,6 +7,7 @@ package config
 import (
 	"fmt"
 	"os"
+	"time"
 
 	"gopkg.in/yaml.v3"
 )
@@ -32,62 +33,64 @@ type EnvironmentConfig struct {
 
 // ServerConfig - server conf struct
 type ServerConfig struct {
-	CoreDNSAddr                string `yaml:"corednsaddr"`
-	APIConnString              string `yaml:"apiconn"`
-	APIHost                    string `yaml:"apihost"`
-	APIPort                    string `yaml:"apiport"`
-	Broker                     string `yam:"broker"`
-	ServerBrokerEndpoint       string `yaml:"serverbrokerendpoint"`
-	BrokerType                 string `yaml:"brokertype"`
-	EmqxRestEndpoint           string `yaml:"emqxrestendpoint"`
-	NetclientAutoUpdate        string `yaml:"netclientautoupdate"`
-	NetclientEndpointDetection string `yaml:"netclientendpointdetection"`
-	MasterKey                  string `yaml:"masterkey"`
-	DNSKey                     string `yaml:"dnskey"`
-	AllowedOrigin              string `yaml:"allowedorigin"`
-	NodeID                     string `yaml:"nodeid"`
-	RestBackend                string `yaml:"restbackend"`
-	MessageQueueBackend        string `yaml:"messagequeuebackend"`
-	DNSMode                    string `yaml:"dnsmode"`
-	DisableRemoteIPCheck       string `yaml:"disableremoteipcheck"`
-	Version                    string `yaml:"version"`
-	SQLConn                    string `yaml:"sqlconn"`
-	Platform                   string `yaml:"platform"`
-	Database                   string `yaml:"database"`
-	Verbosity                  int32  `yaml:"verbosity"`
-	AuthProvider               string `yaml:"authprovider"`
-	OIDCIssuer                 string `yaml:"oidcissuer"`
-	ClientID                   string `yaml:"clientid"`
-	ClientSecret               string `yaml:"clientsecret"`
-	FrontendURL                string `yaml:"frontendurl"`
-	DisplayKeys                string `yaml:"displaykeys"`
-	AzureTenant                string `yaml:"azuretenant"`
-	Telemetry                  string `yaml:"telemetry"`
-	HostNetwork                string `yaml:"hostnetwork"`
-	Server                     string `yaml:"server"`
-	PublicIPService            string `yaml:"publicipservice"`
-	MQPassword                 string `yaml:"mqpassword"`
-	MQUserName                 string `yaml:"mqusername"`
-	MetricsExporter            string `yaml:"metrics_exporter"`
-	BasicAuth                  string `yaml:"basic_auth"`
-	LicenseValue               string `yaml:"license_value"`
-	NetmakerTenantID           string `yaml:"netmaker_tenant_id"`
-	IsPro                      string `yaml:"is_ee" json:"IsEE"`
-	StunPort                   int    `yaml:"stun_port"`
-	StunList                   string `yaml:"stun_list"`
-	TurnServer                 string `yaml:"turn_server"`
-	TurnApiServer              string `yaml:"turn_api_server"`
-	TurnPort                   int    `yaml:"turn_port"`
-	TurnUserName               string `yaml:"turn_username"`
-	TurnPassword               string `yaml:"turn_password"`
-	UseTurn                    bool   `yaml:"use_turn"`
-	UsersLimit                 int    `yaml:"user_limit"`
-	NetworksLimit              int    `yaml:"network_limit"`
-	MachinesLimit              int    `yaml:"machines_limit"`
-	IngressesLimit             int    `yaml:"ingresses_limit"`
-	EgressesLimit              int    `yaml:"egresses_limit"`
-	DeployedByOperator         bool   `yaml:"deployed_by_operator"`
-	Environment                string `yaml:"environment"`
+	CoreDNSAddr                string        `yaml:"corednsaddr"`
+	APIConnString              string        `yaml:"apiconn"`
+	APIHost                    string        `yaml:"apihost"`
+	APIPort                    string        `yaml:"apiport"`
+	Broker                     string        `yam:"broker"`
+	ServerBrokerEndpoint       string        `yaml:"serverbrokerendpoint"`
+	BrokerType                 string        `yaml:"brokertype"`
+	EmqxRestEndpoint           string        `yaml:"emqxrestendpoint"`
+	NetclientAutoUpdate        string        `yaml:"netclientautoupdate"`
+	NetclientEndpointDetection string        `yaml:"netclientendpointdetection"`
+	MasterKey                  string        `yaml:"masterkey"`
+	DNSKey                     string        `yaml:"dnskey"`
+	AllowedOrigin              string        `yaml:"allowedorigin"`
+	NodeID                     string        `yaml:"nodeid"`
+	RestBackend                string        `yaml:"restbackend"`
+	MessageQueueBackend        string        `yaml:"messagequeuebackend"`
+	DNSMode                    string        `yaml:"dnsmode"`
+	DisableRemoteIPCheck       string        `yaml:"disableremoteipcheck"`
+	Version                    string        `yaml:"version"`
+	SQLConn                    string        `yaml:"sqlconn"`
+	Platform                   string        `yaml:"platform"`
+	Database                   string        `yaml:"database"`
+	Verbosity                  int32         `yaml:"verbosity"`
+	AuthProvider               string        `yaml:"authprovider"`
+	OIDCIssuer                 string        `yaml:"oidcissuer"`
+	ClientID                   string        `yaml:"clientid"`
+	ClientSecret               string        `yaml:"clientsecret"`
+	FrontendURL                string        `yaml:"frontendurl"`
+	DisplayKeys                string        `yaml:"displaykeys"`
+	AzureTenant                string        `yaml:"azuretenant"`
+	Telemetry                  string        `yaml:"telemetry"`
+	HostNetwork                string        `yaml:"hostnetwork"`
+	Server                     string        `yaml:"server"`
+	PublicIPService            string        `yaml:"publicipservice"`
+	MQPassword                 string        `yaml:"mqpassword"`
+	MQUserName                 string        `yaml:"mqusername"`
+	MetricsExporter            string        `yaml:"metrics_exporter"`
+	BasicAuth                  string        `yaml:"basic_auth"`
+	LicenseValue               string        `yaml:"license_value"`
+	NetmakerTenantID           string        `yaml:"netmaker_tenant_id"`
+	IsPro                      string        `yaml:"is_ee" json:"IsEE"`
+	StunPort                   int           `yaml:"stun_port"`
+	StunList                   string        `yaml:"stun_list"`
+	TurnServer                 string        `yaml:"turn_server"`
+	TurnApiServer              string        `yaml:"turn_api_server"`
+	TurnPort                   int           `yaml:"turn_port"`
+	TurnUserName               string        `yaml:"turn_username"`
+	TurnPassword               string        `yaml:"turn_password"`
+	UseTurn                    bool          `yaml:"use_turn"`
+	UsersLimit                 int           `yaml:"user_limit"`
+	NetworksLimit              int           `yaml:"network_limit"`
+	MachinesLimit              int           `yaml:"machines_limit"`
+	IngressesLimit             int           `yaml:"ingresses_limit"`
+	EgressesLimit              int           `yaml:"egresses_limit"`
+	DeployedByOperator         bool          `yaml:"deployed_by_operator"`
+	Environment                string        `yaml:"environment"`
+	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration"`
+	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
 }
 
 // SQLConfig - Generic SQL Config

+ 4 - 2
controllers/dns.go

@@ -33,6 +33,8 @@ func dnsHandlers(r *mux.Router) {
 //
 //			Security:
 //	  		oauth
+//			Responses:
+//			200: dnsResponse
 func getNodeDNS(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -264,8 +266,8 @@ func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
 //	  		oauth
 //
 //			Responses:
-//				200: dnsStringJSONResponse
-//				*: dnsStringJSONResponse
+//			200: dnsResponse
+//				*: dnsResponse
 func pushDNS(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")

+ 2 - 2
controllers/dns_test.go

@@ -238,7 +238,7 @@ func TestSetDNS(t *testing.T) {
 		assert.False(t, info.IsDir())
 		content, err := os.ReadFile("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
-		assert.Contains(t, string(content), "linuxhost.skynet")
+		assert.Contains(t, string(content), "linuxhost")
 	})
 	t.Run("EntryExists", func(t *testing.T) {
 		entry := models.DNSEntry{Address: "10.0.0.3", Name: "newhost", Network: "skynet"}
@@ -251,7 +251,7 @@ func TestSetDNS(t *testing.T) {
 		assert.False(t, info.IsDir())
 		content, err := os.ReadFile("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
-		assert.Contains(t, string(content), "newhost.skynet")
+		assert.Contains(t, string(content), "newhost")
 	})
 
 }

+ 138 - 65
controllers/docs.go

@@ -10,8 +10,8 @@
 //
 //	Schemes: https
 //	BasePath: /
-//	Version: 0.21.0
-//	Host: netmaker.io
+//	Version: 0.21.1
+//	Host: api.demo.netmaker.io
 //
 //	Consumes:
 //	- application/json
@@ -26,15 +26,37 @@
 package controller
 
 import (
-	serverconfigpkg "github.com/gravitl/netmaker/config"
+	"os"
+
+	"github.com/gravitl/netmaker/config"
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
 )
 
 var _ = useUnused() // "use" the function to prevent "unused function" errors
 
+// swagger:parameters getFile
+type filenameToGet struct {
+	// Filename
+	// in: path
+	// required: true
+	Filename string `json:"filename"`
+}
+
+// swagger:response hasAdmin
+type hasAdmin struct {
+	// in: body
+	Admin bool
+}
+
+// swagger:response apiHostResponse
+type apiHostResponse struct {
+	// in: body
+	Host models.ApiHost
+}
+
 // swagger:parameters getNodeDNS getCustomDNS getDNS
-type dnsPathParams struct {
+type dnsNetworkPathParam struct {
 	// Network
 	// in: path
 	Network string `json:"network"`
@@ -45,7 +67,6 @@ type dnsParams struct {
 	// Network
 	// in: path
 	Network string `json:"network"`
-
 	// DNS Entry
 	// in: body
 	Body []models.DNSEntry `json:"body"`
@@ -76,6 +97,18 @@ type stringJSONResponse struct {
 	Response string `json:"response"`
 }
 
+//swagger:response EnrollmentKey
+type EnrollmentKey struct {
+	// in: body
+	EnrollmentKey models.EnrollmentKey
+}
+
+//swagger:response EnrollmentKeys
+type EnrollmentKeys struct {
+	// in: body
+	EnrollmentKeys []models.EnrollmentKey
+}
+
 // swagger:parameters getAllExtClients
 type getAllClientsRequest struct {
 	// Networks
@@ -97,6 +130,12 @@ type extClientResponse struct {
 	ExtClient models.ExtClient `json:"ext_client"`
 }
 
+// swagger:response fileResponse
+type fileResponse struct {
+	// in: body
+	File os.File
+}
+
 // swagger:response successResponse
 type successResponse struct {
 	// Success Response
@@ -104,12 +143,24 @@ type successResponse struct {
 	SuccessResponse models.SuccessResponse `json:"success_response"`
 }
 
+// swagger:parameters getExtClientConf
+type extClientConfParams struct {
+	// Client ID
+	// in: path
+	ClientID string `json:"clientid"`
+	// Network
+	// in: path
+	Network string `json:"network"`
+	// Type
+	// in: path
+	Type string `json:"type"`
+}
+
 // swagger:parameters getExtClient getExtClientConf updateExtClient deleteExtClient
 type extClientPathParams struct {
 	// Client ID
 	// in: path
 	ClientID string `json:"clientid"`
-
 	// Network
 	// in: path
 	Network string `json:"network"`
@@ -137,20 +188,17 @@ type createExtClientPathParams struct {
 
 	// Node ID
 	// in: path
-	NodeID string `json:"node"`
+	NodeID string `json:"nodeid"`
 
 	// Custom ExtClient
 	// in: body
 	CustomExtClient models.CustomExtClient `json:"custom_ext_client"`
 }
 
-// swagger:parameters getNode updateNode deleteNode createRelay deleteRelay createEgressGateway deleteEgressGateway createIngressGateway deleteIngressGateway uncordonNode
+// swagger:parameters getNode updateNode deleteNode createRelay deleteRelay createEgressGateway deleteEgressGateway createIngressGateway deleteIngressGateway ingressGatewayUsers
 type networkNodePathParams struct {
-	// Network
 	// in: path
 	Network string `json:"network"`
-
-	// Node ID
 	// in: path
 	NodeID string `json:"nodeid"`
 }
@@ -161,11 +209,11 @@ type byteArrayResponse struct {
 	ByteArray []byte `json:"byte_array"`
 }
 
-// swagger:parameters getNetworks
-type headerNetworks struct {
-	// name: networks
-	// in: header
-	Networks []string `json:"networks"`
+// swagger:parameters getNetwork deleteNetwork updateNetwork getNetworkACL updateNetworkACL
+type NetworkParam struct {
+	// name: network name
+	// in:  path
+	Networkname string `json:"networkname"`
 }
 
 // swagger:response getNetworksSliceResponse
@@ -175,6 +223,13 @@ type getNetworksSliceResponse struct {
 	Networks []models.Network `json:"networks"`
 }
 
+// swagger:response hostPull
+type hostPull struct {
+	// hostPull
+	// in: body
+	HostPull models.HostPull
+}
+
 // swagger:parameters createNetwork updateNetwork
 type networkBodyParam struct {
 	// Network
@@ -182,18 +237,11 @@ type networkBodyParam struct {
 	Network models.Network `json:"network"`
 }
 
-// swagger:parameters updateNetwork getNetwork updateNetwork updateNetworkNodeLimit deleteNetwork keyUpdate createAccessKey getAccessKeys deleteAccessKey updateNetworkACL getNetworkACL
+// swagger:parameters updateNetworkNodeLimit keyUpdate createAccessKey getAccessKeys getNetworkNodes
 type networkPathParam struct {
-	// Network Name
-	// in: path
-	NetworkName string `json:"networkname"`
-}
-
-// swagger:parameters deleteAccessKey
-type networkAccessKeyNamePathParam struct {
-	// Access Key Name
+	// Network
 	// in: path
-	AccessKeyName string `json:"access_key_name"`
+	Network string `json:"network"`
 }
 
 // swagger:response networkBodyResponse
@@ -238,6 +286,15 @@ type nodeBodyParam struct {
 	Node models.LegacyNode `json:"node"`
 }
 
+//swagger:response okResponse
+type okRespone struct{}
+
+// swagger:response RegisterResponse
+type RegisterResponse struct {
+	// in: body
+	RegisterResponse models.RegisterResponse
+}
+
 // swagger:parameters createRelay
 type relayRequestBodyParam struct {
 	// Relay Request
@@ -252,53 +309,68 @@ type egressGatewayBodyParam struct {
 	EgressGatewayRequest models.EgressGatewayRequest `json:"egress_gateway_request"`
 }
 
+// swagger:parameters attachUserToRemoteAccessGateway removeUserFromRemoteAccessGW getUserRemoteAccessGws
+type RemoteAccessGatewayUser struct {
+	// in: path
+	Username string `json:"username"`
+}
+
 // swagger:parameters authenticate
 type authParamBodyParam struct {
+	// network
+	// in: path
+	Network string `json:"network"`
 	// AuthParams
 	// in: body
 	AuthParams models.AuthParams `json:"auth_params"`
 }
 
-// swagger:response serverConfigResponse
-type serverConfigResponse struct {
-	// Server Config
+// swagger:response signal
+type signal struct {
 	// in: body
-	ServerConfig serverconfigpkg.ServerConfig `json:"server_config"`
+	Signal models.Signal
 }
 
-// swagger:response nodeGetResponse
-type nodeGetResponse struct {
-	// Node Get
-	// in: body
-	NodeGet models.NodeGet `json:"node_get"`
+// swagger:parameters synchost deleteHost updateHost signalPeer updateKeys
+type HostID struct {
+	// HostID
+	// in: path
+	HostID string `json:"hostid"`
 }
 
-// swagger:response nodeLastModifiedResponse
-type nodeLastModifiedResponse struct {
-	// Node Last Modified
-	// in: body
-	NodesLastModified int64 `json:"nodes_last_modified"`
+// swagger:parameters addHostToNetwork deleteHostFromNetwork
+type HostFromNetworkParams struct {
+	// hostid to add or delete from network
+	// in: path
+	HostID string `json:"hostid"`
+	// network
+	// in: path
+	Network string `json:"network"`
 }
 
-// swagger:parameters register
-//type registerRequestBodyParam struct {
-//	// Register Request
-//	// in: body
-//	RegisterRequest config.RegisterRequest `json:"register_request"`
-//}
-//
-//// swagger:response registerResponse
-//type registerResponse struct {
-//	// Register Response
-//	// in: body
-//	RegisterResponse config.RegisterResponse `json:"register_response"`
-//}
+// swagger:parameters deleteEnrollmentKey
+type DeleteEnrollmentKeyParam struct {
+	// in: path
+	KeyID string `json:"keyid"`
+}
 
-// swagger:response boolResponse
-type boolResponse struct {
-	// Boolean Response
+// swagger:parameters handleHostRegister
+type RegisterParams struct {
+	// in: path
+	Token string `json:"token"`
+	// in: body
+	Host models.Host `json:"host"`
+}
+
+// swagger:response serverConfigResponse
+type serverConfigResponse struct {
+	// Server Config
 	// in: body
-	BoolResponse bool `json:"bool_response"`
+	// example
+	//{
+	//"mqusername": "xxxxxxx"
+	//}
+	ServerConfig config.ServerConfig `json:"server_config"`
 }
 
 // swagger:parameters createAdmin updateUser updateUserNetworks createUser
@@ -331,7 +403,6 @@ type usernamePathParam struct {
 
 // prevent issues with integration tests for types just used by Swagger docs.
 func useUnused() bool {
-	_ = dnsPathParams{}
 	_ = dnsParams{}
 	_ = dnsResponse{}
 	_ = dnsDeletePathParams{}
@@ -346,11 +417,9 @@ func useUnused() bool {
 	_ = createExtClientPathParams{}
 	_ = networkNodePathParams{}
 	_ = byteArrayResponse{}
-	_ = headerNetworks{}
 	_ = getNetworksSliceResponse{}
 	_ = networkBodyParam{}
 	_ = networkPathParam{}
-	_ = networkAccessKeyNamePathParam{}
 	_ = networkBodyResponse{}
 	_ = aclContainerBodyParam{}
 	_ = aclContainerResponse{}
@@ -361,14 +430,18 @@ func useUnused() bool {
 	_ = egressGatewayBodyParam{}
 	_ = authParamBodyParam{}
 	_ = serverConfigResponse{}
-	_ = nodeGetResponse{}
-	_ = nodeLastModifiedResponse{}
-	//	_ = registerRequestBodyParam{}
-	//	_ = registerResponse{}
-	_ = boolResponse{}
 	_ = userBodyParam{}
 	_ = userBodyResponse{}
 	_ = userAuthBodyParam{}
 	_ = usernamePathParam{}
+	_ = hasAdmin{}
+	_ = apiHostResponse{}
+	_ = fileResponse{}
+	_ = extClientConfParams{}
+	_ = hostPull{}
+	_ = okRespone{}
+	_ = signal{}
+	_ = filenameToGet{}
+	_ = dnsNetworkPathParam{}
 	return false
 }

+ 65 - 19
controllers/enrollmentkeys.go

@@ -7,19 +7,25 @@ import (
 	"time"
 
 	"github.com/gorilla/mux"
+
 	"github.com/gravitl/netmaker/auth"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slog"
 )
 
 func enrollmentKeyHandlers(r *mux.Router) {
-	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodPost)
-	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).Methods(http.MethodGet)
-	r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/v1/host/register/{token}", http.HandlerFunc(handleHostRegister)).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).
+		Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).
+		Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).
+		Methods(http.MethodDelete)
+	r.HandleFunc("/api/v1/host/register/{token}", http.HandlerFunc(handleHostRegister)).
+		Methods(http.MethodPost)
 }
 
 // swagger:route GET /api/v1/enrollment-keys enrollmentKeys getEnrollmentKeys
@@ -32,7 +38,7 @@ func enrollmentKeyHandlers(r *mux.Router) {
 //	  		oauth
 //
 //			Responses:
-//				200: getEnrollmentKeysSlice
+//				200: EnrollmentKeys
 func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
 	keys, err := logic.GetAllEnrollmentKeys()
 	if err != nil {
@@ -57,7 +63,7 @@ func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(ret)
 }
 
-// swagger:route DELETE /api/v1/enrollment-keys/{keyID} enrollmentKeys deleteEnrollmentKey
+// swagger:route DELETE /api/v1/enrollment-keys/{keyid} enrollmentKeys deleteEnrollmentKey
 //
 // Deletes an EnrollmentKey from Netmaker server.
 //
@@ -67,9 +73,9 @@ func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: deleteEnrollmentKeyResponse
+//				200: okResponse
 func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
-	var params = mux.Vars(r)
+	params := mux.Vars(r)
 	keyID := params["keyID"]
 	err := logic.DeleteEnrollmentKey(keyID)
 	if err != nil {
@@ -91,9 +97,8 @@ func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: createEnrollmentKeyResponse
+//				200: EnrollmentKey
 func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
-
 	var enrollmentKeyBody models.APIEnrollmentKey
 
 	err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
@@ -108,7 +113,13 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 		newTime = time.Unix(enrollmentKeyBody.Expiration, 0)
 	}
 
-	newEnrollmentKey, err := logic.CreateEnrollmentKey(enrollmentKeyBody.UsesRemaining, newTime, enrollmentKeyBody.Networks, enrollmentKeyBody.Tags, enrollmentKeyBody.Unlimited)
+	newEnrollmentKey, err := logic.CreateEnrollmentKey(
+		enrollmentKeyBody.UsesRemaining,
+		newTime,
+		enrollmentKeyBody.Networks,
+		enrollmentKeyBody.Tags,
+		enrollmentKeyBody.Unlimited,
+	)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -135,9 +146,9 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: handleHostRegisterResponse
+//				200: RegisterResponse
 func handleHostRegister(w http.ResponseWriter, r *http.Request) {
-	var params = mux.Vars(r)
+	params := mux.Vars(r)
 	token := params["token"]
 	logger.Log(0, "received registration attempt with token", token)
 	// check if token exists
@@ -155,7 +166,6 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	hostExists := false
 	// re-register host with turn just in case.
 	if servercfg.IsUsingTurn() {
 		err = logic.RegisterHostWithTurn(newHost.ID.String(), newHost.HostPass)
@@ -164,9 +174,20 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	// check if host already exists
+	hostExists := false
 	if hostExists = logic.HostExists(&newHost); hostExists && len(enrollmentKey.Networks) == 0 {
-		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "attempted to re-register with no networks")
-		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("host already exists"), "badrequest"))
+		logger.Log(
+			0,
+			"host",
+			newHost.ID.String(),
+			newHost.Name,
+			"attempted to re-register with no networks",
+		)
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(fmt.Errorf("host already exists"), "badrequest"),
+		)
 		return
 	}
 	// version check
@@ -189,11 +210,16 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 	// use the token
 	if ok := logic.TryToUseEnrollmentKey(enrollmentKey); !ok {
 		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration")
-		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("invalid enrollment key"), "badrequest"))
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(fmt.Errorf("invalid enrollment key"), "badrequest"),
+		)
 		return
 	}
 	hostPass := newHost.HostPass
 	if !hostExists {
+		newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
 		// register host
 		logic.CheckHostPorts(&newHost)
 		// create EMQX credentials and ACLs for host
@@ -208,14 +234,21 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 			}
 		}
 		if err = logic.CreateHost(&newHost); err != nil {
-			logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration -", err.Error())
+			logger.Log(
+				0,
+				"host",
+				newHost.ID.String(),
+				newHost.Name,
+				"failed registration -",
+				err.Error(),
+			)
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 			return
 		}
 	} else {
 		// need to revise the list of networks from key
 		// based on the ones host currently has
-		var networksToAdd = []string{}
+		networksToAdd := []string{}
 		currentNets := logic.GetHostNetworks(newHost.ID.String())
 		for _, newNet := range enrollmentKey.Networks {
 			if !logic.StringSliceContains(currentNets, newNet) {
@@ -223,6 +256,19 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 			}
 		}
 		enrollmentKey.Networks = networksToAdd
+		currHost, err := logic.GetHost(newHost.ID.String())
+		if err != nil {
+			slog.Error("failed registration", "hostID", newHost.ID.String(), "hostName", newHost.Name, "error", err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+		logic.UpdateHostFromClient(&newHost, currHost)
+		err = logic.UpsertHost(currHost)
+		if err != nil {
+			slog.Error("failed to update host", "id", currHost.ID, "error", err)
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
 	}
 	// ready the response
 	server := servercfg.GetServerInfo()

+ 7 - 10
controllers/ext_client.go

@@ -90,16 +90,6 @@ func getAllExtClients(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
 
-	headerNetworks := r.Header.Get("networks")
-	networksSlice := []string{}
-	marshalErr := json.Unmarshal([]byte(headerNetworks), &networksSlice)
-	if marshalErr != nil {
-		slog.Error("error unmarshalling networks", "error", marshalErr.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(marshalErr, "internal"))
-		return
-	}
-
-	var err error
 	clients, err := logic.GetAllExtClients()
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, "failed to get all extclients: ", err.Error())
@@ -313,6 +303,8 @@ Endpoint = %s
 //
 //			Security:
 //	  		oauth
+//			Responses:
+//			200:  okResponse
 func createExtClient(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -385,6 +377,11 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	extclient.RemoteAccessClientID = customExtClient.RemoteAccessClientID
 	extclient.IngressGatewayID = nodeid
 
+	// set extclient dns to ingressdns if extclient dns is not explicitly set
+	if (extclient.DNS == "") && (node.IngressDNS != "") {
+		extclient.DNS = node.IngressDNS
+	}
+
 	extclient.Network = node.Network
 	host, err := logic.GetHost(node.HostID.String())
 	if err != nil {

+ 3 - 1
controllers/files.go

@@ -7,7 +7,7 @@ import (
 )
 
 func fileHandlers(r *mux.Router) {
-	// swagger:route GET /meshclient/files/{filename} meshclient fileServer
+	// swagger:route GET /meshclient/files/{filename} meshclient getFile
 	//
 	// Retrieve a file from the file server.
 	//
@@ -15,5 +15,7 @@ func fileHandlers(r *mux.Router) {
 	//
 	// 		Security:
 	//   		oauth
+	//		Responses:
+	//		200: fileResponse
 	r.PathPrefix("/meshclient/files").Handler(http.StripPrefix("/meshclient/files", http.FileServer(http.Dir("./meshclient/files"))))
 }

+ 37 - 18
controllers/hosts.go

@@ -23,6 +23,7 @@ func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", Authorize(true, false, "all", http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/hosts/{hostid}/upgrade", logic.SecurityCheck(true, http.HandlerFunc(upgradeHost))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(deleteHostFromNetwork))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost)
@@ -31,6 +32,22 @@ func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/v1/auth-register/host", socketHandler)
 }
 
+// upgrade host is a handler to send upgrade message to a host
+func upgradeHost(w http.ResponseWriter, r *http.Request) {
+	host, err := logic.GetHost(mux.Vars(r)["hostid"])
+	if err != nil {
+		slog.Error("failed to find host", "error", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "notfound"))
+		return
+	}
+	if err := mq.HostUpdate(&models.HostUpdate{Action: models.Upgrade, Host: *host}); err != nil {
+		slog.Error("failed to upgrade host", "error", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logic.ReturnSuccessResponse(w, r, "passed message to upgrade host")
+}
+
 // swagger:route GET /api/hosts hosts getHosts
 //
 // Lists all hosts.
@@ -41,7 +58,7 @@ func hostHandlers(r *mux.Router) {
 //	  		oauth
 //
 //			Responses:
-//				200: getHostsSliceResponse
+//				200: apiHostResponse
 func getHosts(w http.ResponseWriter, r *http.Request) {
 	currentHosts, err := logic.GetAllHosts()
 	if err != nil {
@@ -56,7 +73,7 @@ func getHosts(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiHosts)
 }
 
-// swagger:route GET /api/v1/host pull pullHost
+// swagger:route GET /api/v1/host hosts pullHost
 //
 // Used by clients for "pull" command
 //
@@ -66,7 +83,7 @@ func getHosts(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: pull
+//				200: hostPull
 func pull(w http.ResponseWriter, r *http.Request) {
 
 	hostID := r.Header.Get(hostIDHeader) // return JSON/API formatted keys
@@ -83,13 +100,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
 	}
 	allNodes, err := logic.GetAllNodes()
 	if err != nil {
-		logger.Log(0, "could not pull peers for host", hostID)
+		logger.Log(0, "failed to get nodes: ", hostID)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
 	hPU, err := logic.GetPeerUpdateForHost("", host, allNodes, nil, nil)
 	if err != nil {
-		logger.Log(0, "could not pull peers for host", hostID)
+		logger.Log(0, "could not pull peers for host", hostID, err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
@@ -128,7 +145,7 @@ func pull(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: updateHostResponse
+//				200: apiHostResponse
 func updateHost(w http.ResponseWriter, r *http.Request) {
 	var newHostData models.ApiHost
 	err := json.NewDecoder(r.Body).Decode(&newHostData)
@@ -196,7 +213,7 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: deleteHostResponse
+//				200: apiHostResponse
 func deleteHost(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	hostid := params["hostid"]
@@ -235,9 +252,8 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 //
 //			Security:
 //	  		oauth
-//
 //			Responses:
-//				200: addHostToNetworkResponse
+//				200: okResponse
 func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
 
 	var params = mux.Vars(r)
@@ -284,7 +300,7 @@ func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: deleteHostFromNetworkResponse
+//				200: okResponse
 func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 
 	var params = mux.Vars(r)
@@ -343,9 +359,12 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 	}
 	node.Action = models.NODE_DELETE
 	node.PendingDelete = true
-	// notify node change
-	mq.RunUpdates(node, false)
-	go func() { // notify of peer change
+	go func() {
+		// notify node change
+		if err := mq.NodeUpdate(node); err != nil {
+			slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+		}
+		// notify of peer change
 		err = mq.PublishDeletedNodePeerUpdate(node)
 		if err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
@@ -358,7 +377,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
-// swagger:route POST /api/hosts/adm/authenticate hosts authenticateHost
+// swagger:route POST /api/hosts/adm/authenticate authenticate authenticateHost
 //
 // Host based authentication for making further API calls.
 //
@@ -451,7 +470,7 @@ func authenticateHost(response http.ResponseWriter, request *http.Request) {
 	response.Write(successJSONResponse)
 }
 
-// swagger:route POST /api/hosts/{hostid}/signalpeer signalPeer
+// swagger:route POST /api/hosts/{hostid}/signalpeer hosts signalPeer
 //
 // send signal to peer.
 //
@@ -517,7 +536,7 @@ func signalPeer(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(signal)
 }
 
-// swagger:route POST /api/hosts/keys host updateAllKeys
+// swagger:route POST /api/hosts/keys hosts updateAllKeys
 //
 // Update keys for a network.
 //
@@ -555,7 +574,7 @@ func updateAllKeys(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
-// swagger:route POST /api/hosts/{hostid}keys host updateKeys
+// swagger:route POST /api/hosts/{hostid}keys hosts updateKeys
 //
 // Update keys for a network.
 //
@@ -594,7 +613,7 @@ func updateKeys(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
-// swagger:route POST /api/hosts/{hostId}/sync host syncHost
+// swagger:route POST /api/hosts/{hostid}/sync hosts synchost
 //
 // Requests a host to pull.
 //

+ 1 - 1
controllers/legacy.go

@@ -22,7 +22,7 @@ func legacyHandlers(r *mux.Router) {
 //	  		oauth
 //
 //			Responses:
-//				200: wipeLegacyNodesResponse
+//				200: successResponse
 func wipeLegacyNodes(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")

+ 15 - 4
controllers/migrate.go

@@ -19,7 +19,7 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
-// swagger:route PUT /api/v1/nodes/migrate nodes migrateNode
+// swagger:route PUT /api/v1/nodes/migrate nodes migrateData
 //
 // Used to migrate a legacy node.
 //
@@ -29,7 +29,7 @@ import (
 //	  		oauth
 //
 //			Responses:
-//				200: nodeJoinResponse
+//				200: hostPull
 func migrate(w http.ResponseWriter, r *http.Request) {
 	data := models.MigrationData{}
 	host := models.Host{}
@@ -123,7 +123,11 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 			if err != nil {
 				logger.Log(0, "error creating ingress gateway for node", node.ID, err.Error())
 			}
-			mq.RunUpdates(&ingressNode, true)
+			go func() {
+				if err := mq.NodeUpdate(&ingressNode); err != nil {
+					slog.Error("error publishing node update to node", "node", ingressNode.ID, "error", err)
+				}
+			}()
 		}
 	}
 }
@@ -136,6 +140,9 @@ func convertLegacyHostNode(legacy models.LegacyNode) (models.Host, models.Node)
 	host.AutoUpdate = servercfg.AutoUpdateEnabled()
 	host.Interface = "netmaker"
 	host.ListenPort = int(legacy.ListenPort)
+	if host.ListenPort == 0 {
+		host.ListenPort = 51821
+	}
 	host.MTU = int(legacy.MTU)
 	host.PublicKey, _ = wgtypes.ParseKey(legacy.PublicKey)
 	host.MacAddress = net.HardwareAddr(legacy.MacAddress)
@@ -147,6 +154,11 @@ func convertLegacyHostNode(legacy models.LegacyNode) (models.Host, models.Node)
 	host.IsDocker = models.ParseBool(legacy.IsDocker)
 	host.IsK8S = models.ParseBool(legacy.IsK8S)
 	host.IsStatic = models.ParseBool(legacy.IsStatic)
+	host.PersistentKeepalive = time.Duration(legacy.PersistentKeepalive) * time.Second
+	if host.PersistentKeepalive == 0 {
+		host.PersistentKeepalive = models.DefaultPersistentKeepAlive
+	}
+
 	node := convertLegacyNode(legacy, host.ID)
 	return host, node
 }
@@ -198,7 +210,6 @@ func convertLegacyNode(legacy models.LegacyNode, hostID uuid.UUID) models.Node {
 	node.IsRelay = false
 	node.RelayedNodes = []string{}
 	node.DNSOn = models.ParseBool(legacy.DNSOn)
-	node.PersistentKeepalive = time.Duration(int64(time.Second) * int64(legacy.PersistentKeepalive))
 	node.LastModified = time.Now()
 	node.ExpirationDateTime = time.Unix(legacy.ExpirationDateTime, 0)
 	node.EgressGatewayNatEnabled = models.ParseBool(legacy.EgressGatewayNatEnabled)

+ 2 - 2
controllers/network.go

@@ -180,7 +180,7 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: stringJSONResponse
+//				200: successResponse
 func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
@@ -278,7 +278,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(network)
 }
 
-// swagger:route PUT /api/networks networks updateNetwork
+// swagger:route PUT /api/networks/{networkname} networks updateNetwork
 //
 // Update pro settings for a network.
 //

+ 30 - 21
controllers/node.go

@@ -34,7 +34,7 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 
-// swagger:route POST /api/nodes/adm/{network}/authenticate nodes authenticate
+// swagger:route POST /api/nodes/adm/{network}/authenticate authenticate authenticate
 //
 // Authenticate to make further API calls related to a network.
 //
@@ -440,9 +440,11 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
 	go func() {
+		if err := mq.NodeUpdate(&node); err != nil {
+			slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+		}
 		mq.PublishPeerUpdate()
 	}()
-	mq.RunUpdates(&node, true)
 }
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid}/deletegateway nodes deleteEgressGateway
@@ -481,9 +483,11 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
 	go func() {
+		if err := mq.NodeUpdate(&node); err != nil {
+			slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+		}
 		mq.PublishPeerUpdate()
 	}()
-	mq.RunUpdates(&node, true)
 }
 
 // == INGRESS ==
@@ -530,8 +534,11 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 	logger.Log(1, r.Header.Get("user"), "created ingress gateway on node", nodeid, "on network", netid)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
-
-	mq.RunUpdates(&node, true)
+	go func() {
+		if err := mq.NodeUpdate(&node); err != nil {
+			slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+		}
+	}()
 }
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid}/deleteingress nodes deleteIngressGateway
@@ -582,16 +589,16 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 			if err != nil {
 				return
 			}
-			go mq.PublishSingleHostPeerUpdate(
-				host,
-				allNodes,
-				nil,
-				removedClients[:],
-			)
+			go func() {
+				if err := mq.PublishSingleHostPeerUpdate(host, allNodes, nil, removedClients[:]); err != nil {
+					slog.Error("publishSingleHostUpdate", "host", host.Name, "error", err)
+				}
+				if err := mq.NodeUpdate(&node); err != nil {
+					slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+				}
+			}()
 		}
 	}
-
-	mq.RunUpdates(&node, true)
 }
 
 // swagger:route PUT /api/nodes/{network}/{nodeid} nodes updateNode
@@ -660,9 +667,11 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	logger.Log(1, r.Header.Get("user"), "updated node", currentNode.ID.String(), "on network", currentNode.Network)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
-	mq.RunUpdates(newNode, ifaceDelta)
 	go func(aclUpdate, relayupdate bool, newNode *models.Node) {
-		if aclUpdate || relayupdate {
+		if err := mq.NodeUpdate(newNode); err != nil {
+			slog.Error("error publishing node update to node", "node", newNode.ID, "error", err)
+		}
+		if aclUpdate || relayupdate || ifaceDelta {
 			if err := mq.PublishPeerUpdate(); err != nil {
 				logger.Log(0, "error during node ACL update for node", newNode.ID.String())
 			}
@@ -735,13 +744,13 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
-	if !fromNode { // notify node change
-		mq.RunUpdates(&node, false)
-	}
 	go func() { // notify of peer change
-		var err error
-		err = mq.PublishDeletedNodePeerUpdate(&node)
-		if err != nil {
+		if !fromNode {
+			if err := mq.NodeUpdate(&node); err != nil {
+				slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+			}
+		}
+		if err := mq.PublishDeletedNodePeerUpdate(&node); err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
 		}
 		host, err := logic.GetHost(node.HostID.String())

+ 39 - 20
controllers/server.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/gorilla/mux"
+
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
@@ -15,24 +16,32 @@ import (
 
 func serverHandlers(r *mux.Router) {
 	// r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(true, http.HandlerFunc(addNetwork))).Methods(http.MethodPost)
-	r.HandleFunc("/api/server/health", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
-		resp.WriteHeader(http.StatusOK)
-		resp.Write([]byte("Server is up and running!!"))
-	}))
-	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
-	r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
+	r.HandleFunc(
+		"/api/server/health",
+		http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+			resp.WriteHeader(http.StatusOK)
+			resp.Write([]byte("Server is up and running!!"))
+		}),
+	)
+	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).
+		Methods(http.MethodGet)
+	r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).
+		Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
-	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).
+		Methods(http.MethodGet)
 }
 
-func getUsage(w http.ResponseWriter, r *http.Request) {
+func getUsage(w http.ResponseWriter, _ *http.Request) {
 	type usage struct {
-		Hosts     int `json:"hosts"`
-		Clients   int `json:"clients"`
-		Networks  int `json:"networks"`
-		Users     int `json:"users"`
-		Ingresses int `json:"ingresses"`
-		Egresses  int `json:"egresses"`
+		Hosts            int `json:"hosts"`
+		Clients          int `json:"clients"`
+		Networks         int `json:"networks"`
+		Users            int `json:"users"`
+		Ingresses        int `json:"ingresses"`
+		Egresses         int `json:"egresses"`
+		Relays           int `json:"relays"`
+		InternetGateways int `json:"internet_gateways"`
 	}
 	var serverUsage usage
 	hosts, err := logic.GetAllHosts()
@@ -51,6 +60,7 @@ func getUsage(w http.ResponseWriter, r *http.Request) {
 	if err == nil {
 		serverUsage.Networks = len(networks)
 	}
+	// TODO this part bellow can be optimized to get nodes just once
 	ingresses, err := logic.GetAllIngresses()
 	if err == nil {
 		serverUsage.Ingresses = len(ingresses)
@@ -59,12 +69,19 @@ func getUsage(w http.ResponseWriter, r *http.Request) {
 	if err == nil {
 		serverUsage.Egresses = len(egresses)
 	}
+	relays, err := logic.GetRelays()
+	if err == nil {
+		serverUsage.Relays = len(relays)
+	}
+	gateways, err := logic.GetInternetGateways()
+	if err == nil {
+		serverUsage.InternetGateways = len(gateways)
+	}
 	w.Header().Set("Content-Type", "application/json")
 	json.NewEncoder(w).Encode(models.SuccessResponse{
 		Code:     http.StatusOK,
 		Response: serverUsage,
 	})
-
 }
 
 // swagger:route GET /api/server/status server getStatus
@@ -83,6 +100,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
 		DB           bool   `json:"db_connected"`
 		Broker       bool   `json:"broker_connected"`
 		LicenseError string `json:"license_error"`
+		IsPro        bool   `json:"is_pro"`
 	}
 
 	licenseErr := ""
@@ -94,6 +112,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
 		DB:           database.IsConnected(),
 		Broker:       mq.IsConnected(),
 		LicenseError: licenseErr,
+		IsPro:        servercfg.IsPro,
 	}
 
 	w.Header().Set("Content-Type", "application/json")
@@ -103,12 +122,12 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
 // allowUsers - allow all authenticated (valid) users - only used by getConfig, may be able to remove during refactor
 func allowUsers(next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
-		var errorResponse = models.ErrorResponse{
+		errorResponse := models.ErrorResponse{
 			Code: http.StatusUnauthorized, Message: logic.Unauthorized_Msg,
 		}
 		bearerToken := r.Header.Get("Authorization")
-		var tokenSplit = strings.Split(bearerToken, " ")
-		var authToken = ""
+		tokenSplit := strings.Split(bearerToken, " ")
+		authToken := ""
 		if len(tokenSplit) < 2 {
 			logic.ReturnErrorResponse(w, r, errorResponse)
 			return
@@ -142,7 +161,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) {
 	// get params
 
 	json.NewEncoder(w).Encode(servercfg.GetServerInfo())
-	//w.WriteHeader(http.StatusOK)
+	// w.WriteHeader(http.StatusOK)
 }
 
 // swagger:route GET /api/server/getconfig server getConfig
@@ -168,5 +187,5 @@ func getConfig(w http.ResponseWriter, r *http.Request) {
 		scfg.IsPro = "yes"
 	}
 	json.NewEncoder(w).Encode(scfg)
-	//w.WriteHeader(http.StatusOK)
+	// w.WriteHeader(http.StatusOK)
 }

+ 33 - 7
controllers/user.go

@@ -12,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slog"
 )
@@ -36,9 +37,9 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterHostSSO).Methods(http.MethodGet)
 }
 
-// swagger:route POST /api/users/adm/authenticate user authenticateUser
+// swagger:route POST /api/users/adm/authenticate authenticate authenticateUser
 //
-// Node authenticates using its password and retrieves a JWT for authorization.
+// User authenticates using its password and retrieves a JWT for authorization.
 //
 //			Schemes: https
 //
@@ -96,7 +97,6 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 	}
 	// Send back the JWT
 	successJSONResponse, jsonError := json.Marshal(successResponse)
-
 	if jsonError != nil {
 		logger.Log(0, username,
 			"error marshalling resp: ", err.Error())
@@ -106,6 +106,33 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 	logger.Log(2, username, "was authenticated")
 	response.Header().Set("Content-Type", "application/json")
 	response.Write(successJSONResponse)
+
+	go func() {
+		if servercfg.IsPro && servercfg.GetRacAutoDisable() {
+			// enable all associeated clients for the user
+			clients, err := logic.GetAllExtClients()
+			if err != nil {
+				slog.Error("error getting clients: ", "error", err)
+				return
+			}
+			for _, client := range clients {
+				if client.OwnerID == username && !client.Enabled {
+					slog.Info(fmt.Sprintf("enabling ext client %s for user %s due to RAC autodisabling feature", client.ClientID, client.OwnerID))
+					if newClient, err := logic.ToggleExtClientConnectivity(&client, true); err != nil {
+						slog.Error("error disabling ext client in RAC autodisable hook", "error", err)
+						continue // dont return but try for other clients
+					} else {
+						// publish peer update to ingress gateway
+						if ingressNode, err := logic.GetNodeByID(newClient.IngressGatewayID); err == nil {
+							if err = mq.PublishPeerUpdate(); err != nil {
+								slog.Error("error updating ext clients on", "ingress", ingressNode.ID.String(), "err", err.Error())
+							}
+						}
+					}
+				}
+			}
+		}
+	}()
 }
 
 // swagger:route GET /api/users/adm/hassuperadmin user hasSuperAdmin
@@ -118,7 +145,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: successResponse
+//				200: hasAdmin
 func hasSuperAdmin(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -426,9 +453,8 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if auth.IsOauthUser(user) == nil {
-		err := fmt.Errorf("cannot update user info for oauth user %s", username)
-		logger.Log(0, err.Error())
+	if auth.IsOauthUser(user) == nil && userchange.Password != "" {
+		err := fmt.Errorf("cannot update user's password for an oauth user %s", username)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
 		return
 	}

+ 0 - 5
docker/Caddyfile

@@ -1,6 +1,5 @@
 # Dashboard
 https://dashboard.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	# Apply basic security headers
 	header {
 		# Enable cross origin access to *.{$NM_DOMAIN}
@@ -22,24 +21,20 @@ https://dashboard.{$NM_DOMAIN} {
 
 # API
 https://api.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://netmaker:8081
 }
 
 # TURN
 https://turn.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy host.docker.internal:3479
 }
 
 # TURN API
 https://turnapi.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://host.docker.internal:8089
 }
 
 # MQ
 wss://broker.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy ws://mq:8883 # For EMQX websockets use `reverse_proxy ws://mq:8083`
 }

+ 0 - 8
docker/Caddyfile-pro

@@ -1,6 +1,5 @@
 # Dashboard
 https://dashboard.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	# Apply basic security headers
 	header {
 		# Enable cross origin access to *.{$NM_DOMAIN}
@@ -22,42 +21,35 @@ https://dashboard.{$NM_DOMAIN} {
 
 # Netmaker Exporter
 https://netmaker-exporter.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://netmaker-exporter:8085
 }
 
 # Prometheus
 https://prometheus.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://prometheus:9090
 }
 
 # Grafana
 https://grafana.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://grafana:3000
 }
 
 # API
 https://api.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://netmaker:8081
 }
 
 # TURN
 https://turn.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy host.docker.internal:3479
 }
 
 # TURN API
 https://turnapi.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy http://host.docker.internal:8089
 }
 
 # MQ
 wss://broker.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	reverse_proxy ws://mq:8883
 }

+ 7 - 7
go.mod

@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.3
-	github.com/go-playground/validator/v10 v10.15.1
+	github.com/go-playground/validator/v10 v10.15.5
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.1
 	github.com/gorilla/handlers v1.5.1
@@ -14,12 +14,12 @@ require (
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.4
-	github.com/txn2/txeh v1.5.3
-	golang.org/x/crypto v0.12.0
-	golang.org/x/net v0.14.0 // indirect
-	golang.org/x/oauth2 v0.11.0
-	golang.org/x/sys v0.11.0 // indirect
-	golang.org/x/text v0.12.0 // indirect
+	github.com/txn2/txeh v1.5.5
+	golang.org/x/crypto v0.13.0
+	golang.org/x/net v0.15.0 // indirect
+	golang.org/x/oauth2 v0.12.0
+	golang.org/x/sys v0.12.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1

+ 14 - 14
go.sum

@@ -30,8 +30,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
-github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
+github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -96,33 +96,33 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/txn2/txeh v1.5.3 h1:ZMgc3r+5/AFtE/ayCoICpvxj7xl/CYsZjnIGhozV/Kc=
-github.com/txn2/txeh v1.5.3/go.mod h1:qYzGG9kCzeVEI12geK4IlanHWY8X4uy/I3NcW7mk8g4=
+github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
+github.com/txn2/txeh v1.5.5/go.mod h1:qYzGG9kCzeVEI12geK4IlanHWY8X4uy/I3NcW7mk8g4=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
-golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
-golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
-golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
-golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
+golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
-golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb h1:9aqVcYEDHmSNb0uOWukxV5lHV09WqiSiCuhEgWNETLY=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

+ 6 - 0
logic/auth.go

@@ -112,6 +112,7 @@ func CreateSuperAdmin(u *models.User) error {
 		return errors.New("superadmin user already exists")
 	}
 	u.IsSuperAdmin = true
+	u.IsAdmin = false
 	return CreateUser(u)
 }
 
@@ -141,6 +142,11 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
 
 	// Create a new JWT for the node
 	tokenString, _ := CreateUserJWT(authRequest.UserName, result.IsSuperAdmin, result.IsAdmin)
+
+	// update last login time
+	result.LastLoginTime = time.Now()
+	UpsertUser(result)
+
 	return tokenString, nil
 }
 

+ 1 - 1
logic/dns.go

@@ -32,7 +32,7 @@ func SetDNS() error {
 			return err
 		}
 		for _, entry := range dns {
-			hostfile.AddHost(entry.Address, entry.Name+"."+entry.Network)
+			hostfile.AddHost(entry.Address, entry.Name)
 		}
 	}
 	if corefilestring == "" {

+ 28 - 1
logic/extpeers.go

@@ -9,6 +9,7 @@ import (
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/models"
+	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
@@ -225,7 +226,7 @@ func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) mode
 	if update.PublicKey != "" && old.PublicKey != update.PublicKey {
 		new.PublicKey = update.PublicKey
 	}
-	if update.DNS != "" && update.DNS != old.DNS {
+	if update.DNS != old.DNS {
 		new.DNS = update.DNS
 	}
 	if update.Enabled != old.Enabled {
@@ -276,3 +277,29 @@ func GetAllExtClients() ([]models.ExtClient, error) {
 
 	return clients, nil
 }
+
+// ToggleExtClientConnectivity - enables or disables an ext client
+func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.ExtClient, error) {
+	update := models.CustomExtClient{
+		Enabled:              enable,
+		ClientID:             client.ClientID,
+		PublicKey:            client.PublicKey,
+		DNS:                  client.DNS,
+		ExtraAllowedIPs:      client.ExtraAllowedIPs,
+		DeniedACLs:           client.DeniedACLs,
+		RemoteAccessClientID: client.RemoteAccessClientID,
+	}
+
+	// update in DB
+	newClient := UpdateExtClient(client, &update)
+	if err := DeleteExtClient(client.Network, client.ClientID); err != nil {
+		slog.Error("failed to delete ext client during update", "id", client.ClientID, "network", client.Network, "error", err)
+		return newClient, err
+	}
+	if err := SaveExtClient(&newClient); err != nil {
+		slog.Error("failed to save updated ext client during update", "id", newClient.ClientID, "network", newClient.Network, "error", err)
+		return newClient, err
+	}
+
+	return newClient, nil
+}

+ 22 - 2
logic/gateway.go

@@ -11,7 +11,27 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 )
 
-// GetAllIngresses - gets all the hosts that are ingresses
+// GetInternetGateways - gets all the nodes that are internet gateways
+func GetInternetGateways() ([]models.Node, error) {
+	nodes, err := GetAllNodes()
+	if err != nil {
+		return nil, err
+	}
+	igs := make([]models.Node, 0)
+	for _, node := range nodes {
+		if !node.IsEgressGateway {
+			continue
+		}
+		for _, ran := range node.EgressGatewayRanges {
+			if ran == "0.0.0.0/0" {
+				igs = append(igs, node)
+			}
+		}
+	}
+	return igs, nil
+}
+
+// GetAllIngresses - gets all the nodes that are ingresses
 func GetAllIngresses() ([]models.Node, error) {
 	nodes, err := GetAllNodes()
 	if err != nil {
@@ -26,7 +46,7 @@ func GetAllIngresses() ([]models.Node, error) {
 	return ingresses, nil
 }
 
-// GetAllEgresses - gets all the hosts that are egresses
+// GetAllEgresses - gets all the nodes that are egresses
 func GetAllEgresses() ([]models.Node, error) {
 	nodes, err := GetAllNodes()
 	if err != nil {

+ 12 - 10
logic/hosts.go

@@ -13,11 +13,12 @@ import (
 
 	"github.com/devilcove/httpclient"
 	"github.com/google/uuid"
+	"golang.org/x/crypto/bcrypt"
+
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
-	"golang.org/x/crypto/bcrypt"
 )
 
 var (
@@ -66,6 +67,7 @@ func deleteHostFromCache(hostID string) {
 	delete(hostsCacheMap, hostID)
 	hostCacheMutex.Unlock()
 }
+
 func loadHostsIntoCache(hMap map[string]models.Host) {
 	hostCacheMutex.Lock()
 	hostsCacheMap = hMap
@@ -79,7 +81,6 @@ const (
 
 // GetAllHosts - returns all hosts in flat list or error
 func GetAllHosts() ([]models.Host, error) {
-
 	currHosts := getHostsFromCache()
 	if len(currHosts) != 0 {
 		return currHosts, nil
@@ -139,7 +140,6 @@ func GetHostsMap() (map[string]models.Host, error) {
 
 // GetHost - gets a host from db given id
 func GetHost(hostid string) (*models.Host, error) {
-
 	if host, ok := getHostFromCache(hostid); ok {
 		return &host, nil
 	}
@@ -217,11 +217,13 @@ func UpdateHost(newHost, currentHost *models.Host) {
 		newHost.ListenPort = currentHost.ListenPort
 	}
 
+	if newHost.PersistentKeepalive == 0 {
+		newHost.PersistentKeepalive = currentHost.PersistentKeepalive
+	}
 }
 
 // UpdateHostFromClient - used for updating host on server with update recieved from client
 func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool) {
-
 	if newHost.PublicKey != currHost.PublicKey {
 		currHost.PublicKey = newHost.PublicKey
 		sendPeerUpdate = true
@@ -230,7 +232,8 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
 		currHost.ListenPort = newHost.ListenPort
 		sendPeerUpdate = true
 	}
-	if newHost.WgPublicListenPort != 0 && currHost.WgPublicListenPort != newHost.WgPublicListenPort {
+	if newHost.WgPublicListenPort != 0 &&
+		currHost.WgPublicListenPort != newHost.WgPublicListenPort {
 		currHost.WgPublicListenPort = newHost.WgPublicListenPort
 		sendPeerUpdate = true
 	}
@@ -242,9 +245,9 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
 	currHost.Debug = newHost.Debug
 	currHost.Verbosity = newHost.Verbosity
 	currHost.Version = newHost.Version
-	if newHost.Name != "" {
-		currHost.Name = newHost.Name
-	}
+	currHost.IsStatic = newHost.IsStatic
+	currHost.MTU = newHost.MTU
+	currHost.Name = newHost.Name
 	if len(newHost.NatType) > 0 && newHost.NatType != currHost.NatType {
 		currHost.NatType = newHost.NatType
 		sendPeerUpdate = true
@@ -488,7 +491,7 @@ func CheckHostPorts(h *models.Host) {
 	}
 	for _, host := range hosts {
 		if host.ID.String() == h.ID.String() {
-			//skip self
+			// skip self
 			continue
 		}
 		if !host.EndpointIP.Equal(h.EndpointIP) {
@@ -503,7 +506,6 @@ func CheckHostPorts(h *models.Host) {
 			h.ListenPort = minPort
 		}
 	}
-
 }
 
 // HostExists - checks if given host already exists

+ 1 - 1
logic/jwts.go

@@ -54,7 +54,7 @@ func CreateJWT(uuid string, macAddress string, network string) (response string,
 
 // CreateUserJWT - creates a user jwt token
 func CreateUserJWT(username string, issuperadmin, isadmin bool) (response string, err error) {
-	expirationTime := time.Now().Add(60 * 12 * time.Minute)
+	expirationTime := time.Now().Add(servercfg.GetServerConfig().JwtValidityDuration)
 	claims := &models.UserClaims{
 		UserName:     username,
 		IsSuperAdmin: issuperadmin,

+ 4 - 5
logic/nodes.go

@@ -350,9 +350,6 @@ func SetNodeDefaults(node *models.Node) {
 		node.DefaultACL = parentNetwork.DefaultACL
 	}
 
-	if node.PersistentKeepalive == 0 {
-		node.PersistentKeepalive = time.Second * time.Duration(parentNetwork.DefaultKeepalive)
-	}
 	node.SetLastModified()
 	node.SetLastCheckIn()
 	node.SetDefaultConnected()
@@ -425,12 +422,14 @@ func GetAllNodesAPI(nodes []models.Node) []models.ApiNode {
 
 // DeleteExpiredNodes - goroutine which deletes nodes which are expired
 func DeleteExpiredNodes(ctx context.Context, peerUpdate chan *models.Node) {
+	// Delete Expired Nodes Every Hour
+	ticker := time.NewTicker(time.Hour)
 	for {
 		select {
 		case <-ctx.Done():
+			ticker.Stop()
 			return
-		case <-time.After(time.Hour):
-			// Delete Expired Nodes Every Hour
+		case <-ticker.C:
 			allnodes, err := GetAllNodes()
 			if err != nil {
 				slog.Error("failed to retrieve all nodes", "error", err.Error())

+ 8 - 9
logic/peers.go

@@ -64,7 +64,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				}
 				relayPeer := wgtypes.PeerConfig{
 					PublicKey:                   relayHost.PublicKey,
-					PersistentKeepaliveInterval: &relayNode.PersistentKeepalive,
+					PersistentKeepaliveInterval: &relayHost.PersistentKeepalive,
 					ReplaceAllowedIPs:           true,
 					AllowedIPs:                  GetAllowedIPs(&node, &relayNode, nil),
 				}
@@ -111,18 +111,18 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			peer := peer
 			if peer.ID.String() == node.ID.String() {
 				logger.Log(2, "peer update, skipping self")
-				//skip yourself
+				// skip yourself
 				continue
 			}
 
 			peerHost, err := GetHost(peer.HostID.String())
 			if err != nil {
 				logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
-				return models.HostPeerUpdate{}, err
+				continue
 			}
 			peerConfig := wgtypes.PeerConfig{
 				PublicKey:                   peerHost.PublicKey,
-				PersistentKeepaliveInterval: &peer.PersistentKeepalive,
+				PersistentKeepaliveInterval: &peerHost.PersistentKeepalive,
 				ReplaceAllowedIPs:           true,
 			}
 			if peer.IsEgressGateway {
@@ -173,14 +173,13 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
 			}
 
-			peerPort := GetPeerListenPort(peerHost)
 			var nodePeer wgtypes.PeerConfig
 			if _, ok := peerIndexMap[peerHost.PublicKey.String()]; !ok {
 				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 				hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
 					Interfaces: peerHost.Interfaces,
-					ListenPort: peerPort,
+					ListenPort: peerHost.ListenPort,
 					IsStatic:   peerHost.IsStatic,
 				}
 				nodePeer = peerConfig
@@ -191,7 +190,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].Remove = false
 				hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
 					Interfaces: peerHost.Interfaces,
-					ListenPort: peerPort,
+					ListenPort: peerHost.ListenPort,
 					IsStatic:   peerHost.IsStatic,
 				}
 				nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
@@ -391,7 +390,7 @@ func GetEgressIPs(peer *models.Node) []net.IPNet {
 		logger.Log(0, "error retrieving host for peer", peer.ID.String(), err.Error())
 	}
 
-	//check for internet gateway
+	// check for internet gateway
 	internetGateway := false
 	if slices.Contains(peer.EgressGatewayRanges, "0.0.0.0/0") || slices.Contains(peer.EgressGatewayRanges, "::/0") {
 		internetGateway = true
@@ -440,7 +439,7 @@ func getNodeAllowedIPs(peer, node *models.Node) []net.IPNet {
 	}
 	// handle egress gateway peers
 	if peer.IsEgressGateway {
-		//hasGateway = true
+		// hasGateway = true
 		egressIPs := GetEgressIPs(peer)
 		allowedips = append(allowedips, egressIPs...)
 	}

+ 4 - 0
logic/relay.go

@@ -5,6 +5,10 @@ import (
 	"net"
 )
 
+var GetRelays = func() ([]models.Node, error) {
+	return []models.Node{}, nil
+}
+
 var RelayedAllowedIPs = func(peer, node *models.Node) []net.IPNet {
 	return []net.IPNet{}
 }

+ 1 - 1
logic/timer.go

@@ -17,7 +17,7 @@ import (
 const timer_hours_between_runs = 24
 
 // HookManagerCh - channel to add any new hooks
-var HookManagerCh = make(chan models.HookDetails, 2)
+var HookManagerCh = make(chan models.HookDetails, 3)
 
 // == Public ==
 

+ 0 - 1
logic/wireguard.go

@@ -12,7 +12,6 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 		newNode.IsEgressGateway != currentNode.IsEgressGateway ||
 		newNode.IsIngressGateway != currentNode.IsIngressGateway ||
 		newNode.IsRelay != currentNode.IsRelay ||
-		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
 		newNode.DNSOn != currentNode.DNSOn ||
 		newNode.Connected != currentNode.Connected {
 		return true

+ 6 - 1
logic/zombie.go

@@ -77,16 +77,21 @@ func checkForZombieHosts(h *models.Host) {
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	logger.Log(2, "Zombie management started")
 	InitializeZombies()
+
+	// Zombie Nodes Cleanup Four Times a Day
+	ticker := time.NewTicker(time.Hour * ZOMBIE_TIMEOUT)
+
 	for {
 		select {
 		case <-ctx.Done():
+			ticker.Stop()
 			close(peerUpdate)
 			return
 		case id := <-newZombie:
 			zombies = append(zombies, id)
 		case id := <-newHostZombie:
 			hostZombies = append(hostZombies, id)
-		case <-time.After(time.Hour * ZOMBIE_TIMEOUT): // run this check 4 times a day
+		case <-ticker.C: // run this check 4 times a day
 			logger.Log(3, "checking for zombie nodes")
 			if len(zombies) > 0 {
 				for i := len(zombies) - 1; i >= 0; i-- {

+ 1 - 1
main.go

@@ -28,7 +28,7 @@ import (
 	"golang.org/x/exp/slog"
 )
 
-var version = "v0.21.0"
+var version = "v0.21.1"
 
 // Start DB Connection and start API Request Handler
 func main() {

+ 31 - 3
migrate/migrate.go

@@ -3,17 +3,19 @@ package migrate
 import (
 	"encoding/json"
 
+	"golang.org/x/exp/slog"
+
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
-	"golang.org/x/exp/slog"
 )
 
 // Run - runs all migrations
 func Run() {
 	updateEnrollmentKeys()
 	assignSuperAdmin()
+	updateHosts()
 }
 
 func assignSuperAdmin() {
@@ -37,7 +39,13 @@ func assignSuperAdmin() {
 			user.IsAdmin = false
 			err = logic.UpsertUser(*user)
 			if err != nil {
-				slog.Error("error updating user to superadmin", "user", user.UserName, "error", err.Error())
+				slog.Error(
+					"error updating user to superadmin",
+					"user",
+					user.UserName,
+					"error",
+					err.Error(),
+				)
 				continue
 			} else {
 				createdSuperAdmin = true
@@ -49,7 +57,6 @@ func assignSuperAdmin() {
 	if !createdSuperAdmin {
 		slog.Error("failed to create superadmin!!")
 	}
-
 }
 
 func updateEnrollmentKeys() {
@@ -87,3 +94,24 @@ func updateEnrollmentKeys() {
 
 	}
 }
+
+func updateHosts() {
+	rows, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
+	if err != nil {
+		logger.Log(0, "failed to fetch database records for hosts")
+	}
+	for _, row := range rows {
+		var host models.Host
+		if err := json.Unmarshal([]byte(row), &host); err != nil {
+			logger.Log(0, "failed to unmarshal database row to host", "row", row)
+			continue
+		}
+		if host.PersistentKeepalive == 0 {
+			host.PersistentKeepalive = models.DefaultPersistentKeepAlive
+			if err := logic.UpsertHost(&host); err != nil {
+				logger.Log(0, "failed to upsert host", host.ID.String())
+				continue
+			}
+		}
+	}
+}

+ 27 - 24
models/api_host.go

@@ -3,33 +3,35 @@ package models
 import (
 	"net"
 	"strings"
+	"time"
 )
 
 // ApiHost - the host struct for API usage
 type ApiHost struct {
-	ID                 string   `json:"id"`
-	Verbosity          int      `json:"verbosity"`
-	FirewallInUse      string   `json:"firewallinuse"`
-	Version            string   `json:"version"`
-	Name               string   `json:"name"`
-	OS                 string   `json:"os"`
-	Debug              bool     `json:"debug"`
-	IsStatic           bool     `json:"isstatic"`
-	ListenPort         int      `json:"listenport"`
-	WgPublicListenPort int      `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
-	MTU                int      `json:"mtu" yaml:"mtu"`
-	Interfaces         []Iface  `json:"interfaces" yaml:"interfaces"`
-	DefaultInterface   string   `json:"defaultinterface" yaml:"defautlinterface"`
-	EndpointIP         string   `json:"endpointip" yaml:"endpointip"`
-	PublicKey          string   `json:"publickey"`
-	MacAddress         string   `json:"macaddress"`
-	Nodes              []string `json:"nodes"`
-	IsDefault          bool     `json:"isdefault" yaml:"isdefault"`
-	IsRelayed          bool     `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
-	RelayedBy          string   `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
-	IsRelay            bool     `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
-	RelayedHosts       []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
-	NatType            string   `json:"nat_type" yaml:"nat_type"`
+	ID                  string   `json:"id"`
+	Verbosity           int      `json:"verbosity"`
+	FirewallInUse       string   `json:"firewallinuse"`
+	Version             string   `json:"version"`
+	Name                string   `json:"name"`
+	OS                  string   `json:"os"`
+	Debug               bool     `json:"debug"`
+	IsStatic            bool     `json:"isstatic"`
+	ListenPort          int      `json:"listenport"`
+	WgPublicListenPort  int      `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
+	MTU                 int      `json:"mtu"                   yaml:"mtu"`
+	Interfaces          []Iface  `json:"interfaces"            yaml:"interfaces"`
+	DefaultInterface    string   `json:"defaultinterface"      yaml:"defautlinterface"`
+	EndpointIP          string   `json:"endpointip"            yaml:"endpointip"`
+	PublicKey           string   `json:"publickey"`
+	MacAddress          string   `json:"macaddress"`
+	Nodes               []string `json:"nodes"`
+	IsDefault           bool     `json:"isdefault"             yaml:"isdefault"`
+	IsRelayed           bool     `json:"isrelayed"             yaml:"isrelayed"             bson:"isrelayed"`
+	RelayedBy           string   `json:"relayed_by"            yaml:"relayed_by"            bson:"relayed_by"`
+	IsRelay             bool     `json:"isrelay"               yaml:"isrelay"               bson:"isrelay"`
+	RelayedHosts        []string `json:"relay_hosts"           yaml:"relay_hosts"           bson:"relay_hosts"`
+	NatType             string   `json:"nat_type"              yaml:"nat_type"`
+	PersistentKeepalive int      `json:"persistentkeepalive"   yaml:"persistentkeepalive"`
 }
 
 // Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
@@ -57,6 +59,7 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.Version = h.Version
 	a.IsDefault = h.IsDefault
 	a.NatType = h.NatType
+	a.PersistentKeepalive = int(h.PersistentKeepalive.Seconds())
 	return &a
 }
 
@@ -94,6 +97,6 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.IsDefault = a.IsDefault
 	h.NatType = currentHost.NatType
 	h.TurnEndpoint = currentHost.TurnEndpoint
-
+	h.PersistentKeepalive = time.Duration(a.PersistentKeepalive) * time.Second
 	return &h
 }

+ 0 - 3
models/api_node.go

@@ -15,7 +15,6 @@ type ApiNode struct {
 	Address6                string   `json:"address6" validate:"omitempty,ipv6"`
 	LocalAddress            string   `json:"localaddress" validate:"omitempty,ipv4"`
 	AllowedIPs              []string `json:"allowedips"`
-	PersistentKeepalive     int32    `json:"persistentkeepalive"`
 	LastModified            int64    `json:"lastmodified"`
 	ExpirationDateTime      int64    `json:"expdatetime"`
 	LastCheckIn             int64    `json:"lastcheckin"`
@@ -68,7 +67,6 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.IngressDNS = a.IngressDns
 	convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest
 	convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled
-	convertedNode.PersistentKeepalive = time.Second * time.Duration(a.PersistentKeepalive)
 	convertedNode.RelayedNodes = a.RelayedNodes
 	convertedNode.DefaultACL = a.DefaultACL
 	convertedNode.OwnerID = currentNode.OwnerID
@@ -127,7 +125,6 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
 	if isEmptyAddr(apiNode.LocalAddress) {
 		apiNode.LocalAddress = ""
 	}
-	apiNode.PersistentKeepalive = int32(nm.PersistentKeepalive.Seconds())
 	apiNode.LastModified = nm.LastModified.Unix()
 	apiNode.LastCheckIn = nm.LastCheckIn.Unix()
 	apiNode.LastPeerUpdate = nm.LastPeerUpdate.Unix()

+ 47 - 40
models/host.go

@@ -3,6 +3,7 @@ package models
 import (
 	"net"
 	"net/netip"
+	"time"
 
 	"github.com/google/uuid"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -33,38 +34,42 @@ var NAT_Types = struct {
 }
 
 // WIREGUARD_INTERFACE name of wireguard interface
-const WIREGUARD_INTERFACE = "netmaker"
+const (
+	WIREGUARD_INTERFACE        = "netmaker"
+	DefaultPersistentKeepAlive = 20 * time.Second
+)
 
 // Host - represents a host on the network
 type Host struct {
-	ID                 uuid.UUID        `json:"id" yaml:"id"`
-	Verbosity          int              `json:"verbosity" yaml:"verbosity"`
-	FirewallInUse      string           `json:"firewallinuse" yaml:"firewallinuse"`
-	Version            string           `json:"version" yaml:"version"`
-	IPForwarding       bool             `json:"ipforwarding" yaml:"ipforwarding"`
-	DaemonInstalled    bool             `json:"daemoninstalled" yaml:"daemoninstalled"`
-	AutoUpdate         bool             `json:"autoupdate" yaml:"autoupdate"`
-	HostPass           string           `json:"hostpass" yaml:"hostpass"`
-	Name               string           `json:"name" yaml:"name"`
-	OS                 string           `json:"os" yaml:"os"`
-	Interface          string           `json:"interface" yaml:"interface"`
-	Debug              bool             `json:"debug" yaml:"debug"`
-	ListenPort         int              `json:"listenport" yaml:"listenport"`
-	WgPublicListenPort int              `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
-	MTU                int              `json:"mtu" yaml:"mtu"`
-	PublicKey          wgtypes.Key      `json:"publickey" yaml:"publickey"`
-	MacAddress         net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
-	TrafficKeyPublic   []byte           `json:"traffickeypublic" yaml:"traffickeypublic"`
-	Nodes              []string         `json:"nodes" yaml:"nodes"`
-	Interfaces         []Iface          `json:"interfaces" yaml:"interfaces"`
-	DefaultInterface   string           `json:"defaultinterface" yaml:"defaultinterface"`
-	EndpointIP         net.IP           `json:"endpointip" yaml:"endpointip"`
-	IsDocker           bool             `json:"isdocker" yaml:"isdocker"`
-	IsK8S              bool             `json:"isk8s" yaml:"isk8s"`
-	IsStatic           bool             `json:"isstatic" yaml:"isstatic"`
-	IsDefault          bool             `json:"isdefault" yaml:"isdefault"`
-	NatType            string           `json:"nat_type,omitempty" yaml:"nat_type,omitempty"`
-	TurnEndpoint       *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
+	ID                  uuid.UUID        `json:"id"                      yaml:"id"`
+	Verbosity           int              `json:"verbosity"               yaml:"verbosity"`
+	FirewallInUse       string           `json:"firewallinuse"           yaml:"firewallinuse"`
+	Version             string           `json:"version"                 yaml:"version"`
+	IPForwarding        bool             `json:"ipforwarding"            yaml:"ipforwarding"`
+	DaemonInstalled     bool             `json:"daemoninstalled"         yaml:"daemoninstalled"`
+	AutoUpdate          bool             `json:"autoupdate"              yaml:"autoupdate"`
+	HostPass            string           `json:"hostpass"                yaml:"hostpass"`
+	Name                string           `json:"name"                    yaml:"name"`
+	OS                  string           `json:"os"                      yaml:"os"`
+	Interface           string           `json:"interface"               yaml:"interface"`
+	Debug               bool             `json:"debug"                   yaml:"debug"`
+	ListenPort          int              `json:"listenport"              yaml:"listenport"`
+	WgPublicListenPort  int              `json:"wg_public_listen_port"   yaml:"wg_public_listen_port"`
+	MTU                 int              `json:"mtu"                     yaml:"mtu"`
+	PublicKey           wgtypes.Key      `json:"publickey"               yaml:"publickey"`
+	MacAddress          net.HardwareAddr `json:"macaddress"              yaml:"macaddress"`
+	TrafficKeyPublic    []byte           `json:"traffickeypublic"        yaml:"traffickeypublic"`
+	Nodes               []string         `json:"nodes"                   yaml:"nodes"`
+	Interfaces          []Iface          `json:"interfaces"              yaml:"interfaces"`
+	DefaultInterface    string           `json:"defaultinterface"        yaml:"defaultinterface"`
+	EndpointIP          net.IP           `json:"endpointip"              yaml:"endpointip"`
+	IsDocker            bool             `json:"isdocker"                yaml:"isdocker"`
+	IsK8S               bool             `json:"isk8s"                   yaml:"isk8s"`
+	IsStatic            bool             `json:"isstatic"                yaml:"isstatic"`
+	IsDefault           bool             `json:"isdefault"               yaml:"isdefault"`
+	NatType             string           `json:"nat_type,omitempty"      yaml:"nat_type,omitempty"`
+	TurnEndpoint        *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
+	PersistentKeepalive time.Duration    `json:"persistentkeepalive"     yaml:"persistentkeepalive"`
 }
 
 // FormatBool converts a boolean to a [yes|no] string
@@ -89,26 +94,28 @@ func ParseBool(s string) bool {
 type HostMqAction string
 
 const (
+	// Upgrade - const to request host to update it's client
+	Upgrade HostMqAction = "UPGRADE"
 	// SignalHost - const for host signal action
-	SignalHost = "SIGNAL_HOST"
+	SignalHost HostMqAction = "SIGNAL_HOST"
 	// UpdateHost - constant for host update action
-	UpdateHost = "UPDATE_HOST"
+	UpdateHost HostMqAction = "UPDATE_HOST"
 	// DeleteHost - constant for host delete action
-	DeleteHost = "DELETE_HOST"
+	DeleteHost HostMqAction = "DELETE_HOST"
 	// JoinHostToNetwork - constant for host network join action
-	JoinHostToNetwork = "JOIN_HOST_TO_NETWORK"
+	JoinHostToNetwork HostMqAction = "JOIN_HOST_TO_NETWORK"
 	// Acknowledgement - ACK response for hosts
-	Acknowledgement = "ACK"
+	Acknowledgement HostMqAction = "ACK"
 	// RequestAck - request an ACK
-	RequestAck = "REQ_ACK"
+	RequestAck HostMqAction = "REQ_ACK"
 	// CheckIn - update last check in times and public address and interfaces
-	CheckIn = "CHECK_IN"
-	// REGISTER_WITH_TURN - registers host with turn server if configured
-	RegisterWithTurn = "REGISTER_WITH_TURN"
+	CheckIn HostMqAction = "CHECK_IN"
+	// RegisterWithTurn - registers host with turn server if configured
+	RegisterWithTurn HostMqAction = "REGISTER_WITH_TURN"
 	// UpdateKeys - update wireguard private/public keys
-	UpdateKeys = "UPDATE_KEYS"
+	UpdateKeys HostMqAction = "UPDATE_KEYS"
 	// RequestPull - request a pull from a host
-	RequestPull = "REQ_PULL"
+	RequestPull HostMqAction = "REQ_PULL"
 )
 
 // SignalAction - turn peer signal action

+ 21 - 25
models/node.go

@@ -54,28 +54,27 @@ type Iface struct {
 
 // CommonNode - represents a commonn node data elements shared by netmaker and netclient
 type CommonNode struct {
-	ID                  uuid.UUID     `json:"id" yaml:"id"`
-	HostID              uuid.UUID     `json:"hostid" yaml:"hostid"`
-	Network             string        `json:"network" yaml:"network"`
-	NetworkRange        net.IPNet     `json:"networkrange" yaml:"networkrange"`
-	NetworkRange6       net.IPNet     `json:"networkrange6" yaml:"networkrange6"`
-	InternetGateway     *net.UDPAddr  `json:"internetgateway" yaml:"internetgateway"`
-	Server              string        `json:"server" yaml:"server"`
-	Connected           bool          `json:"connected" yaml:"connected"`
-	Address             net.IPNet     `json:"address" yaml:"address"`
-	Address6            net.IPNet     `json:"address6" yaml:"address6"`
-	Action              string        `json:"action" yaml:"action"`
-	LocalAddress        net.IPNet     `json:"localaddress" yaml:"localaddress"`
-	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
-	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
-	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
-	IsRelayed           bool          `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
-	RelayedBy           string        `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
-	IsRelay             bool          `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
-	RelayedNodes        []string      `json:"relaynodes" yaml:"relayedNodes"`
-	IngressDNS          string        `json:"ingressdns" yaml:"ingressdns"`
-	DNSOn               bool          `json:"dnson" yaml:"dnson"`
-	PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
+	ID                  uuid.UUID    `json:"id" yaml:"id"`
+	HostID              uuid.UUID    `json:"hostid" yaml:"hostid"`
+	Network             string       `json:"network" yaml:"network"`
+	NetworkRange        net.IPNet    `json:"networkrange" yaml:"networkrange"`
+	NetworkRange6       net.IPNet    `json:"networkrange6" yaml:"networkrange6"`
+	InternetGateway     *net.UDPAddr `json:"internetgateway" yaml:"internetgateway"`
+	Server              string       `json:"server" yaml:"server"`
+	Connected           bool         `json:"connected" yaml:"connected"`
+	Address             net.IPNet    `json:"address" yaml:"address"`
+	Address6            net.IPNet    `json:"address6" yaml:"address6"`
+	Action              string       `json:"action" yaml:"action"`
+	LocalAddress        net.IPNet    `json:"localaddress" yaml:"localaddress"`
+	IsEgressGateway     bool         `json:"isegressgateway" yaml:"isegressgateway"`
+	EgressGatewayRanges []string     `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
+	IsIngressGateway    bool         `json:"isingressgateway" yaml:"isingressgateway"`
+	IsRelayed           bool         `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
+	RelayedBy           string       `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
+	IsRelay             bool         `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
+	RelayedNodes        []string     `json:"relaynodes" yaml:"relayedNodes"`
+	IngressDNS          string       `json:"ingressdns" yaml:"ingressdns"`
+	DNSOn               bool         `json:"dnson" yaml:"dnson"`
 }
 
 // Node - a model of a network node
@@ -369,9 +368,6 @@ func (newNode *Node) Fill(currentNode *Node, isPro bool) { // TODO add new field
 	if newNode.Address6.String() == "" {
 		newNode.Address6 = currentNode.Address6
 	}
-	if newNode.PersistentKeepalive < 0 {
-		newNode.PersistentKeepalive = currentNode.PersistentKeepalive
-	}
 	if newNode.LastModified != currentNode.LastModified {
 		newNode.LastModified = currentNode.LastModified
 	}

+ 27 - 32
models/structs.go

@@ -24,19 +24,21 @@ type AuthParams struct {
 
 // User struct - struct for Users
 type User struct {
-	UserName     string              `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
-	Password     string              `json:"password" bson:"password" validate:"required,min=5"`
-	IsAdmin      bool                `json:"isadmin" bson:"isadmin"`
-	IsSuperAdmin bool                `json:"issuperadmin"`
-	RemoteGwIDs  map[string]struct{} `json:"remote_gw_ids"`
+	UserName      string              `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
+	Password      string              `json:"password" bson:"password" validate:"required,min=5"`
+	IsAdmin       bool                `json:"isadmin" bson:"isadmin"`
+	IsSuperAdmin  bool                `json:"issuperadmin"`
+	RemoteGwIDs   map[string]struct{} `json:"remote_gw_ids"`
+	LastLoginTime time.Time           `json:"last_login_time"`
 }
 
 // ReturnUser - return user struct
 type ReturnUser struct {
-	UserName     string              `json:"username"`
-	IsAdmin      bool                `json:"isadmin"`
-	IsSuperAdmin bool                `json:"issuperadmin"`
-	RemoteGwIDs  map[string]struct{} `json:"remote_gw_ids"`
+	UserName      string              `json:"username"`
+	IsAdmin       bool                `json:"isadmin"`
+	IsSuperAdmin  bool                `json:"issuperadmin"`
+	RemoteGwIDs   map[string]struct{} `json:"remote_gw_ids"`
+	LastLoginTime time.Time           `json:"last_login_time"`
 }
 
 // UserAuthParams - user auth params struct
@@ -248,23 +250,22 @@ type NodeJoinResponse struct {
 
 // ServerConfig - struct for dealing with the server information for a netclient
 type ServerConfig struct {
-	CoreDNSAddr string       `yaml:"corednsaddr"`
-	API         string       `yaml:"api"`
-	APIPort     string       `yaml:"apiport"`
-	DNSMode     string       `yaml:"dnsmode"`
-	Version     string       `yaml:"version"`
-	MQPort      string       `yaml:"mqport"`
-	MQUserName  string       `yaml:"mq_username"`
-	MQPassword  string       `yaml:"mq_password"`
-	Server      string       `yaml:"server"`
-	Broker      string       `yaml:"broker"`
-	IsPro       bool         `yaml:"isee" json:"Is_EE"`
-	StunPort    int          `yaml:"stun_port"`
-	StunList    []StunServer `yaml:"stun_list"`
-	TrafficKey  []byte       `yaml:"traffickey"`
-	TurnDomain  string       `yaml:"turn_domain"`
-	TurnPort    int          `yaml:"turn_port"`
-	UseTurn     bool         `yaml:"use_turn"`
+	CoreDNSAddr string `yaml:"corednsaddr"`
+	API         string `yaml:"api"`
+	APIPort     string `yaml:"apiport"`
+	DNSMode     string `yaml:"dnsmode"`
+	Version     string `yaml:"version"`
+	MQPort      string `yaml:"mqport"`
+	MQUserName  string `yaml:"mq_username"`
+	MQPassword  string `yaml:"mq_password"`
+	Server      string `yaml:"server"`
+	Broker      string `yaml:"broker"`
+	IsPro       bool   `yaml:"isee" json:"Is_EE"`
+	StunPort    int    `yaml:"stun_port"`
+	TrafficKey  []byte `yaml:"traffickey"`
+	TurnDomain  string `yaml:"turn_domain"`
+	TurnPort    int    `yaml:"turn_port"`
+	UseTurn     bool   `yaml:"use_turn"`
 }
 
 // User.NameInCharset - returns if name is in charset below or not
@@ -290,12 +291,6 @@ type JoinData struct {
 	Key  string `json:"key" yaml:"key"`
 }
 
-// StunServer - struct to hold data required for using stun server
-type StunServer struct {
-	Domain string `json:"domain" yaml:"domain"`
-	Port   int    `json:"port" yaml:"port"`
-}
-
 // HookDetails - struct to hold hook info
 type HookDetails struct {
 	Hook     func() error

+ 1 - 10
mq/handlers.go

@@ -3,10 +3,10 @@ package mq
 import (
 	"encoding/json"
 	"fmt"
+
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
@@ -20,15 +20,6 @@ import (
 var UpdateMetrics = func(client mqtt.Client, msg mqtt.Message) {
 }
 
-func RunUpdates(node *models.Node, ifaceDelta bool) {
-	go func() { // don't block http response
-		// publish node update if not server
-		if err := NodeUpdate(node); err != nil {
-			logger.Log(1, "error publishing node update to node", node.ID.String(), err.Error())
-		}
-	}()
-}
-
 // DefaultHandler default message queue handler  -- NOT USED
 func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
 	slog.Info("mqtt default handler", "topic", msg.Topic(), "message", msg.Payload())

+ 1 - 1
mq/publishers.go

@@ -315,7 +315,7 @@ func PublishDeleteExtClientDNS(client *models.ExtClient) error {
 func PublishCustomDNS(entry *models.DNSEntry) error {
 	dns := models.DNSUpdate{
 		Action: models.DNSInsert,
-		Name:   entry.Name + "." + entry.Network,
+		Name:   entry.Name,
 		//entry.Address6 is never used
 		Address: entry.Address,
 	}

+ 1 - 1
pro/controllers/users.go

@@ -78,7 +78,7 @@ func attachUserToRemoteAccessGw(w http.ResponseWriter, r *http.Request) {
 
 // swagger:route DELETE /api/users/{username}/remote_access_gw user removeUserFromRemoteAccessGW
 //
-// Attach User to a remote access gateway.
+// Delete User from a remote access gateway.
 //
 //			Schemes: https
 //

+ 4 - 0
pro/initialize.go

@@ -38,6 +38,9 @@ func InitPro() {
 		logic.SetFreeTierForTelemetry(false)
 		// == End License Handling ==
 		AddLicenseHooks()
+		if servercfg.GetServerConfig().RacAutoDisable {
+			AddRacHooks()
+		}
 		resetFailover()
 	})
 	logic.EnterpriseFailoverFunc = proLogic.SetFailover
@@ -52,6 +55,7 @@ func InitPro() {
 	logic.GetMetrics = proLogic.GetMetrics
 	logic.UpdateMetrics = proLogic.UpdateMetrics
 	logic.DeleteMetrics = proLogic.DeleteMetrics
+	logic.GetRelays = proLogic.GetRelays
 	logic.GetAllowedIpsForRelayed = proLogic.GetAllowedIpsForRelayed
 	logic.RelayedAllowedIPs = proLogic.RelayedAllowedIPs
 	logic.UpdateRelayed = proLogic.UpdateRelayed

+ 14 - 7
pro/license.go

@@ -13,6 +13,7 @@ import (
 	"net/http"
 	"time"
 
+	"golang.org/x/crypto/nacl/box"
 	"golang.org/x/exp/slog"
 
 	"github.com/gravitl/netmaker/database"
@@ -20,7 +21,6 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
-	"golang.org/x/crypto/nacl/box"
 )
 
 const (
@@ -29,7 +29,7 @@ const (
 
 type apiServerConf struct {
 	PrivateKey []byte `json:"private_key" binding:"required"`
-	PublicKey  []byte `json:"public_key" binding:"required"`
+	PublicKey  []byte `json:"public_key"  binding:"required"`
 }
 
 // AddLicenseHooks - adds the validation and cache clear hooks
@@ -113,7 +113,11 @@ func ValidateLicense() (err error) {
 		return err
 	}
 
-	respData, err := ncutils.BoxDecrypt(base64decode(licenseResponse.EncryptedLicense), apiPublicKey, tempPrivKey)
+	respData, err := ncutils.BoxDecrypt(
+		base64decode(licenseResponse.EncryptedLicense),
+		apiPublicKey,
+		tempPrivKey,
+	)
 	if err != nil {
 		err = fmt.Errorf("failed to decrypt license: %w", err)
 		return err
@@ -133,7 +137,7 @@ func ValidateLicense() (err error) {
 // as well as secure communication with API
 // if none present, it generates a new pair
 func FetchApiServerKeys() (pub *[32]byte, priv *[32]byte, err error) {
-	var returnData = apiServerConf{}
+	returnData := apiServerConf{}
 	currentData, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, db_license_key)
 	if err != nil && !database.IsEmptyRecord(err) {
 		return nil, nil, err
@@ -182,7 +186,6 @@ func getLicensePublicKey(licensePubKeyEncoded string) (*[32]byte, error) {
 }
 
 func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, error) {
-
 	publicKeyBytes, err := ncutils.ConvertKeyToBytes(publicKey)
 	if err != nil {
 		return nil, err
@@ -199,7 +202,11 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
 		return nil, err
 	}
 
-	req, err := http.NewRequest(http.MethodPost, getAccountsHost()+"/api/v1/license/validate", bytes.NewReader(requestBody))
+	req, err := http.NewRequest(
+		http.MethodPost,
+		getAccountsHost()+"/api/v1/license/validate",
+		bytes.NewReader(requestBody),
+	)
 	if err != nil {
 		return nil, err
 	}
@@ -242,7 +249,7 @@ func getAccountsHost() string {
 }
 
 func cacheResponse(response []byte) error {
-	var lrc = licenseResponseCache{
+	lrc := licenseResponseCache{
 		Body: response,
 	}
 

+ 28 - 5
pro/logic/relays.go

@@ -3,15 +3,32 @@ package logic
 import (
 	"errors"
 	"fmt"
+	"net"
+
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
-	"net"
+	"golang.org/x/exp/slog"
 )
 
+// GetRelays - gets all the nodes that are relays
+func GetRelays() ([]models.Node, error) {
+	nodes, err := logic.GetAllNodes()
+	if err != nil {
+		return nil, err
+	}
+	relays := make([]models.Node, 0)
+	for _, node := range nodes {
+		if node.IsRelay {
+			relays = append(relays, node)
+		}
+	}
+	return relays, nil
+}
+
 // CreateRelay - creates a relay
 func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) {
 	var returnnodes []models.Node
@@ -67,7 +84,7 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.N
 	return returnnodes
 }
 
-//func GetRelayedNodes(relayNode *models.Node) (models.Node, error) {
+// func GetRelayedNodes(relayNode *models.Node) (models.Node, error) {
 //	var returnnodes []models.Node
 //	networkNodes, err := GetNetworkNodes(relayNode.Network)
 //	if err != nil {
@@ -81,12 +98,12 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.N
 //		}
 //	}
 //	return returnnodes, nil
-//}
+// }
 
 // ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
-	//isIp := functions.IsIpCIDR(gateway.RangeString)
+	// isIp := functions.IsIpCIDR(gateway.RangeString)
 	empty := len(relay.RelayedNodes) == 0
 	if empty {
 		return errors.New("IP Ranges Cannot Be Empty")
@@ -136,7 +153,13 @@ func UpdateRelayed(currentNode, newNode *models.Node) {
 	updatenodes := updateRelayNodes(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes)
 	if len(updatenodes) > 0 {
 		for _, relayedNode := range updatenodes {
-			mq.RunUpdates(&relayedNode, false)
+			node := relayedNode
+			go func() {
+				if err := mq.NodeUpdate(&node); err != nil {
+					slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+				}
+
+			}()
 		}
 	}
 }

+ 76 - 0
pro/remote_access_client.go

@@ -0,0 +1,76 @@
+package pro
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slog"
+)
+
+const racAutoDisableCheckInterval = 3 * time.Minute
+
+// AddRacHooks - adds hooks for Remote Access Client
+func AddRacHooks() {
+	slog.Debug("adding RAC autodisable hook")
+	logic.HookManagerCh <- models.HookDetails{
+		Hook:     racAutoDisableHook,
+		Interval: racAutoDisableCheckInterval,
+	}
+}
+
+// racAutoDisableHook - checks if RAC is enabled and if it is, checks if it should be disabled
+func racAutoDisableHook() error {
+	slog.Debug("running RAC autodisable hook")
+
+	users, err := logic.GetUsers()
+	if err != nil {
+		slog.Error("error getting users: ", "error", err)
+		return err
+	}
+	clients, err := logic.GetAllExtClients()
+	if err != nil {
+		slog.Error("error getting clients: ", "error", err)
+		return err
+	}
+
+	currentTime := time.Now()
+	validityDuration := servercfg.GetJwtValidityDuration()
+	for _, user := range users {
+		if !currentTime.After(user.LastLoginTime.Add(validityDuration)) {
+			continue
+		}
+		for _, client := range clients {
+			if (client.OwnerID == user.UserName) && !user.IsAdmin && !user.IsSuperAdmin && client.Enabled {
+				slog.Info(fmt.Sprintf("disabling ext client %s for user %s due to RAC autodisabling", client.ClientID, client.OwnerID))
+				if err := disableExtClient(&client); err != nil {
+					slog.Error("error disabling ext client in RAC autodisable hook", "error", err)
+					continue // dont return but try for other clients
+				}
+			}
+		}
+	}
+
+	slog.Debug("finished running RAC autodisable hook")
+	return nil
+}
+
+func disableExtClient(client *models.ExtClient) error {
+	if newClient, err := logic.ToggleExtClientConnectivity(client, false); err != nil {
+		return err
+	} else {
+		// publish peer update to ingress gateway
+		if ingressNode, err := logic.GetNodeByID(newClient.IngressGatewayID); err == nil {
+			if err = mq.PublishPeerUpdate(); err != nil {
+				slog.Error("error updating ext clients on", "ingress", ingressNode.ID.String(), "err", err.Error())
+			}
+		} else {
+			return err
+		}
+	}
+
+	return nil
+}

+ 11 - 7
pro/types.go

@@ -54,13 +54,15 @@ type LicenseSecret struct {
 
 // Usage - struct for license usage
 type Usage struct {
-	Servers   int `json:"servers"`
-	Users     int `json:"users"`
-	Hosts     int `json:"hosts"`
-	Clients   int `json:"clients"`
-	Networks  int `json:"networks"`
-	Ingresses int `json:"ingresses"`
-	Egresses  int `json:"egresses"`
+	Servers          int `json:"servers"`
+	Users            int `json:"users"`
+	Hosts            int `json:"hosts"`
+	Clients          int `json:"clients"`
+	Networks         int `json:"networks"`
+	Ingresses        int `json:"ingresses"`
+	Egresses         int `json:"egresses"`
+	Relays           int `json:"relays"`
+	InternetGateways int `json:"internet_gateways"`
 }
 
 // Usage.SetDefaults - sets the default values for usage
@@ -72,6 +74,8 @@ func (l *Usage) SetDefaults() {
 	l.Networks = 0
 	l.Ingresses = 0
 	l.Egresses = 0
+	l.Relays = 0
+	l.InternetGateways = 0
 }
 
 // ValidateLicenseRequest - used for request to validate license endpoint

+ 9 - 2
pro/util.go

@@ -16,9 +16,7 @@ func base64encode(input []byte) string {
 
 // base64decode - base64 decode helper function
 func base64decode(input string) []byte {
-
 	bytes, err := base64.StdEncoding.DecodeString(input)
-
 	if err != nil {
 		return nil
 	}
@@ -44,6 +42,7 @@ func getCurrentServerUsage() (limits Usage) {
 	if err == nil {
 		limits.Networks = len(networks)
 	}
+	// TODO this part bellow can be optimized to get nodes just once
 	ingresses, err := logic.GetAllIngresses()
 	if err == nil {
 		limits.Ingresses = len(ingresses)
@@ -52,5 +51,13 @@ func getCurrentServerUsage() (limits Usage) {
 	if err == nil {
 		limits.Egresses = len(egresses)
 	}
+	relays, err := logic.GetRelays()
+	if err == nil {
+		limits.Relays = len(relays)
+	}
+	gateways, err := logic.GetInternetGateways()
+	if err == nil {
+		limits.InternetGateways = len(gateways)
+	}
 	return
 }

+ 8 - 4
release.md

@@ -1,11 +1,15 @@
 
-# Netmaker v0.21.0
+# Netmaker v0.21.1
 
 ## Whats New
-- New User Management, Refer Docs For More Info
-- Added Support For Remote Access Client
+- Remote access client session management, refer users section in docs for more details
+- Can now create generic DNS entries
+- Upgrade client version to match server version from UI
+- Moved PersistentKeepAlive setting from node to host level
 ## What's Fixed
--  Proper Cleanup Of Extclients On a Client Gateway Deletion
+- Extclients DNS now properly set from ingress dns value provided
+- Allow role update of OAuth user
+- Fixed zombie node issue
 ## known issues
 - Windows installer does not install WireGuard
 - netclient-gui will continously display error dialog if netmaker server is offline

+ 4 - 0
scripts/netmaker.default.env

@@ -78,3 +78,7 @@ FRONTEND_URL=
 AZURE_TENANT=
 # https://oidc.yourprovider.com - URL of oidc provider
 OIDC_ISSUER=
+# Duration of JWT token validity in seconds
+JWT_VALIDITY_DURATION=43200
+# Auto disable a user's connecteds clients bassed on JWT token expiration
+RAC_AUTO_DISABLE="true"

+ 0 - 113
scripts/nm-certs.sh

@@ -1,113 +0,0 @@
-#!/bin/bash
-
-CONFIG_FILE=netmaker.env
-SCRIPT_DIR=$(dirname "$(realpath "$0")")
-
-# get and check the config
-if [ ! -f "$SCRIPT_DIR/$CONFIG_FILE" ]; then
-	echo "Config file missing"
-	exit 1
-fi
-source "$SCRIPT_DIR/$CONFIG_FILE"
-if [ -z "$NM_DOMAIN" ] || [ -z "$NM_EMAIL" ]; then
-	echo "Config not valid"
-	exit 1
-fi
-
-# TODO make sure this doesnt break, parse `certbot certificates` if yes
-CERT_DIR="$SCRIPT_DIR/letsencrypt/live/api.$NM_DOMAIN"
-
-echo "Setting up SSL certificates..."
-
-# preserve the env state
-RESTART_CADDY=false
-if [ -n "$(docker ps | grep caddy)" ]; then
-	echo "Caddy is running, stopping for now..."
-	RESTART_CADDY=true
-	docker-compose -f /root/docker-compose.yml stop caddy
-fi
-
-if [ "$INSTALL_TYPE" = "ce" ]; then
-	CERTBOT_PARAMS=$(cat <<EOF
-	certonly --standalone \
-		--non-interactive --agree-tos \
-		-m $NM_EMAIL \
-		-d api.$NM_DOMAIN \
-		-d broker.$NM_DOMAIN \
-		-d dashboard.$NM_DOMAIN \
-		-d turn.$NM_DOMAIN \
-		-d turnapi.$NM_DOMAIN
-EOF
-)
-elif [ "$INSTALL_TYPE" = "pro" ]; then
-	CERTBOT_PARAMS=$(cat <<EOF
-	certonly --standalone \
-		--non-interactive --expand --agree-tos \
-		-m $NM_EMAIL \
-		-d api.$NM_DOMAIN \
-		-d broker.$NM_DOMAIN \
-		-d dashboard.$NM_DOMAIN \
-		-d turn.$NM_DOMAIN \
-		-d turnapi.$NM_DOMAIN \
-		-d netmaker-exporter.$NM_DOMAIN \
-		-d grafana.$NM_DOMAIN \
-		-d prometheus.$NM_DOMAIN
-EOF
-)
-fi
-
-# generate an entrypoint for zerossl-certbot
-cat <<EOF >"$SCRIPT_DIR/certbot-entry.sh"
-#!/bin/sh
-# deps
-apk update
-apk add bash curl
-# zerossl
-wget -qO zerossl-bot.sh "https://github.com/zerossl/zerossl-bot/raw/master/zerossl-bot.sh"
-chmod +x zerossl-bot.sh
-# request the certs
-./zerossl-bot.sh "$CERTBOT_PARAMS"
-EOF
-
-chmod +x "$SCRIPT_DIR/certbot-entry.sh"
-
-# request certs
-sudo docker run -it --rm --name certbot \
-	-p 80:80 -p 443:443 \
-	-v "$SCRIPT_DIR/certbot-entry.sh:/opt/certbot/certbot-entry.sh" \
-	-v "$SCRIPT_DIR/letsencrypt:/etc/letsencrypt" \
-	--entrypoint "/opt/certbot/certbot-entry.sh" \
-	certbot/certbot
-
-# clean up
-rm "$SCRIPT_DIR/certbot-entry.sh"
-
-# check if successful
-if [ ! -f "$CERT_DIR"/fullchain.pem ]; then
-	# fallback to letsencrypt-certbot
-	sudo docker run -it --rm --name certbot \
-		-p 80:80 -p 443:443 \
-		-v "$SCRIPT_DIR/letsencrypt:/etc/letsencrypt" \
-		certbot/certbot $CERTBOT_PARAMS
-	if [ ! -f "$CERT_DIR"/fullchain.pem ]; then
-		echo "Missing file: $CERT_DIR/fullchain.pem"
-		echo "SSL certificates failed"
-		exit 1
-	fi
-fi
-
-# copy for mounting
-mkdir -p certs
-cp -L "$CERT_DIR/fullchain.pem" "$SCRIPT_DIR/certs/fullchain.pem"
-cp -L "$CERT_DIR/privkey.pem" "$SCRIPT_DIR/certs/privkey.pem"
-
-echo "SSL certificates ready"
-
-# preserve the env state
-if [ "$RESTART_CADDY" = true ]; then
-	echo "Starting Caddy..."
-	docker-compose -f /root/docker-compose.yml start caddy
-fi
-
-# install crontab
-ln -sfn "$SCRIPT_DIR"/nm-certs.sh /etc/cron.monthly/nm-certs.sh

+ 1 - 6
scripts/nm-quick.sh

@@ -310,7 +310,7 @@ save_config() { (
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
 		"TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
 		"DISABLE_REMOTE_IP_CHECK" "NETCLIENT_ENDPOINT_DETECTION" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
-		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT")
+		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE")
 	for name in "${toCopy[@]}"; do
 		save_config_item $name "${!name}"
 	done
@@ -759,7 +759,6 @@ install_netmaker() {
 		wget -qO "$SCRIPT_DIR"/Caddyfile "$CADDY_URL"
 		wget -qO "$SCRIPT_DIR"/netmaker.default.env "$BASE_URL/scripts/netmaker.default.env"
 		wget -qO "$SCRIPT_DIR"/mosquitto.conf "$BASE_URL/docker/mosquitto.conf"
-		wget -qO "$SCRIPT_DIR"/nm-certs.sh "$BASE_URL/scripts/nm-certs.sh"
 		wget -qO "$SCRIPT_DIR"/wait.sh "$BASE_URL/docker/wait.sh"
 	fi
 
@@ -770,10 +769,6 @@ install_netmaker() {
 	ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
 	save_config
 
-	# Fetch / update certs using certbot
-	chmod +x "$SCRIPT_DIR"/nm-certs.sh
-	"$SCRIPT_DIR"/nm-certs.sh
-
 	echo "Starting containers..."
 
 	# increase the timeouts

+ 1 - 1
scripts/nm-upgrade-0-17-1-to-0-19-0.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-LATEST="v0.21.0"
+LATEST="v0.21.1"
 INSTALL_PATH="/root"
 
 trap restore_old_netmaker_instructions

+ 1 - 1
scripts/nm-upgrade.sh

@@ -180,7 +180,7 @@ save_config() { (
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
 		"TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
 		"DISABLE_REMOTE_IP_CHECK" "NETCLIENT_ENDPOINT_DETECTION" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
-		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT")
+		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE")
 	for name in "${toCopy[@]}"; do
 		save_config_item $name "${!name}"
 	done

+ 21 - 73
servercfg/serverconf.go

@@ -85,17 +85,36 @@ func GetServerConfig() config.ServerConfig {
 	cfg.FrontendURL = GetFrontendURL()
 	cfg.Telemetry = Telemetry()
 	cfg.Server = GetServer()
-	cfg.StunList = GetStunListString()
 	cfg.Verbosity = GetVerbosity()
 	cfg.IsPro = "no"
 	if IsPro {
 		cfg.IsPro = "yes"
 	}
+	cfg.JwtValidityDuration = GetJwtValidityDuration()
+	cfg.RacAutoDisable = GetRacAutoDisable()
 
 	return cfg
 }
 
-// GetServerConfig - gets the server config into memory from file or env
+// GetJwtValidityDuration - returns the JWT validity duration in seconds
+func GetJwtValidityDuration() time.Duration {
+	var defaultDuration = time.Duration(24) * time.Hour
+	if os.Getenv("JWT_VALIDITY_DURATION") != "" {
+		t, err := strconv.Atoi(os.Getenv("JWT_VALIDITY_DURATION"))
+		if err != nil {
+			return defaultDuration
+		}
+		return time.Duration(t) * time.Second
+	}
+	return defaultDuration
+}
+
+// GetRacAutoDisable - returns whether the feature to autodisable RAC is enabled
+func GetRacAutoDisable() bool {
+	return os.Getenv("RAC_AUTO_DISABLE") == "true"
+}
+
+// GetServerInfo - gets the server config into memory from file or env
 func GetServerInfo() models.ServerConfig {
 	var cfg models.ServerConfig
 	cfg.Server = GetServer()
@@ -112,7 +131,6 @@ func GetServerInfo() models.ServerConfig {
 	cfg.Version = GetVersion()
 	cfg.IsPro = IsPro
 	cfg.StunPort = GetStunPort()
-	cfg.StunList = GetStunList()
 	cfg.TurnDomain = GetTurnHost()
 	cfg.TurnPort = GetTurnPort()
 	cfg.UseTurn = IsUsingTurn()
@@ -223,46 +241,6 @@ func GetAPIPort() string {
 	return apiport
 }
 
-// GetStunList - gets the stun servers
-func GetStunList() []models.StunServer {
-	stunList := []models.StunServer{
-		{
-			Domain: "stun1.netmaker.io",
-			Port:   3478,
-		},
-		{
-			Domain: "stun2.netmaker.io",
-			Port:   3478,
-		},
-	}
-	parsed := false
-	if os.Getenv("STUN_LIST") != "" {
-		stuns, err := parseStunList(os.Getenv("STUN_LIST"))
-		if err == nil {
-			parsed = true
-			stunList = stuns
-		}
-	}
-	if !parsed && config.Config.Server.StunList != "" {
-		stuns, err := parseStunList(config.Config.Server.StunList)
-		if err == nil {
-			stunList = stuns
-		}
-	}
-	return stunList
-}
-
-// GetStunList - gets the stun servers w/o parsing to struct
-func GetStunListString() string {
-	stunList := "stun1.netmaker.io:3478,stun2.netmaker.io:3478"
-	if os.Getenv("STUN_LIST") != "" {
-		stunList = os.Getenv("STUN_LIST")
-	} else if config.Config.Server.StunList != "" {
-		stunList = config.Config.Server.StunList
-	}
-	return stunList
-}
-
 // GetCoreDNSAddr - gets the core dns address
 func GetCoreDNSAddr() string {
 	addr, _ := GetPublicIP()
@@ -784,33 +762,3 @@ func GetEnvironment() string {
 	}
 	return ""
 }
-
-// parseStunList - turn string into slice of StunServers
-func parseStunList(stunString string) ([]models.StunServer, error) {
-	var err error
-	stunServers := []models.StunServer{}
-	stuns := strings.Split(stunString, ",")
-	if len(stuns) == 0 {
-		return stunServers, errors.New("no stun servers provided")
-	}
-	for _, stun := range stuns {
-		stun = strings.Trim(stun, " ")
-		stunInfo := strings.Split(stun, ":")
-		if len(stunInfo) != 2 {
-			continue
-		}
-		port, err := strconv.Atoi(stunInfo[1])
-		if err != nil || port == 0 {
-			continue
-		}
-		stunServers = append(stunServers, models.StunServer{
-			Domain: stunInfo[0],
-			Port:   port,
-		})
-
-	}
-	if len(stunServers) == 0 {
-		err = errors.New("no stun entries parsable")
-	}
-	return stunServers, err
-}

File diff suppressed because it is too large
+ 617 - 204
swagger.yml


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