Browse Source

Merge branch 'develop' into NET-39/nm-quick-refactor

# Conflicts:
#	docker/Caddyfile
#	scripts/nm-quick.sh
Tobias Cudnik 2 years ago
parent
commit
a97e27cd6c

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

@@ -31,6 +31,7 @@ body:
       label: Version
       description: What version are you running?
       options:
+        - v0.19.0
         - v0.18.7
         - v0.18.6
         - v0.18.5

+ 8 - 6
.github/workflows/deletedroplets.yml

@@ -23,7 +23,7 @@ jobs:
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           color: "#42f545"
           username: "GitHub Bot"
-          message: " ${{ github.event.workflow_run.name }} was successful"
+          message: "${{ github.respository }}: ${{ github.event.workflow_run.name }} was successful"
           file: ./results/results.log
       - name: discord server message
         uses: appleboy/discord-action@master
@@ -32,9 +32,10 @@ jobs:
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           color: "#42f545"
           username: "GitHub Bot"
-          message: "droplets from this workflow will be deleted in 15 min"
+          message: "droplets from this workflow (tag ${{ github.event.workflow_run.id }}-{{ $github.event.workflow_run.run_number }}) will be deleted in 15 min"
           file: ./server/serverinfo.txt
       - name: delete droplets
+        if: success() || failure()
         run: |
           sleep 15m
           curl -X GET \
@@ -59,18 +60,18 @@ jobs:
         with:
           webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
-          color: "#42f545"
+          color: "#990000"
           username: "GitHub Bot"
-          message: " ${{ github.event.workflow_run.name }} failed"
+          message: "${{ github.respository }}: ${{ github.event.workflow_run.name }} failed"
           file: ./results/results.log
       - name: discord server message
         uses: appleboy/discord-action@master
         with:
           webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
-          color: "#42f545"
+          color: "#990000"
           username: "GitHub Bot"
-          message: "droplets from this workflow will be deleted in 6 hours"
+          message: "droplets from this workflow (tag ${{ github.event.workflow_run.id }}-{{ $github.event.workflow_run.run_number }}) will be deleted in 6 hours"
           file: ./server/serverinfo.txt
       - name: discord error message
         uses: appleboy/discord-action@master
@@ -82,6 +83,7 @@ jobs:
           message: "errors from ${{ github.event.workflow_run.name }}"
           file: ./results/errors.log
       - name: delete droplets
+        if: success() || failure()
         run: |
           sleep 6h
           curl -X GET \

+ 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.16.2
+FROM alpine:3.17.2
 
 # add a c lib
 # set the working directory

+ 1 - 1
Dockerfile-quick

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

+ 1 - 1
README.md

@@ -17,7 +17,7 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.18.7-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.19.0-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" />

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

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.7
+    image: gravitl/netmaker:v0.19.0
     restart: on-failure
     volumes:
       - dnsconfig:/root/config/dnsconfig
@@ -36,7 +36,7 @@ services:
       - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.7
+    image: gravitl/netmaker-ui:v0.19.0
     depends_on:
       - netmaker
     links:

+ 22 - 0
compose/docker-compose.ee.yml

@@ -33,6 +33,12 @@ services:
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
       DEFAULT_PROXY_MODE: "off"
+      TURN_SERVER_HOST: "turn.NETMAKER_BASE_DOMAIN"
+      TURN_SERVER_API_HOST: "https://turnapi.NETMAKER_BASE_DOMAIN"
+      TURN_PORT: "3479"
+      TURN_USERNAME: "REPLACE_TURN_USERNAME"
+      TURN_PASSWORD: "REPLACE_TURN_PASSWORD"
+      USE_TURN: "true"
     ports:
       - "3478:3478/udp"
   netmaker-ui:
@@ -119,6 +125,21 @@ services:
       API_PORT: "8085"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       PROMETHEUS_HOST: https://prometheus.NETMAKER_BASE_DOMAIN
+  turn:
+    container_name: turn
+    image: gravitl/turnserver:v1.0.0
+    network_mode: "host"
+    volumes:
+      - turn_server:/etc/config
+    environment:
+      DEBUG_MODE: "off"
+      VERBOSITY: "1"
+      TURN_PORT: "3479"
+      TURN_API_PORT: "8089"
+      CORS_ALLOWED_ORIGIN: "*"
+      TURN_SERVER_HOST: "turn.NETMAKER_BASE_DOMAIN"
+      USERNAME: "REPLACE_TURN_USERNAME"
+      PASSWORD: "REPLACE_TURN_PASSWORD"
 volumes:
   caddy_data: {}
   caddy_conf: {}
@@ -127,3 +148,4 @@ volumes:
   mosquitto_logs: {}
   prometheus_data: {}
   grafana_data: {}
+  turn_server: {}

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

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

+ 23 - 0
compose/docker-compose.reference.yml

@@ -40,6 +40,12 @@ services:
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
       DEFAULT_PROXY_MODE: "off" # if ON, all new clients will enable proxy by default if OFF, all new clients will disable proxy by default, if AUTO, stick with the existing logic for NAT detection
+      TURN_SERVER_HOST: "turn.NETMAKER_BASE_DOMAIN" # domain for your turn server
+      TURN_SERVER_API_HOST: "https://turnapi.NETMAKER_BASE_DOMAIN" # domain of the turn api server
+      TURN_PORT: "3479" #  port to access turn server
+      TURN_USERNAME: "REPLACE_TURN_USERNAME"  # the username to set for turn api access
+      TURN_PASSWORD: "REPLACE_TURN_PASSWORD" #  the password to set for turn api access
+      USE_TURN: "true" #config for using turn, accepts either true/false
     ports:
       - "3478:3478/udp" # the stun port
   netmaker-ui:  # The Netmaker UI Component
@@ -89,6 +95,22 @@ services:
     ports:
       - "1883:1883"
       - "8883:8883"
+  turn:
+    container_name: turn
+    image: gravitl/turnserver:v1.0.0
+    network_mode: "host"
+    volumes:
+      - turn_server:/etc/config
+    environment:
+      DEBUG_MODE: "off"
+      VERBOSITY: "1"
+      TURN_PORT: "3479"
+      TURN_API_PORT: "8089"
+      CORS_ALLOWED_ORIGIN: "*"
+      TURN_SERVER_HOST: "turn.NETMAKER_BASE_DOMAIN"
+      USERNAME: "REPLACE_TURN_USERNAME"
+      PASSWORD: "REPLACE_TURN_PASSWORD"
+      USE_TURN: "true"
 volumes:
   caddy_data: {} # runtime data for caddy
   caddy_conf: {} # configuration file for Caddy
@@ -96,3 +118,4 @@ volumes:
   sqldata: {} # storage for embedded sqlite
   dnsconfig: {} # storage for coredns
   mosquitto_logs: {} # storage for mqtt logs
+  turn_server: {}

+ 24 - 0
compose/docker-compose.yml

@@ -30,6 +30,12 @@ services:
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
       STUN_PORT: "3478"
       DEFAULT_PROXY_MODE: "off"
+      TURN_SERVER_HOST: "turn.NETMAKER_BASE_DOMAIN"
+      TURN_SERVER_API_HOST: "https://turnapi.NETMAKER_BASE_DOMAIN"
+      TURN_PORT: "3479"
+      TURN_USERNAME: "REPLACE_TURN_USERNAME"
+      TURN_PASSWORD: "REPLACE_TURN_PASSWORD"
+      USE_TURN: "true"
     ports:
       - "3478:3478/udp"
   netmaker-ui:
@@ -46,6 +52,8 @@ services:
     image: caddy:2.6.2
     container_name: caddy
     restart: unless-stopped
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
     volumes:
       - /root/Caddyfile:/etc/caddy/Caddyfile
       - /root/fullchain.pem:/root/fullchain.pem
@@ -78,9 +86,25 @@ services:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
       - /root/wait.sh:/mosquitto/config/wait.sh
       - mosquitto_logs:/mosquitto/log
+  turn:
+    container_name: turn
+    image: gravitl/turnserver:v1.0.0
+    network_mode: "host"
+    volumes:
+      - turn_server:/etc/config
+    environment:
+      DEBUG_MODE: "off"
+      VERBOSITY: "1"
+      TURN_PORT: "3479"
+      TURN_API_PORT: "8089"
+      CORS_ALLOWED_ORIGIN: "*"
+      TURN_SERVER_HOST: "turn.NETMAKER_BASE_DOMAIN"
+      USERNAME: "REPLACE_TURN_USERNAME"
+      PASSWORD: "REPLACE_TURN_PASSWORD"
 volumes:
   caddy_data: {}
   caddy_conf: {}
   sqldata: {}
   dnsconfig: {}
   mosquitto_logs: {}
+  turn_server: {}

+ 6 - 0
config/config.go

@@ -76,6 +76,12 @@ type ServerConfig struct {
 	StunList             string    `yaml:"stun_list"`
 	Proxy                string    `yaml:"proxy"`
 	DefaultProxyMode     ProxyMode `yaml:"defaultproxymode"`
+	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"`
 }
 
 // ProxyMode - default proxy mode for server

+ 1 - 1
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //	Schemes: https
 //	BasePath: /
-//	Version: 0.18.7
+//	Version: 0.19.0
 //	Host: netmaker.io
 //
 //	Consumes:

+ 7 - 0
controllers/enrollmentkeys.go

@@ -153,6 +153,13 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	hostExists := false
+	// re-register host with turn just in case.
+	if servercfg.IsUsingTurn() {
+		err = logic.RegisterHostWithTurn(newHost.ID.String(), newHost.HostPass)
+		if err != nil {
+			logger.Log(0, "failed to register host with turn server: ", err.Error())
+		}
+	}
 	// check if host already exists
 	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")

+ 74 - 0
controllers/hosts.go

@@ -30,6 +30,7 @@ func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(deleteHostRelay))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/host", authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/host/{hostid}/signalpeer", authorize(true, false, "host", http.HandlerFunc(signalPeer))).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/auth-register/host", socketHandler)
 }
 
@@ -94,6 +95,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 		serverConf.MQUserName = hostID
 	}
+	key, keyErr := logic.RetrievePublicTrafficKey()
+	if keyErr != nil {
+		logger.Log(0, "error retrieving key:", keyErr.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	serverConf.TrafficKey = key
 	response := models.HostPull{
 		Host:         *host,
 		ServerConfig: serverConf,
@@ -450,6 +458,72 @@ func authenticateHost(response http.ResponseWriter, request *http.Request) {
 	response.Write(successJSONResponse)
 }
 
+// swagger:route POST /api/hosts/{hostid}/signalpeer signalPeer
+//
+// send signal to peer.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: signal
+func signalPeer(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	hostid := params["hostid"]
+	// confirm host exists
+	_, err := logic.GetHost(hostid)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to get host:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var signal models.Signal
+	w.Header().Set("Content-Type", "application/json")
+	err = json.NewDecoder(r.Body).Decode(&signal)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if signal.ToHostPubKey == "" || signal.TurnRelayEndpoint == "" {
+		msg := "insufficient data to signal peer"
+		logger.Log(0, r.Header.Get("user"), msg)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New(msg), "badrequest"))
+		return
+	}
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	// push the signal to host through mq
+	found := false
+	for _, hostI := range hosts {
+		if hostI.PublicKey.String() == signal.ToHostPubKey {
+			// found host publish message and break
+			found = true
+			err = mq.HostUpdate(&models.HostUpdate{
+				Action: models.SignalHost,
+				Host:   hostI,
+				Signal: signal,
+			})
+			if err != nil {
+				logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to publish signal to peer: "+err.Error()), "badrequest"))
+				return
+			}
+			break
+		}
+	}
+	if !found {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to signal, peer not found"), "badrequest"))
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(signal)
+}
+
 // swagger:route POST /api/hosts/keys host updateAllKeys
 //
 // Update keys for a network.

+ 12 - 0
docker/Caddyfile

@@ -32,6 +32,18 @@ https://stun.NETMAKER_BASE_DOMAIN {
 	reverse_proxy netmaker:3478
 }
 
+# TURN
+https://turn.NETMAKER_BASE_DOMAIN {
+	tls /root/fullchain.pem /root/privkey.pem
+	reverse_proxy host.docker.internal:3479
+}
+
+#TURN API
+https://turnapi.NETMAKER_BASE_DOMAIN {
+	tls /root/fullchain.pem /root/privkey.pem
+    reverse_proxy http://host.docker.internal:8089
+}
+
 # MQ
 wss://broker.NETMAKER_BASE_DOMAIN {
 	tls /root/fullchain.pem /root/privkey.pem

+ 10 - 0
docker/Caddyfile-EE

@@ -50,6 +50,16 @@ https://stun.NETMAKER_BASE_DOMAIN {
 	reverse_proxy netmaker:3478
 }
 
+# TURN
+https://turn.NETMAKER_BASE_DOMAIN {
+	reverse_proxy host.docker.internal:3479
+}
+
+#TURN API
+https://turnapi.NETMAKER_BASE_DOMAIN {
+        reverse_proxy http://host.docker.internal:8089
+}
+
 # MQ
 wss://broker.NETMAKER_BASE_DOMAIN {
         reverse_proxy ws://mq:8883

+ 1 - 1
docker/Dockerfile-go-builder

@@ -1,4 +1,4 @@
-FROM golang:1.19-alpine3.16 
+FROM golang:1.19.6-alpine3.17 
 ARG version 
 RUN apk add build-base
 WORKDIR /app

+ 4 - 3
go.mod

@@ -4,12 +4,12 @@ go 1.19
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/go-playground/validator/v10 v10.12.0
+	github.com/go-playground/validator/v10 v10.13.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
-	github.com/lib/pq v1.10.8
+	github.com/lib/pq v1.10.9
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
@@ -41,6 +41,7 @@ require (
 )
 
 require (
+	github.com/devilcove/httpclient v0.6.0
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/matryer/is v1.4.1
 	github.com/olekukonko/tablewriter v0.0.5
@@ -66,7 +67,7 @@ require (
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/go-version v1.6.0
 	github.com/josharian/native v1.0.0 // indirect
-	github.com/leodido/go-urn v1.2.2 // indirect
+	github.com/leodido/go-urn v1.2.3 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/mdlayher/genetlink v1.2.0 // indirect
 	github.com/mdlayher/netlink v1.6.0 // indirect

+ 8 - 7
go.sum

@@ -21,6 +21,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/devilcove/httpclient v0.6.0 h1:M5YAfHeNbu+0QxCiOCo/fKN+Hf0BtF/6aovu3NNgcKk=
+github.com/devilcove/httpclient v0.6.0/go.mod h1:ctrAO2gRgTT+GxtRdWBp2SMQ+vacuxXlbhmlM4oWhs8=
 github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
 github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -34,8 +36,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.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
-github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
+github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ=
+github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4=
 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=
@@ -75,10 +77,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
-github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
-github.com/lib/pq v1.10.8 h1:3fdt97i/cwSU83+E0hZTC/Xpc9mTZxc6UWSCRcSbxiE=
-github.com/lib/pq v1.10.8/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
+github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
@@ -118,7 +120,6 @@ github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQw
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=

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

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       containers:
       - name: netclient
-        image: gravitl/netclient:v0.18.7
+        image: gravitl/netclient:v0.19.0
         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.18.7
+        image: gravitl/netclient:v0.19.0
         env:
         - name: TOKEN
           value: "TOKEN_VALUE"

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

@@ -79,7 +79,7 @@ spec:
           value: "Kubernetes"
         - name: VERBOSITY
           value: "3"
-        image: gravitl/netmaker:v0.18.7
+        image: gravitl/netmaker:v0.19.0
         imagePullPolicy: Always
         name: netmaker
         ports:

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

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

+ 2 - 3
logic/enrollmentkey.go

@@ -9,7 +9,6 @@ import (
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
 // EnrollmentErrors - struct for holding EnrollmentKey error messages
@@ -190,9 +189,9 @@ func getUniqueEnrollmentID() (string, error) {
 	if err != nil {
 		return "", err
 	}
-	newID := ncutils.MakeRandomString(models.EnrollmentKeyLength)
+	newID := RandomString(models.EnrollmentKeyLength)
 	for _, ok := currentKeys[newID]; ok; {
-		newID = ncutils.MakeRandomString(models.EnrollmentKeyLength)
+		newID = RandomString(models.EnrollmentKeyLength)
 	}
 	return newID, nil
 }

+ 3 - 0
logic/gateway.go

@@ -25,6 +25,9 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	if host.OS != "linux" { // support for other OS to be added
 		return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
 	}
+	if host.FirewallInUse == models.FIREWALL_NONE {
+		return models.Node{}, errors.New("firewall is not supported for egress gateways")
+	}
 	for i := len(gateway.Ranges) - 1; i >= 0; i-- {
 		if gateway.Ranges[i] == "::/0" {
 			logger.Log(0, "currently IPv6 internet gateways are not supported", gateway.Ranges[i])

+ 70 - 0
logic/hosts.go

@@ -1,12 +1,17 @@
 package logic
 
 import (
+	"crypto/md5"
+	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"log"
+	"net/http"
 	"sort"
+	"strconv"
 
+	"github.com/devilcove/httpclient"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -92,6 +97,13 @@ func CreateHost(h *models.Host) error {
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 		return ErrHostExists
 	}
+	if servercfg.IsUsingTurn() {
+		err = RegisterHostWithTurn(h.ID.String(), h.HostPass)
+		if err != nil {
+			logger.Log(0, "failed to register host with turn server: ", err.Error())
+		}
+	}
+
 	// encrypt that password so we never see it
 	hash, err := bcrypt.GenerateFromPassword([]byte(h.HostPass), 5)
 	if err != nil {
@@ -207,11 +219,18 @@ func RemoveHost(h *models.Host) error {
 	if len(h.Nodes) > 0 {
 		return fmt.Errorf("host still has associated nodes")
 	}
+	if servercfg.IsUsingTurn() {
+		DeRegisterHostWithTurn(h.ID.String())
+	}
+
 	return database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String())
 }
 
 // RemoveHostByID - removes a given host by id from server
 func RemoveHostByID(hostID string) error {
+	if servercfg.IsUsingTurn() {
+		DeRegisterHostWithTurn(hostID)
+	}
 	return database.DeleteRecord(database.HOSTS_TABLE_NAME, hostID)
 }
 
@@ -435,6 +454,57 @@ func GetHostByNodeID(id string) *models.Host {
 	return nil
 }
 
+// ConvHostPassToHash - converts password to md5 hash
+func ConvHostPassToHash(hostPass string) string {
+	return fmt.Sprintf("%x", md5.Sum([]byte(hostPass)))
+}
+
+// RegisterHostWithTurn - registers the host with the given turn server
+func RegisterHostWithTurn(hostID, hostPass string) error {
+	auth := servercfg.GetTurnUserName() + ":" + servercfg.GetTurnPassword()
+	api := httpclient.JSONEndpoint[models.SuccessResponse, models.ErrorResponse]{
+		URL:           servercfg.GetTurnApiHost(),
+		Route:         "/api/v1/host/register",
+		Method:        http.MethodPost,
+		Authorization: fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))),
+		Data: models.HostTurnRegister{
+			HostID:       hostID,
+			HostPassHash: ConvHostPassToHash(hostPass),
+		},
+		Response:      models.SuccessResponse{},
+		ErrorResponse: models.ErrorResponse{},
+	}
+	_, errData, err := api.GetJSON(models.SuccessResponse{}, models.ErrorResponse{})
+	if err != nil {
+		if errors.Is(err, httpclient.ErrStatus) {
+			logger.Log(1, "error server status", strconv.Itoa(errData.Code), errData.Message)
+		}
+		return err
+	}
+	return nil
+}
+
+// DeRegisterHostWithTurn - to be called when host need to be deregistered from a turn server
+func DeRegisterHostWithTurn(hostID string) error {
+	auth := servercfg.GetTurnUserName() + ":" + servercfg.GetTurnPassword()
+	api := httpclient.JSONEndpoint[models.SuccessResponse, models.ErrorResponse]{
+		URL:           servercfg.GetTurnApiHost(),
+		Route:         fmt.Sprintf("/api/v1/host/deregister?host_id=%s", hostID),
+		Method:        http.MethodPost,
+		Authorization: fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))),
+		Response:      models.SuccessResponse{},
+		ErrorResponse: models.ErrorResponse{},
+	}
+	_, errData, err := api.GetJSON(models.SuccessResponse{}, models.ErrorResponse{})
+	if err != nil {
+		if errors.Is(err, httpclient.ErrStatus) {
+			logger.Log(1, "error server status", strconv.Itoa(errData.Code), errData.Message)
+		}
+		return err
+	}
+	return nil
+}
+
 // SortApiHosts - Sorts slice of ApiHosts by their ID alphabetically with numbers first
 func SortApiHosts(unsortedHosts []models.ApiHost) {
 	sort.Slice(unsortedHosts, func(i, j int) bool {

+ 1 - 4
logic/jwts.go

@@ -19,10 +19,7 @@ var jwtSecretKey []byte
 func SetJWTSecret() {
 	currentSecret, jwtErr := FetchJWTSecret()
 	if jwtErr != nil {
-		newValue, err := GenerateCryptoString(64)
-		if err != nil {
-			logger.FatalLog("something went wrong when generating JWT signature")
-		}
+		newValue := RandomString(64)
 		jwtSecretKey = []byte(newValue) // 512 bit random password
 		if err := StoreJWTSecret(string(jwtSecretKey)); err != nil {
 			logger.FatalLog("something went wrong when configuring JWT authentication")

+ 1 - 0
logic/peers.go

@@ -97,6 +97,7 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 					Proxy:            peerHost.ProxyEnabled,
 					PublicListenPort: int32(GetPeerListenPort(peerHost)),
 					ProxyListenPort:  GetProxyListenPort(peerHost),
+					NatType:          peerHost.NatType,
 				}
 			}
 

+ 9 - 26
logic/util.go

@@ -2,11 +2,10 @@
 package logic
 
 import (
-	crand "crypto/rand"
+	"crypto/rand"
+	"encoding/base32"
 	"encoding/base64"
 	"encoding/json"
-	"math/big"
-	"math/rand"
 	"net"
 	"os"
 	"strings"
@@ -14,6 +13,7 @@ import (
 
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 )
 
 // IsBase64 - checks if a string is in base64 format
@@ -68,32 +68,15 @@ func SetNetworkNodesLastModified(networkName string) error {
 	return nil
 }
 
-// GenerateCryptoString - generates random string of n length
-func GenerateCryptoString(n int) (string, error) {
-	const chars = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
-	ret := make([]byte, n)
-	for i := range ret {
-		num, err := crand.Int(crand.Reader, big.NewInt(int64(len(chars))))
-		if err != nil {
-			return "", err
-		}
-		ret[i] = chars[num.Int64()]
-	}
-
-	return string(ret), nil
-}
-
 // RandomString - returns a random string in a charset
 func RandomString(length int) string {
-	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-
-	var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
-
-	b := make([]byte, length)
-	for i := range b {
-		b[i] = charset[seededRand.Intn(len(charset))]
+	randombytes := make([]byte, length)
+	_, err := rand.Read(randombytes)
+	if err != nil {
+		logger.Log(0, "random string", err.Error())
+		return ""
 	}
-	return string(b)
+	return base32.StdEncoding.EncodeToString(randombytes)[:length]
 }
 
 // StringSliceContains - sees if a string slice contains a string element

+ 5 - 5
netclient/ncutils/netclientutils_test.go → logic/util_test.go

@@ -1,4 +1,4 @@
-package ncutils
+package logic
 
 import (
 	"strings"
@@ -7,10 +7,10 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestMakeRandomString(t *testing.T) {
+func TestRandomString(t *testing.T) {
 	for testCase := 0; testCase < 100; testCase++ {
 		for size := 2; size < 2058; size++ {
-			if length := len(MakeRandomString(size)); length != size {
+			if length := len(RandomString(size)); length != size {
 				t.Fatalf("expected random string of size %d, got %d instead", size, length)
 			}
 		}
@@ -18,9 +18,9 @@ func TestMakeRandomString(t *testing.T) {
 }
 
 func TestMakeRandomStringValid(t *testing.T) {
-	lengthStr := MakeRandomString(10)
+	lengthStr := RandomString(10)
 	assert.Equal(t, len(lengthStr), 10)
-	validMqID := MakeRandomString(23)
+	validMqID := RandomString(23)
 	assert.False(t, strings.Contains(validMqID, "#"))
 	assert.False(t, strings.Contains(validMqID, "!"))
 	assert.False(t, strings.Contains(validMqID, "\""))

+ 1 - 1
main.go

@@ -27,7 +27,7 @@ import (
 	stunserver "github.com/gravitl/netmaker/stun-server"
 )
 
-var version = "v0.18.7"
+var version = "v0.19.0"
 
 // Start DB Connection and start API Request Handler
 func main() {

+ 20 - 0
models/host.go

@@ -101,6 +101,8 @@ func ParseBool(s string) bool {
 type HostMqAction string
 
 const (
+	// SignalHost - const for host signal action
+	SignalHost = "SIGNAL_HOST"
 	// UpdateHost - constant for host update action
 	UpdateHost = "UPDATE_HOST"
 	// DeleteHost - constant for host delete action
@@ -113,6 +115,8 @@ const (
 	RequestAck = "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"
 	// UpdateKeys - update wireguard private/public keys
 	UpdateKeys = "UPDATE_KEYS"
 )
@@ -122,6 +126,22 @@ type HostUpdate struct {
 	Action HostMqAction
 	Host   Host
 	Node   Node
+	Signal Signal
+}
+
+// HostTurnRegister - struct for host turn registration
+type HostTurnRegister struct {
+	HostID       string `json:"host_id"`
+	HostPassHash string `json:"host_pass_hash"`
+}
+
+// Signal - struct for signalling peer
+type Signal struct {
+	Server            string `json:"server"`
+	FromHostPubKey    string `json:"from_host_pubkey"`
+	TurnRelayEndpoint string `json:"turn_relay_addr"`
+	ToHostPubKey      string `json:"to_host_pubkey"`
+	Reply             bool   `json:"reply"`
 }
 
 // RegisterMsg - login message struct for hosts to join via SSO login

+ 4 - 5
models/proxy.go

@@ -40,17 +40,16 @@ type PeerConf struct {
 	ProxyListenPort  int          `json:"proxy_listen_port"`
 	IsExtClient      bool         `json:"is_ext_client"`
 	Address          net.IP       `json:"address"`
-	ExtInternalIp    net.IP       `json:"ext_internal_ip"`
 	IsRelayed        bool         `json:"is_relayed"`
 	RelayedTo        *net.UDPAddr `json:"relayed_to"`
+	NatType          string       `json:"nat_type"`
 }
 
 // ProxyManagerPayload - struct for proxy manager payload
 type ProxyManagerPayload struct {
-	Action        ProxyAction `json:"action"`
-	InterfaceName string      `json:"interface_name"`
-	Server        string      `json:"server"`
-	//WgAddr          string                 `json:"wg_addr"`
+	Action          ProxyAction            `json:"action"`
+	InterfaceName   string                 `json:"interface_name"`
+	Server          string                 `json:"server"`
 	Peers           []wgtypes.PeerConfig   `json:"peers"`
 	PeerMap         map[string]PeerConf    `json:"peer_map"`
 	IsIngress       bool                   `json:"is_ingress"`

+ 3 - 0
models/structs.go

@@ -240,6 +240,9 @@ type ServerConfig struct {
 	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"`
 }
 
 // User.NameInCharset - returns if name is in charset below or not

+ 9 - 0
mq/handlers.go

@@ -163,6 +163,15 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 			return
 		}
 		sendPeerUpdate = true
+	case models.RegisterWithTurn:
+		if servercfg.IsUsingTurn() {
+			err = logic.RegisterHostWithTurn(hostUpdate.Host.ID.String(), hostUpdate.Host.HostPass)
+			if err != nil {
+				logger.Log(0, "failed to register host with turn server: ", err.Error())
+				return
+			}
+		}
+
 	}
 
 	if sendPeerUpdate {

+ 2 - 2
mq/mq.go

@@ -8,7 +8,7 @@ import (
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/servercfg"
 )
 
@@ -27,7 +27,7 @@ var mqclient mqtt.Client
 func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 	broker, _ := servercfg.GetMessageQueueEndpoint()
 	opts.AddBroker(broker)
-	id := ncutils.MakeRandomString(23)
+	id := logic.RandomString(23)
 	opts.ClientID = id
 	opts.SetUsername(user)
 	opts.SetPassword(password)

+ 0 - 14
netclient/ncutils/netclientutils.go

@@ -2,7 +2,6 @@ package ncutils
 
 import (
 	"bytes"
-	"crypto/rand"
 	"encoding/gob"
 )
 
@@ -32,16 +31,3 @@ func ConvertBytesToKey(data []byte) (*[32]byte, error) {
 	}
 	return result, err
 }
-
-// MakeRandomString - generates a random string of len n
-func MakeRandomString(n int) string {
-	const validChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-	result := make([]byte, n)
-	if _, err := rand.Reader.Read(result); err != nil {
-		return ""
-	}
-	for i, b := range result {
-		result[i] = validChars[b%byte(len(validChars))]
-	}
-	return string(result)
-}

+ 10 - 4
release.md

@@ -1,14 +1,20 @@
-# Netmaker v0.18.7
+# Netmaker v0.19.0
 
 ## whats new
+- TURN
+- dependency updates
 - internet gateways (0.0.0.0/0) for egress
 - deprecated editing of network parameters
 - allow extra ips for extclient (not enabled in UI)
     
 ## whats fixed
-- nm-quick - determine lastest version from releases
-- wireguard public/private key rotation
-- ee-license checks
+- unbiased random string
+- get traffic keys on pull
+- CI updates
+- install/update script updates
+- firewall checks
+-  
+- 
 
 ## known issues
 - Caddy does not handle netmaker exporter well for EE

+ 51 - 1
scripts/nm-quick.sh

@@ -453,6 +453,8 @@ set_install_vars() {
 	echo "                api.$NETMAKER_BASE_DOMAIN"
 	echo "             broker.$NETMAKER_BASE_DOMAIN"
 	echo "               stun.$NETMAKER_BASE_DOMAIN"
+	echo "               turn.$NETMAKER_BASE_DOMAIN"
+	echo "               turnapi.$NETMAKER_BASE_DOMAIN"
 
 	if [ "$INSTALL_TYPE" = "ee" ]; then
 		echo "         prometheus.$NETMAKER_BASE_DOMAIN"
@@ -566,6 +568,51 @@ set_install_vars() {
 		done
 	fi
 
+	unset GET_TURN_USERNAME
+	unset GET_TURN_PASSWORD
+	unset CONFIRM_TURN_PASSWORD
+	echo "Enter Credentials For TURN..."
+	if [ -z $AUTO_BUILD ]; then
+		read -p "TURN Username (click 'enter' to use 'netmaker'): " GET_TURN_USERNAME
+	fi
+	if [ -z "$GET_TURN_USERNAME" ]; then
+	echo "using default username for mq"
+	TURN_USERNAME="netmaker"
+	else
+	TURN_USERNAME="$GET_TURN_USERNAME"
+	fi
+
+	TURN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+
+	if [ -z $AUTO_BUILD ]; then
+		select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+			case $REPLY in
+			1)
+			echo "using random password for turn"
+			break
+			;;
+			2)
+			while true
+			do
+				echo "Enter your Password For TURN: "
+				read -s GET_TURN_PASSWORD
+				echo "Enter your password again to confirm: "
+				read -s CONFIRM_TURN_PASSWORD
+				if [ ${GET_TURN_PASSWORD} != ${CONFIRM_TURN_PASSWORD} ]; then
+					echo "wrong password entered, try again..."
+					continue
+				fi
+				TURN_PASSWORD="$GET_TURN_PASSWORD"
+				echo "TURN Password Saved Successfully!!"
+				break
+			done
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
+
 	wait_seconds 2
 
 	echo "-----------------------------------------------------------------"
@@ -623,8 +670,11 @@ install_netmaker() {
 	sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
 	sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
 	sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
-	sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
 	sed -i "s/REPLACE_MQ_USERNAME/$MQ_USERNAME/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_TURN_USERNAME/$TURN_USERNAME/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_TURN_PASSWORD/$TURN_PASSWORD/g" /root/docker-compose.yml
+
 	if [ "$INSTALL_TYPE" = "ee" ]; then
 		sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
 		sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml

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

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-LATEST="v0.18.6"
+LATEST="v0.19.0"
 INSTALL_PATH="/root"
 
 trap restore_old_netmaker_instructions
@@ -260,10 +260,12 @@ collect_server_settings() {
   done
 
   STUN_DOMAIN="stun.$SERVER_NAME"
+  TURN_DOMAIN="turn.$SERVER_NAME"
+  TURNAPI_DOMAIN="turnapi.$SERVER_NAME"
   echo "-----------------------------------------------------"
-  echo "Netmaker v0.18 requires a new DNS entry for $STUN_DOMAIN."
-  echo "Please confirm this is added to your DNS provider before continuing"
-  echo "(note: this is not required if using an nip.io address)"
+  echo "Netmaker v0.19 requires new DNS entries for $STUN_DOMAIN, $TURN_DOMAIN, and $TURNAPI_DOMAIN."
+  echo "Please confirm this is added to your DNS provider before continuing."
+  echo "You can skip this step if using a wildcard DNS entry (e.g. *.$SERVER_NAME) or a nip.io address."
   echo "-----------------------------------------------------"
   confirm
 }
@@ -337,6 +339,16 @@ cat <<EOT >> $INSTALL_PATH/Caddyfile
 https://$STUN_DOMAIN {
   reverse_proxy netmaker:3478
 }
+
+# TURN
+https://$TURN_DOMAIN {
+  reverse_proxy netmaker:3479
+}
+
+#TURN API
+https://turnapi.$TURNAPI_DOMAIN {
+        reverse_proxy http://host.docker.internal:8089
+}
 EOT
 
 }
@@ -385,10 +397,55 @@ set_mq_credentials() {
   done
 }
 
+# set_turn_credentials - sets mq credentials
+set_turn_credentials() {
+
+  unset GET_TURN_USERNAME
+  unset GET_TURN_PASSWORD
+  unset CONFIRM_TURN_PASSWORD
+  echo "Enter Credentials For TURN..."
+  read -p "TURN Username (click 'enter' to use 'netmaker'): " GET_TURN_USERNAME
+  if [ -z "$GET_TURN_USERNAME" ]; then
+    echo "using default username for turn"
+    TURN_USERNAME="netmaker"
+  else
+    TURN_USERNAME="$GET_TURN_USERNAME"
+  fi
+
+  select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+    case $REPLY in
+    1)
+    echo "generating random password for TURN"
+    TURN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+    break
+    ;;      
+      2)
+    while true
+      do
+          echo "Enter your Password For TURN: " 
+          read -s GET_TURN_PASSWORD
+          echo "Enter your password again to confirm: "
+          read -s CONFIRM_TURN_PASSWORD
+          if [ ${GET_TURN_PASSWORD} != ${CONFIRM_TURN_PASSWORD} ]; then
+              echo "wrong password entered, try again..."
+              continue
+          fi
+      TURN_PASSWORD="$GET_TURN_PASSWORD"
+          echo "TURN Password Saved Successfully!!"
+          break
+      done
+        break
+        ;;
+      *) echo "invalid option $REPLY";;
+    esac
+  done
+}
+
 # set_compose - set compose file with proper values
 set_compose() {
 
   set_mq_credentials
+  set_turn_credentials
 
   echo "retrieving updated wait script and mosquitto conf"  
   rm $INSTALL_PATH/wait.sh
@@ -407,21 +464,38 @@ set_compose() {
 
   STUN_PORT=3478
 
-  # RELEASE_REPLACE - Use this once release is ready
-
-  #sed -i "s/v0.17.1/v0.18.6/g" /root/docker-compose.yml
   yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i $INSTALL_PATH/docker-compose.yml
   yq ".services.netmaker.environment += {\"BROKER_ENDPOINT\": \"wss://$BROKER_NAME\"}" -i $INSTALL_PATH/docker-compose.yml  
   yq ".services.netmaker.environment += {\"SERVER_BROKER_ENDPOINT\": \"ws://mq:1883\"}" -i $INSTALL_PATH/docker-compose.yml  
   yq ".services.netmaker.environment += {\"STUN_LIST\": \"$STUN_DOMAIN:$STUN_PORT,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302\"}" -i $INSTALL_PATH/docker-compose.yml  
-  yq ".services.netmaker.environment += {\"MQ_PASSWORD\": \"$MQ_PASSWORD\"}" -i $INSTALL_PATH/docker-compose.yml  
   yq ".services.netmaker.environment += {\"MQ_USERNAME\": \"$MQ_USERNAME\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"MQ_PASSWORD\": \"$MQ_PASSWORD\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"TURN_SERVER_HOST\": \"turn.$SERVER_NAME\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"TURN_SERVER_API_HOST\": \"turnapi.$SERVER_NAME\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"TURN_USERNAME\": \"$TURN_USERNAME\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"TURN_PASSWORD\": \"$TURN_PASSWORD\"}" -i $INSTALL_PATH/docker-compose.yml  
   yq ".services.netmaker.environment += {\"STUN_PORT\": \"$STUN_PORT\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"TURN_PORT\": \"3479\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.netmaker.environment += {\"USE_TURN\": \"true\"}" -i $INSTALL_PATH/docker-compose.yml  
   yq ".services.netmaker.ports += \"3478:3478/udp\"" -i $INSTALL_PATH/docker-compose.yml
 
-  yq ".services.mq.environment += {\"MQ_PASSWORD\": \"$MQ_PASSWORD\"}" -i $INSTALL_PATH/docker-compose.yml  
   yq ".services.mq.environment += {\"MQ_USERNAME\": \"$MQ_USERNAME\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.mq.environment += {\"MQ_PASSWORD\": \"$MQ_PASSWORD\"}" -i $INSTALL_PATH/docker-compose.yml  
 
+  yq ".services.turn += {\"container_name\": \"turn\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn += {\"image\": \"gravitl/turnserver:v1.0.0\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn += {\"network_mode\": \"host\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.volumes += {\"turn_server:/etc/config\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"DEBUG_MODE\": \"off\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"VERBOSITY\": \"1\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"TURN_PORT\": \"3479\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"TURN_API_PORT\": \"8089\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"CORS_ALLOWED_ORIGIN\": \"*\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"TURN_SERVER_HOST\": \"$TURN_DOMAIN\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"TURN_USERNAME\": \"$TURN_USERNAME\"}" -i $INSTALL_PATH/docker-compose.yml  
+  yq ".services.turn.environment += {\"TURN_PASSWORD\": \"$TURN_PASSWORD\"}" -i $INSTALL_PATH/docker-compose.yml  
+
+  yq ".services.volumes += {\".turn_server\": \"{}\"}" -i $INSTALL_PATH/docker-compose.yml  
 
   #remove unnecessary ports
   yq eval 'del( .services.netmaker.ports[] | select(. == "51821*") )' -i $INSTALL_PATH/docker-compose.yml

+ 75 - 3
servercfg/serverconf.go

@@ -107,10 +107,44 @@ func GetServerInfo() models.ServerConfig {
 	cfg.Is_EE = Is_EE
 	cfg.StunPort = GetStunPort()
 	cfg.StunList = GetStunList()
-
+	cfg.TurnDomain = GetTurnHost()
+	cfg.TurnPort = GetTurnPort()
+	cfg.UseTurn = IsUsingTurn()
 	return cfg
 }
 
+// GetTurnHost - fetches the turn host domain
+func GetTurnHost() string {
+	turnServer := ""
+	if os.Getenv("TURN_SERVER_HOST") != "" {
+		turnServer = os.Getenv("TURN_SERVER_HOST")
+	} else if config.Config.Server.TurnServer != "" {
+		turnServer = config.Config.Server.TurnServer
+	}
+	return turnServer
+}
+
+// IsUsingTurn - check if server has turn configured
+func IsUsingTurn() (b bool) {
+	if os.Getenv("USE_TURN") != "" {
+		b = os.Getenv("USE_TURN") == "true"
+	} else {
+		b = config.Config.Server.UseTurn
+	}
+	return
+}
+
+// GetTurnApiHost - fetches the turn api host domain
+func GetTurnApiHost() string {
+	turnApiServer := ""
+	if os.Getenv("TURN_SERVER_API_HOST") != "" {
+		turnApiServer = os.Getenv("TURN_SERVER_API_HOST")
+	} else if config.Config.Server.TurnApiServer != "" {
+		turnApiServer = config.Config.Server.TurnApiServer
+	}
+	return turnApiServer
+}
+
 // GetFrontendURL - gets the frontend url
 func GetFrontendURL() string {
 	var frontend = ""
@@ -186,11 +220,11 @@ func GetAPIPort() string {
 // GetStunList - gets the stun servers
 func GetStunList() []models.StunServer {
 	stunList := []models.StunServer{
-		models.StunServer{
+		{
 			Domain: "stun1.netmaker.io",
 			Port:   3478,
 		},
-		models.StunServer{
+		{
 			Domain: "stun2.netmaker.io",
 			Port:   3478,
 		},
@@ -642,6 +676,44 @@ func GetStunPort() int {
 	return port
 }
 
+// GetTurnPort - Get the port to run the turn server on
+func GetTurnPort() int {
+	port := 3479 //default
+	if os.Getenv("TURN_PORT") != "" {
+		portInt, err := strconv.Atoi(os.Getenv("TURN_PORT"))
+		if err == nil {
+			port = portInt
+		}
+	} else if config.Config.Server.TurnPort != 0 {
+		port = config.Config.Server.TurnPort
+	}
+	return port
+}
+
+// GetTurnUserName - fetches the turn server username
+func GetTurnUserName() string {
+	userName := ""
+	if os.Getenv("TURN_USERNAME") != "" {
+		userName = os.Getenv("TURN_USERNAME")
+	} else {
+		userName = config.Config.Server.TurnUserName
+	}
+	return userName
+
+}
+
+// GetTurnPassword - fetches the turn server password
+func GetTurnPassword() string {
+	pass := ""
+	if os.Getenv("TURN_PASSWORD") != "" {
+		pass = os.Getenv("TURN_PASSWORD")
+	} else {
+		pass = config.Config.Server.TurnPassword
+	}
+	return pass
+
+}
+
 // IsProxyEnabled - is proxy on or off
 func IsProxyEnabled() bool {
 	var enabled = false //default

+ 1 - 1
swagger.yaml

@@ -704,7 +704,7 @@ info:
 
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
-    version: 0.18.7
+    version: 0.19.0
 paths:
     /api/dns:
         get: