diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 308324d0..e5c3ce1b 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.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 diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index fc310fee..6c37084a 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.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 \ diff --git a/Dockerfile b/Dockerfile index 26009c3a..f75af9b9 100644 --- a/Dockerfile +++ b/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 diff --git a/Dockerfile-quick b/Dockerfile-quick index 08f5bc74..cfafa62c 100644 --- a/Dockerfile-quick +++ b/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 diff --git a/README.md b/README.md index 37467723..fc0aaf8b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@

- + diff --git a/compose/docker-compose-emqx.yml b/compose/docker-compose-emqx.yml index e7899e18..e06245f6 100644 --- a/compose/docker-compose-emqx.yml +++ b/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: diff --git a/compose/docker-compose.ee.yml b/compose/docker-compose.ee.yml index 80139f8d..8ce3f8aa 100644 --- a/compose/docker-compose.ee.yml +++ b/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: {} diff --git a/compose/docker-compose.netclient.yml b/compose/docker-compose.netclient.yml index 2c949fdf..1a83b700 100644 --- a/compose/docker-compose.netclient.yml +++ b/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 diff --git a/compose/docker-compose.reference.yml b/compose/docker-compose.reference.yml index b34d3cc3..ba7aa12d 100644 --- a/compose/docker-compose.reference.yml +++ b/compose/docker-compose.reference.yml @@ -40,6 +40,12 @@ services: AZURE_TENANT: "" # "" 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: {} \ No newline at end of file diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 20374c36..d4a377ff 100644 --- a/compose/docker-compose.yml +++ b/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: {} diff --git a/config/config.go b/config/config.go index 9c3e45fa..8e6f1302 100644 --- a/config/config.go +++ b/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 diff --git a/controllers/docs.go b/controllers/docs.go index c6b05d4e..fae0208f 100644 --- a/controllers/docs.go +++ b/controllers/docs.go @@ -10,7 +10,7 @@ // // Schemes: https // BasePath: / -// Version: 0.18.7 +// Version: 0.19.0 // Host: netmaker.io // // Consumes: diff --git a/controllers/enrollmentkeys.go b/controllers/enrollmentkeys.go index 32a08421..2e62cff1 100644 --- a/controllers/enrollmentkeys.go +++ b/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") diff --git a/controllers/hosts.go b/controllers/hosts.go index 52187c5e..833caa44 100644 --- a/controllers/hosts.go +++ b/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. diff --git a/docker/Caddyfile b/docker/Caddyfile index 0aeeeb9f..390fe139 100644 --- a/docker/Caddyfile +++ b/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 diff --git a/docker/Caddyfile-EE b/docker/Caddyfile-EE index aec0d67e..61f39bf8 100644 --- a/docker/Caddyfile-EE +++ b/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 diff --git a/docker/Dockerfile-go-builder b/docker/Dockerfile-go-builder index a9604eaf..08b2d7a2 100644 --- a/docker/Dockerfile-go-builder +++ b/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 diff --git a/go.mod b/go.mod index 825f3364..ae039712 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 6bf0f609..f00ea4ef 100644 --- a/go.sum +++ b/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= diff --git a/k8s/client/netclient-daemonset.yaml b/k8s/client/netclient-daemonset.yaml index 2416fd77..a2997ab2 100644 --- a/k8s/client/netclient-daemonset.yaml +++ b/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" diff --git a/k8s/client/netclient.yaml b/k8s/client/netclient.yaml index 3f57229a..8bb1006b 100644 --- a/k8s/client/netclient.yaml +++ b/k8s/client/netclient.yaml @@ -28,7 +28,7 @@ spec: # - "" containers: - name: netclient - image: gravitl/netclient:v0.18.7 + image: gravitl/netclient:v0.19.0 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/server/netmaker-server.yaml b/k8s/server/netmaker-server.yaml index 94032acb..93068fd5 100644 --- a/k8s/server/netmaker-server.yaml +++ b/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: diff --git a/k8s/server/netmaker-ui.yaml b/k8s/server/netmaker-ui.yaml index 58d4ff97..21338664 100644 --- a/k8s/server/netmaker-ui.yaml +++ b/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: diff --git a/logic/enrollmentkey.go b/logic/enrollmentkey.go index ec1d3c8f..9888f3c3 100644 --- a/logic/enrollmentkey.go +++ b/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 } diff --git a/logic/gateway.go b/logic/gateway.go index 991053a0..b6e71041 100644 --- a/logic/gateway.go +++ b/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]) diff --git a/logic/hosts.go b/logic/hosts.go index 5a21f6dd..a8e58407 100644 --- a/logic/hosts.go +++ b/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 { diff --git a/logic/jwts.go b/logic/jwts.go index 4ac722a6..0f355a87 100644 --- a/logic/jwts.go +++ b/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") diff --git a/logic/peers.go b/logic/peers.go index 3d64f019..c31330f6 100644 --- a/logic/peers.go +++ b/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, } } diff --git a/logic/util.go b/logic/util.go index 0724bca8..52ed902a 100644 --- a/logic/util.go +++ b/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 diff --git a/netclient/ncutils/netclientutils_test.go b/logic/util_test.go similarity index 79% rename from netclient/ncutils/netclientutils_test.go rename to logic/util_test.go index 526c3924..9dec6b81 100644 --- a/netclient/ncutils/netclientutils_test.go +++ b/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, "\"")) diff --git a/main.go b/main.go index fafa8540..480d3f30 100644 --- a/main.go +++ b/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() { diff --git a/models/host.go b/models/host.go index 8068390f..51c6d43e 100644 --- a/models/host.go +++ b/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 diff --git a/models/proxy.go b/models/proxy.go index 8d83a3aa..0a33d25f 100644 --- a/models/proxy.go +++ b/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"` diff --git a/models/structs.go b/models/structs.go index 1fb056e3..9b95b340 100644 --- a/models/structs.go +++ b/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 diff --git a/mq/handlers.go b/mq/handlers.go index 02e27b77..24cc349c 100644 --- a/mq/handlers.go +++ b/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 { diff --git a/mq/mq.go b/mq/mq.go index 7db1ffed..5e6fdb72 100644 --- a/mq/mq.go +++ b/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) diff --git a/netclient/ncutils/netclientutils.go b/netclient/ncutils/netclientutils.go index 66f27e02..9f79892d 100644 --- a/netclient/ncutils/netclientutils.go +++ b/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) -} diff --git a/release.md b/release.md index 02382cfc..7f16d6de 100644 --- a/release.md +++ b/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 diff --git a/scripts/nm-quick.sh b/scripts/nm-quick.sh index 20621bb0..2fc510f8 100755 --- a/scripts/nm-quick.sh +++ b/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 > $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 ” 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//authenticate endpoint, as documented below. title: Netmaker - version: 0.18.7 + version: 0.19.0 paths: /api/dns: get: