diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 771a4666..c38014b0 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.20.3 - v0.20.2 - v0.20.1 - v0.20.0 diff --git a/.github/workflows/branchtest.yml b/.github/workflows/branchtest.yml index 03c4ff58..222e9be5 100644 --- a/.github/workflows/branchtest.yml +++ b/.github/workflows/branchtest.yml @@ -10,12 +10,13 @@ jobs: skip-check: runs-on: ubuntu-latest outputs: - skip: ${{ steps.check.outputs.skip }} + skip: ${{ steps.skip.outputs.skip }} steps: - id: skip uses: fkirc/skip-duplicate-actions@v5 with: concurrent_skipping: 'always' + getbranch: runs-on: ubuntu-latest needs: skip-check @@ -29,7 +30,7 @@ jobs: repository: gravitl/netclient ref: develop - name: check if branch exists - id: checkbranch + id: getbranch run: | if git show-ref ${{ github.head_ref}}; then echo branch exists @@ -39,12 +40,62 @@ jobs: echo "netclientbranch=develop" >> $GITHUB_OUTPUT fi + getserver: + runs-on: ubuntu-latest + needs: skip-check + if: ${{ needs.skip-check.outputs.skip != 'true' }} + outputs: + netmakerserver: ${{ steps.getserver.outputs.server }} + steps: + - name: setup ssh + run: | + mkdir -p ~/.ssh/ + echo "$SSH_KEY" > ~/.ssh/id_devops + chmod 600 ~/.ssh/id_devops + cat >>~/.ssh/config <> /tmp/server + break + fi + done + echo server is $server + if [ "$server" == "" ] + then + echo server not set + exit 1 + fi + echo "netmakerserver=${ server }" >> $GITHUB_OUTPUT + - name: save server name + uses: actions/upload-artifact@v3 + with: + name: server + path: /tmp/ping + retention-days: 3 + terraform: - needs: getbranch + needs: [getbranch, getserver] uses: gravitl/devops/.github/workflows/terraform.yml@master with: netmakerbranch: ${{ github.head_ref }} netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }} + server: ${{ needs.getserver.outputs.netmakerserver }} secrets: inherit diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index 406349a9..7ec939dd 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.github/workflows/deletedroplets.yml @@ -16,6 +16,9 @@ jobs: with: run_id: ${{ github.event.workflow_run.id}} if_no_artifact_found: warn + - name: get server name + run: | + echo "SERVER=$(cat ./server/server) >> $GITHUB_ENV" - name: discord success message uses: appleboy/discord-action@master with: @@ -23,7 +26,7 @@ jobs: webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} color: "#42f545" username: "GitHub Bot" - message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} was successful: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}) will be deleted in 15 min" + message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} on dashboard.${{ env.SERVER }}.clustercat.com was successful: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}) will be deleted in 15 min" file: ./results/results.log - name: delete droplets if: success() || failure() @@ -36,6 +39,14 @@ jobs: env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} + - name: mark server as available + uses: appleboy/ssh-action@master + with: + host: server.${{ env.SERVER }}.clustercat.com + username: root + key: ${{ secrets.TESTING_SSH_KEY }} + script: | + rm /tmp/branchtest on-failure: runs-on: ubuntu-latest @@ -46,6 +57,9 @@ jobs: with: run_id: ${{ github.event.workflow_run.id}} if_no_artifact_found: warn + - name: get server name + run: | + echo "SERVER=$(cat ./server/server) >> $GITHUB_ENV" - name: discord failure message uses: appleboy/discord-action@master with: @@ -53,7 +67,7 @@ jobs: webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} color: "#990000" username: "GitHub Bot" - message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} failed: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt}}) will be deleted in 5 hours" + message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} failed: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt}}) will be deleted in 3 hours" file: ./results/results.log - name: discord error message uses: appleboy/discord-action@master @@ -67,7 +81,7 @@ jobs: - name: delete droplets if: success() || failure() run: | - sleep 5h + sleep 3h curl -X GET \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ @@ -75,3 +89,11 @@ jobs: env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} + - name: mark server as available + uses: appleboy/ssh-action@master + with: + host: server.${{ env.SERVER }}.clustercat.com + username: root + key: ${{ secrets.TESTING_SSH_KEY }} + script: | + rm /tmp/branchtest diff --git a/Dockerfile b/Dockerfile index b7b0e904..e2d5a3c3 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.18.0 +FROM alpine:3.18.2 # add a c lib # set the working directory diff --git a/Dockerfile-quick b/Dockerfile-quick index 13b6cbc8..feb67a93 100644 --- a/Dockerfile-quick +++ b/Dockerfile-quick @@ -1,5 +1,5 @@ #first stage - builder -FROM alpine:3.18.0 +FROM alpine:3.18.2 ARG version WORKDIR /app COPY ./netmaker /root/netmaker diff --git a/README.md b/README.md index 54b34dc3..c69a561d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@

- + diff --git a/cli/cmd/host/create_relay.go b/cli/cmd/host/create_relay.go deleted file mode 100644 index 2344af72..00000000 --- a/cli/cmd/host/create_relay.go +++ /dev/null @@ -1,22 +0,0 @@ -package host - -import ( - "strings" - - "github.com/gravitl/netmaker/cli/functions" - "github.com/spf13/cobra" -) - -var hostCreateRelayCmd = &cobra.Command{ - Use: "create_relay [HOST ID] [RELAYED HOST IDS (comma separated)]", - Args: cobra.ExactArgs(2), - Short: "Turn a Host into a Relay", - Long: `Turn a Host into a Relay`, - Run: func(cmd *cobra.Command, args []string) { - functions.PrettyPrint(functions.CreateRelay(args[0], strings.Split(args[1], ","))) - }, -} - -func init() { - rootCmd.AddCommand(hostCreateRelayCmd) -} diff --git a/cli/cmd/node/create_relay.go b/cli/cmd/node/create_relay.go new file mode 100644 index 00000000..e17dce57 --- /dev/null +++ b/cli/cmd/node/create_relay.go @@ -0,0 +1,22 @@ +package node + +import ( + "strings" + + "github.com/gravitl/netmaker/cli/functions" + "github.com/spf13/cobra" +) + +var hostCreateRelayCmd = &cobra.Command{ + Use: "create_relay [NETWORK][NODE ID] [RELAYED NODE IDS (comma separated)]", + Args: cobra.ExactArgs(3), + Short: "Turn a Node into a Relay", + Long: `Turn a Node into a Relay`, + Run: func(cmd *cobra.Command, args []string) { + functions.PrettyPrint(functions.CreateRelay(args[0], args[1], strings.Split(args[2], ","))) + }, +} + +func init() { + rootCmd.AddCommand(hostCreateRelayCmd) +} diff --git a/cli/cmd/host/delete_relay.go b/cli/cmd/node/delete_relay.go similarity index 51% rename from cli/cmd/host/delete_relay.go rename to cli/cmd/node/delete_relay.go index ccf203e6..91e2522d 100644 --- a/cli/cmd/host/delete_relay.go +++ b/cli/cmd/node/delete_relay.go @@ -1,4 +1,4 @@ -package host +package node import ( "github.com/gravitl/netmaker/cli/functions" @@ -6,12 +6,12 @@ import ( ) var hostDeleteRelayCmd = &cobra.Command{ - Use: "delete_relay [HOST ID]", - Args: cobra.ExactArgs(1), - Short: "Delete Relay role from a host", - Long: `Delete Relay role from a host`, + Use: "delete_relay [NETWORK] [NODE ID]", + Args: cobra.ExactArgs(2), + Short: "Delete Relay from a node", + Long: `Delete Relay from a node`, Run: func(cmd *cobra.Command, args []string) { - functions.PrettyPrint(functions.DeleteRelay(args[0])) + functions.PrettyPrint(functions.DeleteRelay(args[0], args[1])) }, } diff --git a/cli/cmd/node/flags.go b/cli/cmd/node/flags.go index b7da4d03..48f2f749 100644 --- a/cli/cmd/node/flags.go +++ b/cli/cmd/node/flags.go @@ -12,7 +12,7 @@ var ( postUp string postDown string keepAlive int - relayAddrs string + relayedNodes string egressGatewayRanges string expirationDateTime int defaultACL bool diff --git a/cli/cmd/node/update.go b/cli/cmd/node/update.go index ce0d9846..e2d2d383 100644 --- a/cli/cmd/node/update.go +++ b/cli/cmd/node/update.go @@ -35,8 +35,8 @@ var nodeUpdateCmd = &cobra.Command{ node.Address6 = address6 node.LocalAddress = localAddress node.PersistentKeepalive = int32(keepAlive) - if relayAddrs != "" { - node.RelayAddrs = strings.Split(relayAddrs, ",") + if relayedNodes != "" { + node.RelayedNodes = strings.Split(relayedNodes, ",") } if egressGatewayRanges != "" { node.EgressGatewayRanges = strings.Split(egressGatewayRanges, ",") @@ -62,7 +62,7 @@ func init() { 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(&relayAddrs, "relay_addrs", "", "Addresses for relaying connections if node acts as a relay") + 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") nodeUpdateCmd.Flags().BoolVar(&defaultACL, "acl", false, "Enable default ACL ?") diff --git a/cli/functions/host.go b/cli/functions/host.go index 16fac424..bfcf0f03 100644 --- a/cli/functions/host.go +++ b/cli/functions/host.go @@ -36,17 +36,18 @@ func DeleteHostFromNetwork(hostID, network string) *hostNetworksUpdatePayload { return request[hostNetworksUpdatePayload](http.MethodDelete, "/api/hosts/"+hostID+"/networks/"+network, nil) } -// CreateRelay - turn a host into a relay -func CreateRelay(hostID string, relayedHosts []string) *models.ApiHost { - return request[models.ApiHost](http.MethodPost, fmt.Sprintf("/api/hosts/%s/relay", hostID), &models.HostRelayRequest{ - HostID: hostID, - RelayedHosts: relayedHosts, +// CreateRelay - add relay to a node +func CreateRelay(netID, nodeID string, relayedNodes []string) *models.ApiNode { + return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createrelay", netID, nodeID), &models.RelayRequest{ + NodeID: nodeID, + NetID: netID, + RelayedNodes: relayedNodes, }) } -// DeleteRelay - remove relay role from a host -func DeleteRelay(hostID string) *models.ApiHost { - return request[models.ApiHost](http.MethodDelete, fmt.Sprintf("/api/hosts/%s/relay", hostID), nil) +// DeleteRelay - remove relay from a node +func DeleteRelay(netID, nodeID string) *models.ApiNode { + return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleterelay", netID, nodeID), nil) } // RefreshKeys - refresh wireguard keys diff --git a/compose/docker-compose.netclient.yml b/compose/docker-compose.netclient.yml index 06a52094..f6c05c3c 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.20.2' + image: 'gravitl/netclient:v0.20.3' hostname: netmaker-1 network_mode: host restart: on-failure diff --git a/config/config.go b/config/config.go index 8e6f1302..0dbafc85 100644 --- a/config/config.go +++ b/config/config.go @@ -32,56 +32,62 @@ 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"` - 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"` - NetmakerAccountID string `yaml:"netmaker_account_id"` - IsEE string `yaml:"is_ee"` - StunPort int `yaml:"stun_port"` - 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"` + 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"` + NetmakerAccountID string `yaml:"netmaker_account_id"` + IsEE string `yaml:"is_ee"` + StunPort int `yaml:"stun_port"` + 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"` + UsersLimit int `yaml:"user_limit"` + ClientsLimit int `yaml:"client_limit"` + NetworksLimit int `yaml:"network_limit"` + HostsLimit int `yaml:"host_limit"` + DeployedByOperator bool `yaml:"deployed_by_operator"` } // ProxyMode - default proxy mode for server diff --git a/controllers/docs.go b/controllers/docs.go index 589e6892..5408c50c 100644 --- a/controllers/docs.go +++ b/controllers/docs.go @@ -10,7 +10,7 @@ // // Schemes: https // BasePath: / -// Version: 0.20.2 +// Version: 0.20.3 // Host: netmaker.io // // Consumes: diff --git a/controllers/hosts.go b/controllers/hosts.go index 03cb3456..da329c84 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "reflect" "github.com/gorilla/mux" "github.com/gravitl/netmaker/logger" @@ -26,11 +25,9 @@ func hostHandlers(r *mux.Router) { r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete) 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/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(createHostRelay))).Methods(http.MethodPost) - 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/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) } @@ -174,13 +171,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) { } newHost := newHostData.ConvertAPIHostToNMHost(currHost) - // check if relay information is changed - updateRelay := false - if newHost.IsRelay && len(newHost.RelayedHosts) > 0 { - if len(newHost.RelayedHosts) != len(currHost.RelayedHosts) || !reflect.DeepEqual(newHost.RelayedHosts, currHost.RelayedHosts) { - updateRelay = true - } - } logic.UpdateHost(newHost, currHost) // update the in memory struct values if err = logic.UpsertHost(newHost); err != nil { @@ -188,9 +178,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if updateRelay { - logic.UpdateHostRelay(currHost.ID.String(), currHost.RelayedHosts, newHost.RelayedHosts) - } // publish host update through MQ if err := mq.HostUpdate(&models.HostUpdate{ Action: models.UpdateHost, @@ -244,33 +231,6 @@ func deleteHost(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if currHost.IsRelay { - if _, _, err := logic.DeleteHostRelay(hostid); err != nil { - logger.Log(0, r.Header.Get("user"), "failed to dissociate host from relays:", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - } - if currHost.IsRelayed { - relayHost, err := logic.GetHost(currHost.RelayedBy) - if err != nil { - logger.Log(0, r.Header.Get("user"), "failed to fetch relay host:", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - newRelayedHosts := make([]string, 0) - for _, relayedHostID := range relayHost.RelayedHosts { - if relayedHostID != hostid { - newRelayedHosts = append(newRelayedHosts, relayedHostID) - } - } - relayHost.RelayedHosts = newRelayedHosts - if err := logic.UpsertHost(relayHost); err != nil { - logger.Log(0, r.Header.Get("user"), "failed to update host relays:", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - } if err = logic.RemoveHost(currHost); err != nil { logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) diff --git a/controllers/limits.go b/controllers/limits.go index 268ada2d..2b4cee15 100644 --- a/controllers/limits.go +++ b/controllers/limits.go @@ -6,7 +6,6 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" - "github.com/gravitl/netmaker/servercfg" ) // limit consts @@ -23,20 +22,13 @@ func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc { Code: http.StatusForbidden, Message: "free tier limits exceeded on networks", } - if logic.Free_Tier && servercfg.Is_EE { // check that free tier limits not exceeded + if logic.Free_Tier { // check that free tier limits not exceeded if limit_choice == networks_l { currentNetworks, err := logic.GetNetworks() if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit { logic.ReturnErrorResponse(w, r, errorResponse) return } - } else if limit_choice == node_l { - nodes, err := logic.GetAllNodes() - if (err != nil && !database.IsEmptyRecord(err)) || len(nodes) >= logic.Node_Limit { - errorResponse.Message = "free tier limits exceeded on nodes" - logic.ReturnErrorResponse(w, r, errorResponse) - return - } } else if limit_choice == users_l { users, err := logic.GetUsers() if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit { diff --git a/controllers/node.go b/controllers/node.go index 4f06e9da..47684805 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -23,18 +23,16 @@ var hostIDHeader = "host-id" func nodeHandlers(r *mux.Router) { - r.HandleFunc("/api/nodes", authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet) - r.HandleFunc("/api/nodes/{network}", authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet) - r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet) - r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut) - r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete) - r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost) - r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete) - r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods(http.MethodPost) - r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete) + r.HandleFunc("/api/nodes", Authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes/{network}", Authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut) + r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete) + r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", Authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete) r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, http.HandlerFunc(createIngressGateway))).Methods(http.MethodPost) r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete) - r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost) r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost) r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost) } @@ -154,7 +152,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) { // even if it's technically ok // This is kind of a poor man's RBAC. There's probably a better/smarter way. // TODO: Consider better RBAC implementations -func authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc { +func Authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ Code: http.StatusForbidden, Message: logic.Forbidden_Msg, @@ -633,12 +631,12 @@ func updateNode(w http.ResponseWriter, r *http.Request) { } newNode := newData.ConvertToServerNode(¤tNode) relayupdate := false - if currentNode.IsRelay && len(newNode.RelayAddrs) > 0 { - if len(newNode.RelayAddrs) != len(currentNode.RelayAddrs) { + if servercfg.Is_EE && newNode.IsRelay && len(newNode.RelayedNodes) > 0 { + if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) { relayupdate = true } else { - for i, addr := range newNode.RelayAddrs { - if addr != currentNode.RelayAddrs[i] { + for i, node := range newNode.RelayedNodes { + if node != currentNode.RelayedNodes[i] { relayupdate = true } } @@ -651,10 +649,6 @@ func updateNode(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - relayedUpdate := false - if currentNode.IsRelayed && (currentNode.Address.String() != newNode.Address.String() || currentNode.Address6.String() != newNode.Address6.String()) { - relayedUpdate = true - } ifaceDelta := logic.IfaceDelta(¤tNode, newNode) aclUpdate := currentNode.DefaultACL != newNode.DefaultACL if ifaceDelta && servercfg.Is_EE { @@ -671,16 +665,13 @@ func updateNode(w http.ResponseWriter, r *http.Request) { return } if relayupdate { - updatenodes := logic.UpdateRelay(currentNode.Network, currentNode.RelayAddrs, newNode.RelayAddrs) + updatenodes := logic.UpdateRelayed(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes) if len(updatenodes) > 0 { for _, relayedNode := range updatenodes { runUpdates(&relayedNode, false) } } } - if relayedUpdate { - updateRelay(¤tNode, newNode) - } if servercfg.IsDNSMode() { logic.SetDNS() } @@ -690,8 +681,8 @@ func updateNode(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(apiNode) runUpdates(newNode, ifaceDelta) - go func(aclUpdate bool, newNode *models.Node) { - if aclUpdate { + go func(aclUpdate, relayupdate bool, newNode *models.Node) { + if aclUpdate || relayupdate { if err := mq.PublishPeerUpdate(); err != nil { logger.Log(0, "error during node ACL update for node", newNode.ID.String()) } @@ -699,7 +690,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) { if err := mq.PublishReplaceDNS(¤tNode, newNode, host); err != nil { logger.Log(1, "failed to publish dns update", err.Error()) } - }(aclUpdate, newNode) + }(aclUpdate, relayupdate, newNode) } // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode @@ -743,6 +734,22 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal")) return } + if node.IsRelayed { + // cleanup node from relayednodes on relay node + relayNode, err := logic.GetNodeByID(node.RelayedBy) + if err == nil { + relayedNodes := []string{} + for _, relayedNodeID := range relayNode.RelayedNodes { + if relayedNodeID == node.ID.String() { + continue + } + relayedNodes = append(relayedNodes, relayedNodeID) + } + relayNode.RelayedNodes = relayedNodes + logic.UpsertNode(&relayNode) + } + + } 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 @@ -778,30 +785,6 @@ func runUpdates(node *models.Node, ifaceDelta bool) { }() } -func updateRelay(oldnode, newnode *models.Node) { - relay := logic.FindRelay(oldnode) - newrelay := relay - //check if node's address has been updated and if so, update the relayAddrs of the relay node with the updated address of the relayed node - if oldnode.Address.String() != newnode.Address.String() { - for i, ip := range newrelay.RelayAddrs { - if ip == oldnode.Address.IP.String() { - newrelay.RelayAddrs = append(newrelay.RelayAddrs[:i], relay.RelayAddrs[i+1:]...) - newrelay.RelayAddrs = append(newrelay.RelayAddrs, newnode.Address.IP.String()) - } - } - } - //check if node's address(v6) has been updated and if so, update the relayAddrs of the relay node with the updated address(v6) of the relayed node - if oldnode.Address6.String() != newnode.Address6.String() { - for i, ip := range newrelay.RelayAddrs { - if ip == oldnode.Address.IP.String() { - newrelay.RelayAddrs = append(newrelay.RelayAddrs[:i], newrelay.RelayAddrs[i+1:]...) - newrelay.RelayAddrs = append(newrelay.RelayAddrs, newnode.Address6.IP.String()) - } - } - } - logic.UpdateNode(relay, newrelay) -} - func doesUserOwnNode(username, network, nodeID string) bool { u, err := logic.GetUser(username) if err != nil { diff --git a/controllers/relay.go b/controllers/relay.go deleted file mode 100644 index f8242359..00000000 --- a/controllers/relay.go +++ /dev/null @@ -1,199 +0,0 @@ -package controller - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/gravitl/netmaker/logger" - "github.com/gravitl/netmaker/logic" - "github.com/gravitl/netmaker/models" - "github.com/gravitl/netmaker/mq" -) - -// swagger:route POST /api/nodes/{network}/{nodeid}/createrelay nodes createRelay -// -// Create a relay. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: nodeResponse -func createRelay(w http.ResponseWriter, r *http.Request) { - var relay models.RelayRequest - var params = mux.Vars(r) - w.Header().Set("Content-Type", "application/json") - err := json.NewDecoder(r.Body).Decode(&relay) - 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 - } - relay.NetID = params["network"] - relay.NodeID = params["nodeid"] - updatenodes, node, err := logic.CreateRelay(relay) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("failed to create relay on node [%s] on network [%s]: %v", relay.NodeID, relay.NetID, err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - - logger.Log(1, r.Header.Get("user"), "created relay on node", relay.NodeID, "on network", relay.NetID) - for _, relayedNode := range updatenodes { - - err = mq.NodeUpdate(&relayedNode) - if err != nil { - logger.Log(1, "error sending update to relayed node ", relayedNode.ID.String(), "on network", relay.NetID, ": ", err.Error()) - } - } - - apiNode := node.ConvertToAPINode() - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiNode) - runUpdates(&node, true) -} - -// swagger:route DELETE /api/nodes/{network}/{nodeid}/deleterelay nodes deleteRelay -// -// Remove a relay. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: nodeResponse -func deleteRelay(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - nodeid := params["nodeid"] - netid := params["network"] - updatenodes, node, err := logic.DeleteRelay(netid, nodeid) - 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 - } - logger.Log(1, r.Header.Get("user"), "deleted relay server", nodeid, "on network", netid) - for _, relayedNode := range updatenodes { - err = mq.NodeUpdate(&relayedNode) - if err != nil { - logger.Log(1, "error sending update to relayed node ", relayedNode.ID.String(), "on network", netid, ": ", err.Error()) - } - } - apiNode := node.ConvertToAPINode() - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiNode) - runUpdates(&node, true) -} - -// swagger:route POST /api/hosts/{hostid}/relay hosts createHostRelay -// -// Create a relay. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: nodeResponse -func createHostRelay(w http.ResponseWriter, r *http.Request) { - var relay models.HostRelayRequest - var params = mux.Vars(r) - w.Header().Set("Content-Type", "application/json") - err := json.NewDecoder(r.Body).Decode(&relay) - 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 - } - relay.HostID = params["hostid"] - relayHost, relayedHosts, err := logic.CreateHostRelay(relay) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("failed to create relay on host [%s]: %v", relay.HostID, err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - - if err := mq.HostUpdate(&models.HostUpdate{ - Action: models.UpdateHost, - Host: *relayHost, - }); err != nil { - logger.Log(0, "failed to send host update: ", relayHost.ID.String(), err.Error()) - } - logger.Log(1, r.Header.Get("user"), "created relay on host", relay.HostID) - go func(relayHostID string) { - for _, relayedHost := range relayedHosts { - relayedHost.ProxyEnabled = true - logic.UpsertHost(&relayedHost) - if err := mq.HostUpdate(&models.HostUpdate{ - Action: models.UpdateHost, - Host: relayedHost, - }); err != nil { - logger.Log(0, "failed to send host update: ", relayedHost.ID.String(), err.Error()) - } - } - if err := mq.PublishPeerUpdate(); err != nil { - logger.Log(0, "fail to publish peer update: ", err.Error()) - } - - }(relay.HostID) - - apiHostData := relayHost.ConvertNMHostToAPI() - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiHostData) -} - -// swagger:route DELETE /api/hosts/{hostid}/relay hosts deleteHostRelay -// -// Remove a relay. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: nodeResponse -func deleteHostRelay(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - hostid := params["hostid"] - relayHost, relayed, err := logic.DeleteHostRelay(hostid) - 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 - } - logger.Log(1, r.Header.Get("user"), "deleted relay host", hostid) - go func() { - if err := mq.PublishPeerUpdate(); err != nil { - logger.Log(0, "fail to publish peer update: ", err.Error()) - } - if err := mq.HostUpdate(&models.HostUpdate{ - Action: models.UpdateHost, - Host: *relayHost, - }); err != nil { - logger.Log(0, "failed to send host update: ", relayHost.Name, err.Error()) - } - for _, relayedHost := range relayed { - if err := mq.HostUpdate(&models.HostUpdate{ - Action: models.UpdateHost, - Host: relayedHost, - }); err != nil { - logger.Log(0, "failed to send host update: ", relayedHost.Name, err.Error()) - } - } - }() - apiHostData := relayHost.ConvertNMHostToAPI() - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiHostData) -} diff --git a/controllers/server.go b/controllers/server.go index a5d6ef66..e9aa1b76 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -20,8 +20,40 @@ func serverHandlers(r *mux.Router) { 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/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) +} +func getUsage(w http.ResponseWriter, r *http.Request) { + type usage struct { + Hosts int `json:"hosts"` + Clients int `json:"clients"` + Networks int `json:"networks"` + Users int `json:"users"` + } + var serverUsage usage + hosts, err := logic.GetAllHosts() + if err == nil { + serverUsage.Hosts = len(hosts) + } + clients, err := logic.GetAllExtClients() + if err == nil { + serverUsage.Clients = len(clients) + } + users, err := logic.GetUsers() + if err == nil { + serverUsage.Users = len(users) + } + networks, err := logic.GetNetworks() + if err == nil { + serverUsage.Networks = len(networks) + } + 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 @@ -41,6 +73,12 @@ func getStatus(w http.ResponseWriter, r *http.Request) { type status struct { DB bool `json:"db_connected"` Broker bool `json:"broker_connected"` + Usage struct { + Hosts int `json:"hosts"` + Clients int `json:"clients"` + Networks int `json:"networks"` + Users int `json:"users"` + } `json:"usage"` } currentServerStatus := status{ diff --git a/controllers/user.go b/controllers/user.go index 32fabcc1..fdd59fca 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -19,6 +19,9 @@ var ( upgrader = websocket.Upgrader{} ) +// verifyJWT makes logic.VerifyJWT fakeable/mockable in tests +var verifyJWT = logic.VerifyJWT + func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet) @@ -152,7 +155,7 @@ func getUser(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) usernameFetched := params["username"] - user, err := logic.GetUser(usernameFetched) + user, err := logic.GetReturnUser(usernameFetched) if err != nil { logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error()) @@ -230,7 +233,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { } logger.Log(1, admin.UserName, "was made a new admin") - json.NewEncoder(w).Encode(admin) + json.NewEncoder(w).Encode(logic.ToReturnUser(admin)) } // swagger:route POST /api/users/{username} user createUser @@ -264,7 +267,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { return } logger.Log(1, user.UserName, "was created") - json.NewEncoder(w).Encode(user) + json.NewEncoder(w).Encode(logic.ToReturnUser(user)) } // swagger:route PUT /api/users/networks/{username} user updateUserNetworks @@ -314,12 +317,13 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) { } logger.Log(1, username, "status was updated") // re-read and return the new user struct - if userChange, err = logic.GetUser(username); err != nil { + returnUser, err := logic.GetReturnUser(username) + if err != nil { logger.Log(0, username, "failed to fetch user: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - json.NewEncoder(w).Encode(userChange) + json.NewEncoder(w).Encode(returnUser) } // swagger:route PUT /api/users/{username} user updateUser @@ -337,7 +341,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) // start here - jwtUser, _, isadmin, err := logic.VerifyJWT(r.Header.Get("Authorization")) + jwtUser, _, isadmin, err := verifyJWT(r.Header.Get("Authorization")) if err != nil { logger.Log(0, "verifyJWT error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) @@ -385,7 +389,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { return } logger.Log(1, username, "was updated") - json.NewEncoder(w).Encode(user) + json.NewEncoder(w).Encode(logic.ToReturnUser(*user)) } // swagger:route PUT /api/users/{username}/adm user updateUserAdm @@ -409,7 +413,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if auth.IsOauthUser(user) != nil { + if auth.IsOauthUser(user) == nil { err := fmt.Errorf("cannot update user info for oauth user %s", username) logger.Log(0, err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) @@ -436,7 +440,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) { return } logger.Log(1, username, "was updated (admin)") - json.NewEncoder(w).Encode(user) + json.NewEncoder(w).Encode(logic.ToReturnUser(*user)) } // swagger:route DELETE /api/users/{username} user deleteUser diff --git a/controllers/user_test.go b/controllers/user_test.go index 655760ca..f98e341f 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -1,6 +1,12 @@ package controller import ( + "bytes" + "github.com/go-jose/go-jose/v3/json" + "github.com/gorilla/mux" + "io" + "net/http" + "net/http/httptest" "testing" "github.com/stretchr/testify/assert" @@ -19,6 +25,123 @@ func deleteAllUsers(t *testing.T) { } } +func TestGetUserNoHashedPassword(t *testing.T) { + // prepare existing user base + user := models.User{UserName: "freddie", Password: "password"} + haveOnlyOneUser(t, user) + + // prepare request + rec, req := prepareUserRequest(t, models.User{}, user.UserName) + + // test response + getUser(rec, req) + assertUserNameButNoPassword(t, rec.Body, user.UserName) +} + +func TestCreateAdminNoHashedPassword(t *testing.T) { + // prepare existing user base + deleteAllUsers(t) + + // prepare request + user := models.User{UserName: "jonathan", Password: "password"} + rec, req := prepareUserRequest(t, user, "") + + // test response + createAdmin(rec, req) + assertUserNameButNoPassword(t, rec.Body, user.UserName) +} + +func TestCreateUserNoHashedPassword(t *testing.T) { + // prepare existing user base + deleteAllUsers(t) + + // prepare request + user := models.User{UserName: "jonathan", Password: "password"} + rec, req := prepareUserRequest(t, user, "") + + // test response + createUser(rec, req) + assertUserNameButNoPassword(t, rec.Body, user.UserName) +} + +func TestUpdateUserNetworksNoHashedPassword(t *testing.T) { + // prepare existing user base + user1 := models.User{UserName: "joestar", Password: "jonathan"} + haveOnlyOneUser(t, user1) + + // prepare request + user2 := models.User{UserName: "joestar", Password: "joseph"} + rec, req := prepareUserRequest(t, user2, user1.UserName) + + // test response + updateUserNetworks(rec, req) + assertUserNameButNoPassword(t, rec.Body, user1.UserName) +} + +func TestUpdateUserNoHashedPassword(t *testing.T) { + // prepare existing user base + user1 := models.User{UserName: "dio", Password: "brando"} + haveOnlyOneUser(t, user1) + + // prepare request + user2 := models.User{UserName: "giorno", Password: "giovanna"} + rec, req := prepareUserRequest(t, user2, user1.UserName) + + // mock the jwt verification + oldVerify := verifyJWT + verifyJWT = func(bearerToken string) (username string, networks []string, isadmin bool, err error) { + return user1.UserName, user1.Networks, user1.IsAdmin, nil + } + defer func() { verifyJWT = oldVerify }() + + // test response + updateUser(rec, req) + assertUserNameButNoPassword(t, rec.Body, user2.UserName) +} + +func TestUpdateUserAdmNoHashedPassword(t *testing.T) { + // prepare existing user base + user1 := models.User{UserName: "dio", Password: "brando", IsAdmin: true} + haveOnlyOneUser(t, user1) + + // prepare request + user2 := models.User{UserName: "giorno", Password: "giovanna"} + rec, req := prepareUserRequest(t, user2, user1.UserName) + + // test response + updateUserAdm(rec, req) + assertUserNameButNoPassword(t, rec.Body, user2.UserName) +} + +func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam string) (*httptest.ResponseRecorder, *http.Request) { + bits, err := json.Marshal(userForBody) + assert.Nil(t, err) + body := bytes.NewReader(bits) + rec := httptest.NewRecorder() + req := httptest.NewRequest("ANY", "https://example.com", body) // only the body matters here + req = mux.SetURLVars(req, map[string]string{"username": userNameForParam}) + return rec, req +} + +func haveOnlyOneUser(t *testing.T, user models.User) { + deleteAllUsers(t) + var err error + if user.IsAdmin { + err = logic.CreateAdmin(&user) + } else { + err = logic.CreateUser(&user) + } + assert.Nil(t, err) +} + +func assertUserNameButNoPassword(t *testing.T, r io.Reader, userName string) { + var resp models.User + err := json.NewDecoder(r).Decode(&resp) + assert.Nil(t, err) + assert.Equal(t, userName, resp.UserName) + assert.Empty(t, resp.Password) +} + func TestHasAdmin(t *testing.T) { // delete all current users users, _ := logic.GetUsers() diff --git a/ee/ee_controllers/relay.go b/ee/ee_controllers/relay.go new file mode 100644 index 00000000..deffc08b --- /dev/null +++ b/ee/ee_controllers/relay.go @@ -0,0 +1,107 @@ +package ee_controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/gorilla/mux" + controller "github.com/gravitl/netmaker/controllers" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/mq" +) + +// RelayHandlers - handle EE Relays +func RelayHandlers(r *mux.Router) { + + r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", controller.Authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", controller.Authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete) +} + +// swagger:route POST /api/nodes/{network}/{nodeid}/createrelay nodes createRelay +// +// Create a relay. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: nodeResponse +func createRelay(w http.ResponseWriter, r *http.Request) { + var relayRequest models.RelayRequest + var params = mux.Vars(r) + w.Header().Set("Content-Type", "application/json") + err := json.NewDecoder(r.Body).Decode(&relayRequest) + 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 + } + relayRequest.NetID = params["network"] + relayRequest.NodeID = params["nodeid"] + _, relayNode, err := logic.CreateRelay(relayRequest) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to create relay on node [%s] on network [%s]: %v", relayRequest.NodeID, relayRequest.NetID, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + go mq.PublishPeerUpdate() + logger.Log(1, r.Header.Get("user"), "created relay on node", relayRequest.NodeID, "on network", relayRequest.NetID) + apiNode := relayNode.ConvertToAPINode() + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(apiNode) +} + +// swagger:route DELETE /api/nodes/{network}/{nodeid}/deleterelay nodes deleteRelay +// +// Remove a relay. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: nodeResponse +func deleteRelay(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + nodeid := params["nodeid"] + netid := params["network"] + updateNodes, node, err := logic.DeleteRelay(netid, nodeid) + 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 + } + logger.Log(1, r.Header.Get("user"), "deleted relay server", nodeid, "on network", netid) + go func() { + for _, relayedNode := range updateNodes { + err = mq.NodeUpdate(&relayedNode) + if err != nil { + logger.Log(1, "relayed node update ", relayedNode.ID.String(), "on network", relayedNode.Network, ": ", err.Error()) + + } + h, err := logic.GetHost(relayedNode.HostID.String()) + if err == nil { + if h.OS == models.OS_Types.IoT { + node.IsRelay = true // for iot update to recognise that it has to delete relay peer + if err = mq.PublishSingleHostPeerUpdate(context.Background(), h, &node, nil); err != nil { + logger.Log(1, "failed to publish peer update to host", h.ID.String(), ": ", err.Error()) + } + } + } + } + mq.PublishPeerUpdate() + }() + logger.Log(1, r.Header.Get("user"), "deleted relay on node", node.ID.String(), "on network", node.Network) + apiNode := node.ConvertToAPINode() + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(apiNode) +} diff --git a/ee/initialize.go b/ee/initialize.go index c0508246..455ee59d 100644 --- a/ee/initialize.go +++ b/ee/initialize.go @@ -16,23 +16,20 @@ import ( // InitEE - Initialize EE Logic func InitEE() { setIsEnterprise() + servercfg.Is_EE = true models.SetLogo(retrieveEELogo()) controller.HttpHandlers = append( controller.HttpHandlers, ee_controllers.MetricHandlers, ee_controllers.NetworkUsersHandlers, ee_controllers.UserGroupsHandlers, + ee_controllers.RelayHandlers, ) logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() { // == License Handling == ValidateLicense() - if Limits.FreeTier { - logger.Log(0, "proceeding with Free Tier license") - logic.SetFreeTierForTelemetry(true) - } else { - logger.Log(0, "proceeding with Paid Tier license") - logic.SetFreeTierForTelemetry(false) - } + logger.Log(0, "proceeding with Paid Tier license") + logic.SetFreeTierForTelemetry(false) // == End License Handling == AddLicenseHooks() resetFailover() @@ -45,17 +42,6 @@ func InitEE() { logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient } -func setControllerLimits() { - logic.Node_Limit = Limits.Nodes - logic.Users_Limit = Limits.Users - logic.Clients_Limit = Limits.Clients - logic.Free_Tier = Limits.FreeTier - servercfg.Is_EE = true - if logic.Free_Tier { - logic.Networks_Limit = 3 - } -} - func resetFailover() { nets, err := logic.GetNetworks() if err == nil { diff --git a/ee/license.go b/ee/license.go index 521326ec..d9b197ab 100644 --- a/ee/license.go +++ b/ee/license.go @@ -9,12 +9,13 @@ import ( "encoding/json" "fmt" "io" - "math" "net/http" + "time" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/servercfg" "golang.org/x/crypto/nacl/box" @@ -31,8 +32,14 @@ type apiServerConf struct { // AddLicenseHooks - adds the validation and cache clear hooks func AddLicenseHooks() { - logic.AddHook(ValidateLicense) - logic.AddHook(ClearLicenseCache) + logic.HookManagerCh <- models.HookDetails{ + Hook: ValidateLicense, + Interval: time.Hour, + } + logic.HookManagerCh <- models.HookDetails{ + Hook: ClearLicenseCache, + Interval: time.Hour, + } } // ValidateLicense - the initial license check for netmaker server @@ -58,8 +65,8 @@ func ValidateLicense() error { } licenseSecret := LicenseSecret{ - UserID: netmakerAccountID, - Limits: getCurrentServerLimit(), + AssociatedID: netmakerAccountID, + Limits: getCurrentServerLimit(), } secretData, err := json.Marshal(&licenseSecret) @@ -92,17 +99,6 @@ func ValidateLicense() error { logger.FatalLog0(errValidation.Error()) } - Limits.Networks = math.MaxInt - Limits.FreeTier = license.FreeTier == "yes" - Limits.Clients = license.LimitClients - Limits.Nodes = license.LimitNodes - Limits.Servers = license.LimitServers - Limits.Users = license.LimitUsers - if Limits.FreeTier { - Limits.Networks = 3 - } - setControllerLimits() - logger.Log(0, "License validation succeeded!") return nil } @@ -167,6 +163,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro } msg := ValidateLicenseRequest{ + LicenseKey: servercfg.GetLicenseKey(), NmServerPubKey: base64encode(publicKeyBytes), EncryptedPart: base64encode(encryptedData), } @@ -180,9 +177,6 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro if err != nil { return nil, err } - reqParams := req.URL.Query() - reqParams.Add("licensevalue", servercfg.GetLicenseKey()) - req.URL.RawQuery = reqParams.Encode() req.Header.Add("Content-Type", "application/json") req.Header.Add("Accept", "application/json") client := &http.Client{} diff --git a/ee/types.go b/ee/types.go index f59d21fd..17b5dd33 100644 --- a/ee/types.go +++ b/ee/types.go @@ -3,7 +3,7 @@ package ee import "fmt" const ( - api_endpoint = "https://api.controller.netmaker.io/api/v1/license/validate" + api_endpoint = "https://api.accounts.netmaker.io/api/v1/license/validate" license_cache_key = "license_response_cache" license_validation_err_msg = "invalid license" server_id_key = "nm-server-id" @@ -11,38 +11,17 @@ const ( var errValidation = fmt.Errorf(license_validation_err_msg) -// Limits - limits to be referenced throughout server -var Limits = GlobalLimits{ - Servers: 0, - Users: 0, - Nodes: 0, - Clients: 0, - Networks: 0, - FreeTier: false, -} - -// GlobalLimits - struct for holding global limits on this netmaker server in memory -type GlobalLimits struct { - Servers int - Users int - Nodes int - Clients int - FreeTier bool - Networks int -} - // LicenseKey - the license key struct representation with associated data type LicenseKey struct { - LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key - Expiration int64 `json:"expiration"` - LimitServers int `json:"limit_servers"` - LimitUsers int `json:"limit_users"` - LimitNodes int `json:"limit_nodes"` - LimitClients int `json:"limit_clients"` - Metadata string `json:"metadata"` - SubscriptionID string `json:"subscription_id"` // for a paid subscription (non-free-tier license) - FreeTier string `json:"free_tier"` // yes if free tier - IsActive string `json:"is_active"` // yes if active + LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key + Expiration int64 `json:"expiration"` + LimitServers int `json:"limit_servers"` + LimitUsers int `json:"limit_users"` + LimitHosts int `json:"limit_hosts"` + LimitNetworks int `json:"limit_networks"` + LimitClients int `json:"limit_clients"` + Metadata string `json:"metadata"` + IsActive bool `json:"is_active"` // yes if active } // ValidatedLicense - the validated license struct @@ -53,28 +32,31 @@ type ValidatedLicense struct { // LicenseSecret - the encrypted struct for sending user-id type LicenseSecret struct { - UserID string `json:"user_id" binding:"required"` // UUID for user foreign key to User table - Limits LicenseLimits `json:"limits" binding:"required"` + AssociatedID string `json:"associated_id" binding:"required"` // UUID for user foreign key to User table + Limits LicenseLimits `json:"limits" binding:"required"` } // LicenseLimits - struct license limits type LicenseLimits struct { - Servers int `json:"servers" binding:"required"` - Users int `json:"users" binding:"required"` - Nodes int `json:"nodes" binding:"required"` - Clients int `json:"clients" binding:"required"` + Servers int `json:"servers"` + Users int `json:"users"` + Hosts int `json:"hosts"` + Clients int `json:"clients"` + Networks int `json:"networks"` } // LicenseLimits.SetDefaults - sets the default values for limits func (l *LicenseLimits) SetDefaults() { l.Clients = 0 l.Servers = 1 - l.Nodes = 0 + l.Hosts = 0 l.Users = 1 + l.Networks = 0 } // ValidateLicenseRequest - used for request to validate license endpoint type ValidateLicenseRequest struct { + LicenseKey string `json:"license_key" binding:"required"` NmServerPubKey string `json:"nm_server_pub_key" binding:"required"` // Netmaker server public key used to send data back to Netmaker for the Netmaker server to decrypt (eg output from validating license) EncryptedPart string `json:"secret" binding:"required"` } diff --git a/ee/util.go b/ee/util.go index a45bc1a4..b63cf158 100644 --- a/ee/util.go +++ b/ee/util.go @@ -30,12 +30,11 @@ func base64decode(input string) []byte { return bytes } - func getCurrentServerLimit() (limits LicenseLimits) { limits.SetDefaults() - nodes, err := logic.GetAllNodes() + hosts, err := logic.GetAllHosts() if err == nil { - limits.Nodes = len(nodes) + limits.Hosts = len(hosts) } clients, err := logic.GetAllExtClients() if err == nil { @@ -45,5 +44,9 @@ func getCurrentServerLimit() (limits LicenseLimits) { if err == nil { limits.Users = len(users) } + networks, err := logic.GetNetworks() + if err == nil { + limits.Networks = len(networks) + } return } diff --git a/go.mod b/go.mod index 511f4e1e..e005aa51 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,11 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.8.4 github.com/txn2/txeh v1.4.0 - golang.org/x/crypto v0.9.0 - golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.8.0 - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/crypto v0.10.0 + golang.org/x/net v0.11.0 // indirect + golang.org/x/oauth2 v0.9.0 + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31 google.golang.org/protobuf v1.28.1 // indirect diff --git a/go.sum b/go.sum index ac69be14..e5091055 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= 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= @@ -133,10 +133,10 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= +golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -152,8 +152,8 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -161,8 +161,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.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.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/k8s/client/netclient-daemonset.yaml b/k8s/client/netclient-daemonset.yaml index a03156bf..09d3a8bd 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.20.2 + image: gravitl/netclient:v0.20.3 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/client/netclient.yaml b/k8s/client/netclient.yaml index cdb4777b..8a7aecba 100644 --- a/k8s/client/netclient.yaml +++ b/k8s/client/netclient.yaml @@ -28,7 +28,7 @@ spec: # - "" containers: - name: netclient - image: gravitl/netclient:v0.20.2 + image: gravitl/netclient:v0.20.3 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/server/netmaker-ui.yaml b/k8s/server/netmaker-ui.yaml index 047360b1..a39e63e2 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.20.2 + image: gravitl/netmaker-ui:v0.20.3 ports: - containerPort: 443 env: diff --git a/logic/auth.go b/logic/auth.go index c653cb52..6c2b210b 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -42,20 +42,6 @@ func HasAdmin() (bool, error) { return false, err } -// GetReturnUser - gets a user -func GetReturnUser(username string) (models.ReturnUser, error) { - - var user models.ReturnUser - record, err := database.FetchRecord(database.USERS_TABLE_NAME, username) - if err != nil { - return user, err - } - if err = json.Unmarshal([]byte(record), &user); err != nil { - return models.ReturnUser{}, err - } - return user, err -} - // GetUsers - gets users func GetUsers() ([]models.ReturnUser, error) { diff --git a/logic/gateway.go b/logic/gateway.go index e05d985a..556b108b 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -102,6 +102,9 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq if err != nil { return models.Node{}, err } + if node.IsRelayed { + return models.Node{}, errors.New("ingress cannot be created on a relayed node") + } host, err := GetHost(node.HostID.String()) if err != nil { return models.Node{}, err diff --git a/logic/hosts.go b/logic/hosts.go index a41bccf5..d2ed2f30 100644 --- a/logic/hosts.go +++ b/logic/hosts.go @@ -93,7 +93,14 @@ func GetHost(hostid string) (*models.Host, error) { // CreateHost - creates a host if not exist func CreateHost(h *models.Host) error { - _, err := GetHost(h.ID.String()) + hosts, err := GetAllHosts() + if err != nil && !database.IsEmptyRecord(err) { + return err + } + if len(hosts) >= Hosts_Limit { + return errors.New("free tier limits exceeded on hosts") + } + _, err = GetHost(h.ID.String()) if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) { return ErrHostExists } diff --git a/logic/nodes.go b/logic/nodes.go index 4928cac0..1becdb64 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -75,6 +75,16 @@ func UpdateNodeCheckin(node *models.Node) error { return database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME) } +// UpsertNode - updates node in the DB +func UpsertNode(newNode *models.Node) error { + newNode.SetLastModified() + data, err := json.Marshal(newNode) + if err != nil { + return err + } + return database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME) +} + // UpdateNode - takes a node and updates another node with it's values func UpdateNode(currentNode *models.Node, newNode *models.Node) error { if newNode.Address.IP.String() != currentNode.Address.IP.String() { @@ -85,7 +95,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error { } } nodeACLDelta := currentNode.DefaultACL != newNode.DefaultACL - newNode.Fill(currentNode) + newNode.Fill(currentNode, servercfg.Is_EE) // check for un-settable server values if err := ValidateNode(newNode, true); err != nil { @@ -338,34 +348,6 @@ func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node, return node, nil } -// GetNodeRelay - gets the relay node of a given network -func GetNodeRelay(network string, relayedNodeAddr string) (models.Node, error) { - collection, err := database.FetchRecords(database.NODES_TABLE_NAME) - var relay models.Node - if err != nil { - if database.IsEmptyRecord(err) { - return relay, nil - } - logger.Log(2, err.Error()) - return relay, err - } - for _, value := range collection { - err := json.Unmarshal([]byte(value), &relay) - if err != nil { - logger.Log(2, err.Error()) - continue - } - if relay.IsRelay { - for _, addr := range relay.RelayAddrs { - if addr == relayedNodeAddr { - return relay, nil - } - } - } - } - return relay, errors.New(RELAY_NODE_ERR + " " + relayedNodeAddr) -} - func GetNodeByID(uuid string) (models.Node, error) { var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid) if err != nil { @@ -399,24 +381,12 @@ func GetDeletedNodeByID(uuid string) (models.Node, error) { // FindRelay - returns the node that is the relay for a relayed node func FindRelay(node *models.Node) *models.Node { - if !node.IsRelayed { - return nil - } - peers, err := GetNetworkNodes(node.Network) + relay, err := GetNodeByID(node.RelayedBy) if err != nil { + logger.Log(0, "FindRelay: "+err.Error()) return nil } - for _, peer := range peers { - if !peer.IsRelay { - continue - } - for _, ip := range peer.RelayAddrs { - if ip == node.Address.IP.String() || ip == node.Address6.IP.String() { - return &peer - } - } - } - return nil + return &relay } // GetNetworkIngresses - gets the gateways of a network diff --git a/logic/peers.go b/logic/peers.go index f2f1d08f..846402d2 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -13,6 +13,7 @@ import ( "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" "golang.org/x/exp/slices" + "golang.org/x/exp/slog" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) @@ -29,41 +30,6 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy Action: models.ProxyUpdate, } peerConfMap := make(map[string]models.PeerConf) - if host.IsRelayed { - relayHost, err := GetHost(host.RelayedBy) - if err == nil { - relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost))) - if err != nil { - logger.Log(1, "failed to resolve relay node endpoint: ", err.Error()) - } - proxyPayload.IsRelayed = true - proxyPayload.RelayedTo = relayEndpoint - } else { - logger.Log(0, "couldn't find relay host for: ", host.ID.String()) - } - } - if host.IsRelay { - relayedHosts := GetRelayedHosts(host) - relayPeersMap := make(map[string]models.RelayedConf) - for _, relayedHost := range relayedHosts { - relayedHost := relayedHost - payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil, nil) - if err == nil { - relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost))) - if udpErr == nil { - relayPeersMap[relayedHost.PublicKey.String()] = models.RelayedConf{ - RelayedPeerEndpoint: relayedEndpoint, - RelayedPeerPubKey: relayedHost.PublicKey.String(), - Peers: payload.Peers, - } - } - - } - } - proxyPayload.IsRelay = true - proxyPayload.RelayedPeerConf = relayPeersMap - - } var ingressStatus bool for _, nodeID := range host.Nodes { @@ -101,18 +67,6 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy } } - if peerHost.IsRelayed && peerHost.RelayedBy != host.ID.String() { - relayHost, err := GetHost(peerHost.RelayedBy) - if err == nil { - relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost))) - if err == nil { - currPeerConf.IsRelayed = true - currPeerConf.RelayedTo = relayTo - } - - } - } - peerConfMap[peerHost.PublicKey.String()] = currPeerConf } if node.IsIngressGateway { @@ -167,7 +121,9 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host HostNetworkInfo: models.HostInfoMap{}, } - logger.Log(1, "peer update for host", host.ID.String()) + // endpoint detection always comes from the server + hostPeerUpdate.EndpointDetection = servercfg.EndpointDetectionEnabled() + slog.Debug("peer update for host", "hostId", host.ID.String()) peerIndexMap := make(map[string]int) for _, nodeID := range host.Nodes { nodeID := nodeID @@ -178,6 +134,61 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE { continue } + if host.OS == models.OS_Types.IoT { + hostPeerUpdate.NodeAddrs = append(hostPeerUpdate.NodeAddrs, node.PrimaryAddressIPNet()) + if node.IsRelayed { + relayNode, err := GetNodeByID(node.RelayedBy) + if err != nil { + continue + } + relayHost, err := GetHost(relayNode.HostID.String()) + if err != nil { + continue + } + relayPeer := wgtypes.PeerConfig{ + PublicKey: relayHost.PublicKey, + PersistentKeepaliveInterval: &relayNode.PersistentKeepalive, + ReplaceAllowedIPs: true, + AllowedIPs: GetAllowedIPs(&node, &relayNode, nil), + } + uselocal := false + if host.EndpointIP.String() == relayHost.EndpointIP.String() { + // peer is on same network + // set to localaddress + uselocal = true + if node.LocalAddress.IP == nil { + // use public endpint + uselocal = false + } + if node.LocalAddress.String() == relayNode.LocalAddress.String() { + uselocal = false + } + } + relayPeer.Endpoint = &net.UDPAddr{ + IP: relayHost.EndpointIP, + Port: getPeerWgListenPort(relayHost), + } + + if uselocal { + relayPeer.Endpoint.IP = relayNode.LocalAddress.IP + relayPeer.Endpoint.Port = relayHost.ListenPort + } + + hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer) + } else if deletedNode != nil && deletedNode.IsRelay { + relayHost, err := GetHost(deletedNode.HostID.String()) + if err != nil { + continue + } + relayPeer := wgtypes.PeerConfig{ + PublicKey: relayHost.PublicKey, + Remove: true, + } + hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer) + } + continue + } + currentPeers := GetNetworkNodesMemory(allNodes, node.Network) var nodePeerMap map[string]models.PeerRouteInfo if node.IsIngressGateway || node.IsEgressGateway { @@ -195,58 +206,17 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host //skip yourself continue } - var peerConfig wgtypes.PeerConfig + peerHost, err := GetHost(peer.HostID.String()) if err != nil { logger.Log(1, "no peer host", peer.HostID.String(), err.Error()) return models.HostPeerUpdate{}, err } - - peerConfig.PublicKey = peerHost.PublicKey - peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive - peerConfig.ReplaceAllowedIPs = true - uselocal := false - if host.EndpointIP.String() == peerHost.EndpointIP.String() { - // peer is on same network - // set to localaddress - uselocal = true - if node.LocalAddress.IP == nil { - // use public endpint - uselocal = false - } - if node.LocalAddress.String() == peer.LocalAddress.String() { - uselocal = false - } + peerConfig := wgtypes.PeerConfig{ + PublicKey: peerHost.PublicKey, + PersistentKeepaliveInterval: &peer.PersistentKeepalive, + ReplaceAllowedIPs: true, } - peerConfig.Endpoint = &net.UDPAddr{ - IP: peerHost.EndpointIP, - Port: getPeerWgListenPort(peerHost), - } - - if uselocal { - peerConfig.Endpoint.IP = peer.LocalAddress.IP - peerConfig.Endpoint.Port = peerHost.ListenPort - } - allowedips := GetAllowedIPs(&node, &peer, nil) - if peer.IsIngressGateway { - for _, entry := range peer.IngressGatewayRange { - _, cidr, err := net.ParseCIDR(string(entry)) - if err == nil { - allowedips = append(allowedips, *cidr) - } - } - } - if peer.IsEgressGateway { - allowedips = append(allowedips, getEgressIPs(&node, &peer)...) - } - if peer.Action != models.NODE_DELETE && - !peer.PendingDelete && - peer.Connected && - nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) && - (deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) { - peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection - } - if node.IsIngressGateway || node.IsEgressGateway { if peer.IsIngressGateway { _, extPeerIDAndAddrs, err := getExtPeers(&peer) @@ -279,6 +249,47 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host ID: peer.ID.String(), } } + if (node.IsRelayed && node.RelayedBy != peer.ID.String()) || (peer.IsRelayed && peer.RelayedBy != node.ID.String()) { + // if node is relayed and peer is not the relay, set remove to true + if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; ok { + continue + } + peerConfig.Remove = true + hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig) + peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1 + continue + } + + uselocal := false + if host.EndpointIP.String() == peerHost.EndpointIP.String() { + // peer is on same network + // set to localaddress + uselocal = true + if node.LocalAddress.IP == nil { + // use public endpint + uselocal = false + } + if node.LocalAddress.String() == peer.LocalAddress.String() { + uselocal = false + } + } + peerConfig.Endpoint = &net.UDPAddr{ + IP: peerHost.EndpointIP, + Port: getPeerWgListenPort(peerHost), + } + + if uselocal { + peerConfig.Endpoint.IP = peer.LocalAddress.IP + peerConfig.Endpoint.Port = peerHost.ListenPort + } + allowedips := GetAllowedIPs(&node, &peer, nil) + if peer.Action != models.NODE_DELETE && + !peer.PendingDelete && + peer.Connected && + nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) && + (deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) { + peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection + } peerProxyPort := GetProxyListenPort(peerHost) var nodePeer wgtypes.PeerConfig @@ -300,8 +311,9 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host nodePeer = peerConfig } else { peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs - peerAllowedIPs = append(peerAllowedIPs, allowedips...) + peerAllowedIPs = append(peerAllowedIPs, peerConfig.AllowedIPs...) hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs + hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].Remove = false hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{ ID: peer.ID.String(), Address: peer.PrimaryAddress(), @@ -624,14 +636,15 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet } } } + if node.IsRelayed && node.RelayedBy == peer.ID.String() { + allowedips = append(allowedips, getAllowedIpsForRelayed(node, peer)...) + + } return allowedips } -func getEgressIPs(node, peer *models.Node) []net.IPNet { - host, err := GetHost(node.HostID.String()) - if err != nil { - logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error()) - } +func getEgressIPs(peer *models.Node) []net.IPNet { + peerHost, err := GetHost(peer.HostID.String()) if err != nil { logger.Log(0, "error retrieving host for peer", peer.ID.String(), err.Error()) @@ -651,12 +664,12 @@ func getEgressIPs(node, peer *models.Node) []net.IPNet { } // getting the public ip of node if ipnet.Contains(peerHost.EndpointIP) && !internetGateway { // ensuring egress gateway range does not contain endpoint of node - logger.Log(2, "egress IP range of ", iprange, " overlaps with ", host.EndpointIP.String(), ", omitting") + logger.Log(2, "egress IP range of ", iprange, " overlaps with ", peerHost.EndpointIP.String(), ", omitting") continue // skip adding egress range if overlaps with node's ip } // TODO: Could put in a lot of great logic to avoid conflicts / bad routes - if ipnet.Contains(node.LocalAddress.IP) && !internetGateway { // ensuring egress gateway range does not contain public ip of node - logger.Log(2, "egress IP range of ", iprange, " overlaps with ", node.LocalAddress.String(), ", omitting") + if ipnet.Contains(peer.LocalAddress.IP) && !internetGateway { // ensuring egress gateway range does not contain public ip of node + logger.Log(2, "egress IP range of ", iprange, " overlaps with ", peer.LocalAddress.String(), ", omitting") continue // skip adding egress range if overlaps with node's local ip } if err != nil { @@ -687,12 +700,50 @@ func getNodeAllowedIPs(peer, node *models.Node) []net.IPNet { // handle egress gateway peers if peer.IsEgressGateway { //hasGateway = true - egressIPs := getEgressIPs(node, peer) + egressIPs := getEgressIPs(peer) allowedips = append(allowedips, egressIPs...) } + if peer.IsRelay { + for _, relayedNodeID := range peer.RelayedNodes { + if node.ID.String() == relayedNodeID { + continue + } + relayedNode, err := GetNodeByID(relayedNodeID) + if err != nil { + continue + } + allowed := getRelayedAddresses(relayedNodeID) + if relayedNode.IsEgressGateway { + allowed = append(allowed, getEgressIPs(&relayedNode)...) + } + allowedips = append(allowedips, allowed...) + } + } return allowedips } +// getAllowedIpsForRelayed - returns the peerConfig for a node relayed by relay +func getAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNet) { + if relayed.RelayedBy != relay.ID.String() { + logger.Log(0, "RelayedByRelay called with invalid parameters") + return + } + peers, err := GetNetworkNodes(relay.Network) + if err != nil { + logger.Log(0, "error getting network clients", err.Error()) + return + } + for _, peer := range peers { + if peer.ID == relayed.ID || peer.ID == relay.ID { + continue + } + if nodeacls.AreNodesAllowed(nodeacls.NetworkID(relayed.Network), nodeacls.NodeID(relayed.ID.String()), nodeacls.NodeID(peer.ID.String())) { + allowedIPs = append(allowedIPs, GetAllowedIPs(relayed, &peer, nil)...) + } + } + return +} + func getCIDRMaskFromAddr(addr string) net.IPMask { cidr := net.CIDRMask(32, 32) ipAddr, err := netip.ParseAddr(addr) diff --git a/logic/relay.go b/logic/relay.go index 89d0138b..03b7eb33 100644 --- a/logic/relay.go +++ b/logic/relay.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "time" + "net" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" @@ -31,8 +31,7 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) return returnnodes, models.Node{}, err } node.IsRelay = true - node.RelayAddrs = relay.RelayAddrs - + node.RelayedNodes = relay.RelayedNodes node.SetLastModified() nodeData, err := json.Marshal(&node) if err != nil { @@ -41,144 +40,98 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil { return returnnodes, models.Node{}, err } - returnnodes, err = SetRelayedNodes(true, node.Network, node.RelayAddrs) - if err != nil { - return returnnodes, node, err + returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes) + for _, relayedNode := range returnnodes { + data, err := json.Marshal(&relayedNode) + if err != nil { + logger.Log(0, "marshalling relayed node", err.Error()) + continue + } + if err := database.Insert(relayedNode.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil { + logger.Log(0, "inserting relayed node", err.Error()) + continue + } } return returnnodes, node, nil } -// CreateHostRelay - creates a host relay -func CreateHostRelay(relay models.HostRelayRequest) (relayHost *models.Host, relayedHosts []models.Host, err error) { - - relayHost, err = GetHost(relay.HostID) - if err != nil { - return - } - err = validateHostRelay(relay) - if err != nil { - return - } - relayHost.IsRelay = true - relayHost.ProxyEnabled = true - relayHost.RelayedHosts = relay.RelayedHosts - err = UpsertHost(relayHost) - if err != nil { - return - } - relayedHosts = SetRelayedHosts(true, relay.HostID, relay.RelayedHosts) - return -} - -// SetRelayedHosts - updates the relayed hosts status -func SetRelayedHosts(setRelayed bool, relayHostID string, relayedHostIDs []string) []models.Host { - var relayedHosts []models.Host - for _, relayedHostID := range relayedHostIDs { - host, err := GetHost(relayedHostID) - if err == nil { - if setRelayed { - host.IsRelayed = true - host.RelayedBy = relayHostID - host.ProxyEnabled = true - } else { - host.IsRelayed = false - host.RelayedBy = "" - } - err = UpsertHost(host) - if err == nil { - relayedHosts = append(relayedHosts, *host) - } - } - } - return relayedHosts -} - -// SetRelayedNodes- set relayed nodes -func SetRelayedNodes(setRelayed bool, networkName string, addrs []string) ([]models.Node, error) { +// SetRelayedNodes- sets and saves node as relayed +func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.Node { var returnnodes []models.Node - networkNodes, err := GetNetworkNodes(networkName) - if err != nil { - return returnnodes, err - } - for _, node := range networkNodes { - for _, addr := range addrs { - if addr == node.Address.IP.String() || addr == node.Address6.IP.String() { - if setRelayed { - node.IsRelayed = true - } else { - node.IsRelayed = false - } - data, err := json.Marshal(&node) - if err != nil { - return returnnodes, err - } - database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME) - returnnodes = append(returnnodes, node) - } + for _, id := range relayed { + node, err := GetNodeByID(id) + if err != nil { + logger.Log(0, "setRelayedNodes.GetNodebyID", err.Error()) + continue } - } - return returnnodes, nil -} -func GetRelayedNodes(relayNode *models.Node) ([]models.Node, error) { - var returnnodes []models.Node - networkNodes, err := GetNetworkNodes(relayNode.Network) - if err != nil { - return returnnodes, err - } - for _, node := range networkNodes { - for _, addr := range relayNode.RelayAddrs { - if addr == node.Address.IP.String() || addr == node.Address6.IP.String() { - returnnodes = append(returnnodes, node) - } + node.IsRelayed = setRelayed + if node.IsRelayed { + node.RelayedBy = relay + } else { + node.RelayedBy = "" } + node.SetLastModified() + data, err := json.Marshal(&node) + if err != nil { + logger.Log(0, "setRelayedNodes.Marshal", err.Error()) + continue + } + if err := database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil { + logger.Log(0, "setRelayedNodes.Insert", err.Error()) + continue + } + returnnodes = append(returnnodes, node) } - return returnnodes, nil + return returnnodes } -// GetRelayedHosts - gets the relayed hosts of a relay host -func GetRelayedHosts(relayHost *models.Host) []models.Host { - relayedHosts := []models.Host{} - - for _, hostID := range relayHost.RelayedHosts { - relayedHost, err := GetHost(hostID) - if err == nil { - relayedHosts = append(relayedHosts, *relayedHost) - } - } - return relayedHosts -} +//func GetRelayedNodes(relayNode *models.Node) (models.Node, error) { +// var returnnodes []models.Node +// networkNodes, err := GetNetworkNodes(relayNode.Network) +// if err != nil { +// return returnnodes, err +// } +// for _, node := range networkNodes { +// for _, addr := range relayNode.RelayAddrs { +// if addr == node.Address.IP.String() || addr == node.Address6.IP.String() { +// returnnodes = append(returnnodes, node) +// } +// } +// } +// return returnnodes, nil +//} // ValidateRelay - checks if relay is valid func ValidateRelay(relay models.RelayRequest) error { var err error //isIp := functions.IsIpCIDR(gateway.RangeString) - empty := len(relay.RelayAddrs) == 0 + empty := len(relay.RelayedNodes) == 0 if empty { - err = errors.New("IP Ranges Cannot Be Empty") + return errors.New("IP Ranges Cannot Be Empty") + } + node, err := GetNodeByID(relay.NodeID) + if err != nil { + return err + } + if node.IsRelay { + return errors.New("node is already acting as a relay") + } + for _, relayedNodeID := range relay.RelayedNodes { + relayedNode, err := GetNodeByID(relayedNodeID) + if err != nil { + return err + } + if relayedNode.IsIngressGateway { + return errors.New("cannot relay an ingress gateway (" + relayedNodeID + ")") + } } return err } -func validateHostRelay(relay models.HostRelayRequest) error { - if len(relay.RelayedHosts) == 0 { - return errors.New("relayed hosts are empty") - } - return nil -} - -// UpdateRelay - updates a relay -func UpdateRelay(network string, oldAddrs []string, newAddrs []string) []models.Node { - var returnnodes []models.Node - time.Sleep(time.Second / 4) - _, err := SetRelayedNodes(false, network, oldAddrs) - if err != nil { - logger.Log(1, err.Error()) - } - returnnodes, err = SetRelayedNodes(true, network, newAddrs) - if err != nil { - logger.Log(1, err.Error()) - } - return returnnodes +// UpdateRelayed - updates relay nodes +func UpdateRelayed(relay string, oldNodes []string, newNodes []string) []models.Node { + _ = SetRelayedNodes(false, relay, oldNodes) + return SetRelayedNodes(true, relay, newNodes) } // DeleteRelay - deletes a relay @@ -188,15 +141,10 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) { if err != nil { return returnnodes, models.Node{}, err } - returnnodes, err = SetRelayedNodes(false, node.Network, node.RelayAddrs) - if err != nil { - return returnnodes, node, err - } - + returnnodes = SetRelayedNodes(false, nodeid, node.RelayedNodes) node.IsRelay = false - node.RelayAddrs = []string{} + node.RelayedNodes = []string{} node.SetLastModified() - data, err := json.Marshal(&node) if err != nil { return returnnodes, models.Node{}, err @@ -207,24 +155,20 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) { return returnnodes, node, nil } -// DeleteHostRelay - removes host as relay -func DeleteHostRelay(relayHostID string) (relayHost *models.Host, relayedHosts []models.Host, err error) { - relayHost, err = GetHost(relayHostID) +func getRelayedAddresses(id string) []net.IPNet { + addrs := []net.IPNet{} + node, err := GetNodeByID(id) if err != nil { - return + logger.Log(0, "getRelayedAddresses: "+err.Error()) + return addrs } - relayedHosts = SetRelayedHosts(false, relayHostID, relayHost.RelayedHosts) - relayHost.IsRelay = false - relayHost.RelayedHosts = []string{} - err = UpsertHost(relayHost) - if err != nil { - return + if node.Address.IP != nil { + node.Address.Mask = net.CIDRMask(32, 32) + addrs = append(addrs, node.Address) } - return -} - -// UpdateHostRelay - updates the relay host with new relayed hosts -func UpdateHostRelay(relayHostID string, oldRelayedHosts, newRelayedHosts []string) { - _ = SetRelayedHosts(false, relayHostID, oldRelayedHosts) - _ = SetRelayedHosts(true, relayHostID, newRelayedHosts) + if node.Address6.IP != nil { + node.Address.Mask = net.CIDRMask(128, 128) + addrs = append(addrs, node.Address6) + } + return addrs } diff --git a/logic/serverconf.go b/logic/serverconf.go index 01c39346..4d73980d 100644 --- a/logic/serverconf.go +++ b/logic/serverconf.go @@ -4,17 +4,18 @@ import ( "encoding/json" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/servercfg" ) var ( - // Node_Limit - dummy var for community - Node_Limit = 1000000000 // Networks_Limit - dummy var for community Networks_Limit = 1000000000 // Users_Limit - dummy var for community Users_Limit = 1000000000 // Clients_Limit - dummy var for community Clients_Limit = 1000000000 + // Hosts_Limit - dummy var for community + Hosts_Limit = 1000000000 // Free_Tier - specifies if free tier Free_Tier = false ) @@ -85,3 +86,11 @@ func StoreJWTSecret(privateKey string) error { } return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME) } + +func SetFreeTierLimits() { + Free_Tier = true + Users_Limit = servercfg.GetUserLimit() + Clients_Limit = servercfg.GetClientLimit() + Networks_Limit = servercfg.GetNetworkLimit() + Hosts_Limit = servercfg.GetHostLimit() +} diff --git a/logic/telemetry.go b/logic/telemetry.go index 4b9e8635..e867cbc8 100644 --- a/logic/telemetry.go +++ b/logic/telemetry.go @@ -60,6 +60,7 @@ func sendTelemetry() error { Event: "daily checkin", Properties: posthog.NewProperties(). Set("nodes", d.Nodes). + Set("hosts", d.Hosts). Set("servers", d.Servers). Set("non-server nodes", d.Count.NonServer). Set("extclients", d.ExtClients). @@ -84,6 +85,7 @@ func fetchTelemetryData() (telemetryData, error) { data.ExtClients = getDBLength(database.EXT_CLIENT_TABLE_NAME) data.Users = getDBLength(database.USERS_TABLE_NAME) data.Networks = getDBLength(database.NETWORKS_TABLE_NAME) + data.Hosts = getDBLength(database.HOSTS_TABLE_NAME) data.Version = servercfg.GetVersion() data.Servers = getServerCount() nodes, err := GetAllNodes() @@ -167,6 +169,7 @@ func getDBLength(dbname string) int { // telemetryData - What data to send to posthog type telemetryData struct { Nodes int + Hosts int ExtClients int Users int Count clientCount diff --git a/logic/timer.go b/logic/timer.go index 0ea8d1db..89070f2f 100644 --- a/logic/timer.go +++ b/logic/timer.go @@ -1,10 +1,13 @@ package logic import ( + "context" "fmt" + "sync" "time" "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/models" ) // == Constants == @@ -12,6 +15,9 @@ import ( // How long to wait before sending telemetry to server (24 hours) const timer_hours_between_runs = 24 +// HookManagerCh - channel to add any new hooks +var HookManagerCh = make(chan models.HookDetails, 2) + // == Public == // TimerCheckpoint - Checks if 24 hours has passed since telemetry was last sent. If so, sends telemetry data to posthog @@ -40,6 +46,36 @@ func AddHook(ifaceToAdd interface{}) { timeHooks = append(timeHooks, ifaceToAdd) } +// StartHookManager - listens on `HookManagerCh` to run any hook +func StartHookManager(ctx context.Context, wg *sync.WaitGroup) { + defer wg.Done() + for { + select { + case <-ctx.Done(): + logger.Log(0, "## Stopping Hook Manager") + return + case newhook := <-HookManagerCh: + wg.Add(1) + go addHookWithInterval(ctx, wg, newhook.Hook, newhook.Interval) + } + } +} + +func addHookWithInterval(ctx context.Context, wg *sync.WaitGroup, hook func() error, interval time.Duration) { + defer wg.Done() + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + hook() + } + } + +} + // == private == // timeHooks - functions to run once a day, functions must take no parameters diff --git a/logic/users.go b/logic/users.go index 4581d852..cbbade5e 100644 --- a/logic/users.go +++ b/logic/users.go @@ -26,6 +26,30 @@ func GetUser(username string) (*models.User, error) { return &user, err } +// GetReturnUser - gets a user +func GetReturnUser(username string) (models.ReturnUser, error) { + + var user models.ReturnUser + record, err := database.FetchRecord(database.USERS_TABLE_NAME, username) + if err != nil { + return user, err + } + if err = json.Unmarshal([]byte(record), &user); err != nil { + return models.ReturnUser{}, err + } + return user, err +} + +// ToReturnUser - gets a user as a return user +func ToReturnUser(user models.User) models.ReturnUser { + return models.ReturnUser{ + UserName: user.UserName, + Networks: user.Networks, + IsAdmin: user.IsAdmin, + Groups: user.Groups, + } +} + // GetGroupUsers - gets users in a group func GetGroupUsers(group string) ([]models.ReturnUser, error) { var returnUsers []models.ReturnUser diff --git a/logic/wireguard.go b/logic/wireguard.go index c26c7217..7ef6cbab 100644 --- a/logic/wireguard.go +++ b/logic/wireguard.go @@ -29,11 +29,11 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool { } } if newNode.IsRelay { - if len(currentNode.RelayAddrs) != len(newNode.RelayAddrs) { + if len(currentNode.RelayedNodes) != len(newNode.RelayedNodes) { return true } - for _, address := range newNode.RelayAddrs { - if !StringSliceContains(currentNode.RelayAddrs, address) { + for _, node := range newNode.RelayedNodes { + if !StringSliceContains(currentNode.RelayedNodes, node) { return true } } diff --git a/main.go b/main.go index 3729c8dd..66041f55 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ import ( "golang.org/x/exp/slog" ) -var version = "v0.20.2" +var version = "v0.20.3" // Start DB Connection and start API Request Handler func main() { @@ -41,6 +41,9 @@ func main() { initialize() // initial db and acls setGarbageCollection() setVerbosity() + if servercfg.DeployedByOperator() && !servercfg.Is_EE { + logic.SetFreeTierLimits() + } defer database.CloseDB() ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) defer stop() @@ -88,7 +91,6 @@ func initialize() { // Client Mode Prereq Check if err != nil { logger.Log(1, "Timer error occurred: ", err.Error()) } - logic.EnterpriseCheck() var authProvider = auth.InitializeAuthProvider() @@ -146,6 +148,8 @@ func startControllers(wg *sync.WaitGroup, ctx context.Context) { logger.Log(0, "No Server Mode selected, so nothing is being served! Set Rest mode (REST_BACKEND) or MessageQueue (MESSAGEQUEUE_BACKEND) to 'true'.") } + wg.Add(1) + go logic.StartHookManager(ctx, wg) } // Should we be using a context vice a waitgroup???????????? diff --git a/models/api_host.go b/models/api_host.go index 36ab6e72..abf29555 100644 --- a/models/api_host.go +++ b/models/api_host.go @@ -34,6 +34,7 @@ type ApiHost struct { 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"` } // Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host @@ -67,10 +68,7 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost { a.Verbosity = h.Verbosity a.Version = h.Version a.IsDefault = h.IsDefault - a.IsRelay = h.IsRelay - a.RelayedHosts = h.RelayedHosts - a.IsRelayed = h.IsRelayed - a.RelayedBy = h.RelayedBy + a.NatType = h.NatType return &a } @@ -108,10 +106,6 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host { h.Nodes = currentHost.Nodes h.TrafficKeyPublic = currentHost.TrafficKeyPublic h.OS = currentHost.OS - h.RelayedBy = a.RelayedBy - h.RelayedHosts = a.RelayedHosts - h.IsRelay = a.IsRelay - h.IsRelayed = a.IsRelayed h.ProxyEnabled = a.ProxyEnabled h.IsDefault = a.IsDefault h.NatType = currentHost.NatType diff --git a/models/api_node.go b/models/api_node.go index f78a9d6f..843b0bf2 100644 --- a/models/api_node.go +++ b/models/api_node.go @@ -25,11 +25,12 @@ type ApiNode struct { NetworkRange6 string `json:"networkrange6"` IsRelayed bool `json:"isrelayed"` IsRelay bool `json:"isrelay"` + RelayedBy string `json:"relayedby" bson:"relayedby" yaml:"relayedby"` + RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"` IsEgressGateway bool `json:"isegressgateway"` IsIngressGateway bool `json:"isingressgateway"` EgressGatewayRanges []string `json:"egressgatewayranges"` EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"` - RelayAddrs []string `json:"relayaddrs"` FailoverNode string `json:"failovernode"` DNSOn bool `json:"dnson"` IngressDns string `json:"ingressdns"` @@ -53,6 +54,8 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node { convertedNode.HostID, _ = uuid.Parse(a.HostID) convertedNode.IsRelay = a.IsRelay convertedNode.IsRelayed = a.IsRelayed + convertedNode.RelayedBy = a.RelayedBy + convertedNode.RelayedNodes = a.RelayedNodes convertedNode.PendingDelete = a.PendingDelete convertedNode.Failover = a.Failover convertedNode.IsEgressGateway = a.IsEgressGateway @@ -66,7 +69,7 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node { convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled convertedNode.PersistentKeepalive = time.Second * time.Duration(a.PersistentKeepalive) - convertedNode.RelayAddrs = a.RelayAddrs + convertedNode.RelayedNodes = a.RelayedNodes convertedNode.DefaultACL = a.DefaultACL convertedNode.OwnerID = currentNode.OwnerID _, networkRange, err := net.ParseCIDR(a.NetworkRange) @@ -140,11 +143,12 @@ func (nm *Node) ConvertToAPINode() *ApiNode { } apiNode.IsRelayed = nm.IsRelayed apiNode.IsRelay = nm.IsRelay + apiNode.RelayedBy = nm.RelayedBy + apiNode.RelayedNodes = nm.RelayedNodes apiNode.IsEgressGateway = nm.IsEgressGateway apiNode.IsIngressGateway = nm.IsIngressGateway apiNode.EgressGatewayRanges = nm.EgressGatewayRanges apiNode.EgressGatewayNatEnabled = nm.EgressGatewayNatEnabled - apiNode.RelayAddrs = nm.RelayAddrs apiNode.FailoverNode = nm.FailoverNode.String() if isUUIDSet(apiNode.FailoverNode) { apiNode.FailoverNode = "" diff --git a/models/mqtt.go b/models/mqtt.go index 30e1c7b3..8ac6b63a 100644 --- a/models/mqtt.go +++ b/models/mqtt.go @@ -8,18 +8,20 @@ import ( // HostPeerUpdate - struct for host peer updates type HostPeerUpdate struct { - Host Host `json:"host" bson:"host" yaml:"host"` - Server string `json:"server" bson:"server" yaml:"server"` - ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"` - ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"` - NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"` - Peers []wgtypes.PeerConfig - HostPeerIDs HostPeerMap `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"` - ProxyUpdate ProxyManagerPayload `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"` - EgressInfo map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID - IngressInfo IngressInfo `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"` - PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"` - HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"` + Host Host `json:"host" bson:"host" yaml:"host"` + NodeAddrs []net.IPNet `json:"nodes_addrs" yaml:"nodes_addrs"` + Server string `json:"server" bson:"server" yaml:"server"` + ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"` + ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"` + NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"` + Peers []wgtypes.PeerConfig + HostPeerIDs HostPeerMap `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"` + ProxyUpdate ProxyManagerPayload `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"` + EgressInfo map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID + IngressInfo IngressInfo `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"` + PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"` + EndpointDetection bool `json:"endpointdetection" yaml:"endpointdetection"` + HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"` } // IngressInfo - struct for ingress info diff --git a/models/node.go b/models/node.go index 517dd747..2153627f 100644 --- a/models/node.go +++ b/models/node.go @@ -69,6 +69,10 @@ type CommonNode struct { 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"` @@ -86,9 +90,6 @@ type Node struct { EgressGatewayRequest EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"` IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"` IngressGatewayRange6 string `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"` - IsRelayed bool `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"` - IsRelay bool `json:"isrelay" bson:"isrelay" yaml:"isrelay"` - RelayAddrs []string `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"` // == PRO == DefaultACL string `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"` OwnerID string `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"` @@ -180,6 +181,14 @@ func isLess(ipA string, ipB string) bool { return bytes.Compare(ipNetA, ipNetB) < 0 } +// Node.PrimaryAddress - return ipv4 address if present, else return ipv6 +func (node *Node) PrimaryAddressIPNet() net.IPNet { + if node.Address.IP != nil { + return node.Address + } + return node.Address6 +} + // Node.PrimaryAddress - return ipv4 address if present, else return ipv6 func (node *Node) PrimaryAddress() string { if node.Address.IP != nil { @@ -350,7 +359,7 @@ func (node *LegacyNode) SetDefaultFailover() { } // Node.Fill - fills other node data into calling node data if not set on calling node -func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftables present +func (newNode *Node) Fill(currentNode *Node, isEE bool) { // TODO add new field for nftables present newNode.ID = currentNode.ID newNode.HostID = currentNode.HostID // Revisit the logic for boolean values @@ -401,13 +410,13 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable if newNode.Action == "" { newNode.Action = currentNode.Action } - if newNode.RelayAddrs == nil { - newNode.RelayAddrs = currentNode.RelayAddrs + if newNode.RelayedNodes == nil { + newNode.RelayedNodes = currentNode.RelayedNodes } - if newNode.IsRelay != currentNode.IsRelay { + if newNode.IsRelay != currentNode.IsRelay && isEE { newNode.IsRelay = currentNode.IsRelay } - if newNode.IsRelayed == currentNode.IsRelayed { + if newNode.IsRelayed == currentNode.IsRelayed && isEE { newNode.IsRelayed = currentNode.IsRelayed } if newNode.Server == "" { diff --git a/models/structs.go b/models/structs.go index 35b521bc..bff547c3 100644 --- a/models/structs.go +++ b/models/structs.go @@ -2,6 +2,7 @@ package models import ( "strings" + "time" jwt "github.com/golang-jwt/jwt/v4" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -153,9 +154,9 @@ type EgressGatewayRequest struct { // RelayRequest - relay request struct type RelayRequest struct { - NodeID string `json:"nodeid" bson:"nodeid"` - NetID string `json:"netid" bson:"netid"` - RelayAddrs []string `json:"relayaddrs" bson:"relayaddrs"` + NodeID string `json:"nodeid"` + NetID string `json:"netid"` + RelayedNodes []string `json:"relayaddrs"` } // HostRelayRequest - struct for host relay creation @@ -274,3 +275,18 @@ 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 + Interval time.Duration +} + +// LicenseLimits - struct license limits +type LicenseLimits struct { + Servers int `json:"servers"` + Users int `json:"users"` + Hosts int `json:"hosts"` + Clients int `json:"clients"` + Networks int `json:"networks"` +} diff --git a/mq/emqx.go b/mq/emqx.go index cd6eb2d6..27776fe3 100644 --- a/mq/emqx.go +++ b/mq/emqx.go @@ -6,11 +6,14 @@ import ( "fmt" "io" "net/http" + "strings" "sync" "github.com/gravitl/netmaker/servercfg" ) +const already_exists = "ALREADY_EXISTS" + type ( emqxUser struct { UserID string `json:"user_id"` @@ -99,7 +102,9 @@ func CreateEmqxUser(username, password string, admin bool) error { if err != nil { return err } - return fmt.Errorf("error creating EMQX user %v", string(msg)) + if !strings.Contains(string(msg), already_exists) { + return fmt.Errorf("error creating EMQX user %v", string(msg)) + } } return nil } diff --git a/mq/handlers.go b/mq/handlers.go index cf56f31d..75d90b80 100644 --- a/mq/handlers.go +++ b/mq/handlers.go @@ -240,7 +240,7 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) { } } } - slog.Info("updated node metrics", "id", id) + slog.Debug("updated node metrics", "id", id) } } diff --git a/mq/publishers.go b/mq/publishers.go index ec5eb059..80e7c76f 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -88,18 +88,20 @@ func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deleted if len(peerUpdate.Peers) == 0 { // no peers to send return nil } - proxyUpdate, err := logic.GetProxyUpdateForHost(ctx, host) - if err != nil { - return err - } - proxyUpdate.Server = servercfg.GetServer() - if host.ProxyEnabled { - proxyUpdate.Action = models.ProxyUpdate - } else { - proxyUpdate.Action = models.NoProxy - } + if host.OS != models.OS_Types.IoT { + proxyUpdate, err := logic.GetProxyUpdateForHost(ctx, host) + if err != nil { + return err + } + proxyUpdate.Server = servercfg.GetServer() + if host.ProxyEnabled { + proxyUpdate.Action = models.ProxyUpdate + } else { + proxyUpdate.Action = models.NoProxy + } - peerUpdate.ProxyUpdate = proxyUpdate + peerUpdate.ProxyUpdate = proxyUpdate + } data, err := json.Marshal(&peerUpdate) if err != nil { diff --git a/release.md b/release.md index f3c59b98..1ba5b70e 100644 --- a/release.md +++ b/release.md @@ -1,5 +1,5 @@ -# Netmaker v0.20.2 +# Netmaker v0.20.3 ## whats new - diff --git a/scripts/netmaker.default.env b/scripts/netmaker.default.env index a852d6e5..b99f4818 100644 --- a/scripts/netmaker.default.env +++ b/scripts/netmaker.default.env @@ -19,6 +19,7 @@ NETMAKER_ACCOUNT_ID= LICENSE_KEY= SERVER_IMAGE_TAG= UI_IMAGE_TAG= +NETCLIENT_ENDPOINT_DETECTION="disabled" # used for HA - identifies this server vs other servers NODE_ID="netmaker-server-1" METRICS_EXPORTER="off" diff --git a/scripts/nm-quick.sh b/scripts/nm-quick.sh index 42b3f1fb..8ae3d80a 100755 --- a/scripts/nm-quick.sh +++ b/scripts/nm-quick.sh @@ -4,7 +4,7 @@ CONFIG_FILE=netmaker.env # location of nm-quick.sh (usually `/root`) SCRIPT_DIR=$(dirname "$(realpath "$0")") CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE" -NM_QUICK_VERSION="0.1.0" +NM_QUICK_VERSION="0.1.1" LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\") if [ $(id -u) -ne 0 ]; then @@ -17,11 +17,12 @@ unset BUILD_TYPE unset BUILD_TAG unset IMAGE_TAG unset AUTO_BUILD +unset NETMAKER_BASE_DOMAIN # usage - displays usage instructions usage() { echo "nm-quick.sh v$NM_QUICK_VERSION" - echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto]" + echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto] [-d domain]" echo " -e if specified, will install netmaker EE" echo " -b type of build; options:" echo " \"version\" - will install a specific version of Netmaker using remote git and dockerhub" @@ -29,14 +30,16 @@ usage() { echo " \"branch\": - will install a specific branch using remote git and dockerhub" echo " -t tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch" echo " -a auto-build; skip prompts and use defaults, if none provided" + echo " -d domain; if specified, will use this domain instead of auto-generating one" echo "examples:" echo " nm-quick.sh -e -b version -t $LATEST" echo " nm-quick.sh -e -b local -t feature_v0.17.2_newfeature" echo " nm-quick.sh -e -b branch -t develop" + echo " nm-quick.sh -e -b version -t $LATEST -a -d example.com" exit 1 } -while getopts evab:t: flag; do +while getopts evab:d:t: flag; do case "${flag}" in e) INSTALL_TYPE="ee" @@ -60,6 +63,9 @@ while getopts evab:t: flag; do t) BUILD_TAG=${OPTARG} ;; + d) + NETMAKER_BASE_DOMAIN=${OPTARG} + ;; esac done @@ -303,9 +309,9 @@ save_config() { ( local toCopy=("SERVER_HOST" "MASTER_KEY" "TURN_USERNAME" "TURN_PASSWORD" "MQ_USERNAME" "MQ_PASSWORD" "INSTALL_TYPE" "NODE_ID" "METRICS_EXPORTER" "PROMETHEUS" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT" "CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY" - "DEFAULT_PROXY_MODE" "TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" - "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET" "FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" - "EXPORTER_API_PORT") + "DEFAULT_PROXY_MODE" "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") for name in "${toCopy[@]}"; do save_config_item $name "${!name}" done @@ -490,8 +496,9 @@ set_install_vars() { if [ "$IP_ADDR" = "" ]; then IP_ADDR=$(curl -s ifconfig.me) fi - - NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io + if [ "$NETMAKER_BASE_DOMAIN" = "" ]; then + NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io + fi SERVER_HOST=$IP_ADDR if test -z "$MASTER_KEY"; then MASTER_KEY=$( diff --git a/scripts/nm-upgrade-0-17-1-to-0-19-0.sh b/scripts/nm-upgrade-0-17-1-to-0-19-0.sh index 3df9fca9..7804bb5a 100644 --- a/scripts/nm-upgrade-0-17-1-to-0-19-0.sh +++ b/scripts/nm-upgrade-0-17-1-to-0-19-0.sh @@ -1,6 +1,6 @@ #!/bin/bash -LATEST="v0.20.2" +LATEST="v0.20.3" INSTALL_PATH="/root" trap restore_old_netmaker_instructions diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 79d64cc1..185fd870 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gravitl/netmaker/config" + "github.com/gravitl/netmaker/models" ) @@ -51,6 +52,11 @@ func GetServerConfig() config.ServerConfig { } else { cfg.NetclientAutoUpdate = "disabled" } + if EndpointDetectionEnabled() { + cfg.NetclientEndpointDetection = "enabled" + } else { + cfg.NetclientEndpointDetection = "disabled" + } if IsRestBackend() { cfg.RestBackend = "on" } @@ -432,6 +438,17 @@ func AutoUpdateEnabled() bool { return true } +// EndpointDetectionEnabled returns a boolean indicating whether netclient endpoint detection is enabled or disabled +// default is enabled +func EndpointDetectionEnabled() bool { + if os.Getenv("NETCLIENT_ENDPOINT_DETECTION") == "disabled" { + return false + } else if config.Config.Server.NetclientEndpointDetection == "disabled" { + return false + } + return true +} + // IsDNSMode - should it run with DNS func IsDNSMode() bool { isdns := true @@ -725,6 +742,58 @@ func IsProxyEnabled() bool { return enabled } +// GetNetworkLimit - fetches free tier limits on users +func GetUserLimit() int { + var userslimit int + if os.Getenv("USERS_LIMIT") != "" { + userslimit, _ = strconv.Atoi(os.Getenv("USERS_LIMIT")) + } else { + userslimit = config.Config.Server.UsersLimit + } + return userslimit +} + +// GetNetworkLimit - fetches free tier limits on networks +func GetNetworkLimit() int { + var networkslimit int + if os.Getenv("NETWORKS_LIMIT") != "" { + networkslimit, _ = strconv.Atoi(os.Getenv("NETWORKS_LIMIT")) + } else { + networkslimit = config.Config.Server.NetworksLimit + } + return networkslimit +} + +// GetClientLimit - fetches free tier limits on ext. clients +func GetClientLimit() int { + var clientsLimit int + if os.Getenv("CLIENTS_LIMIT") != "" { + clientsLimit, _ = strconv.Atoi(os.Getenv("CLIENTS_LIMIT")) + } else { + clientsLimit = config.Config.Server.ClientsLimit + } + return clientsLimit +} + +// GetHostLimit - fetches free tier limits on hosts +func GetHostLimit() int { + var hostsLimit int + if os.Getenv("HOSTS_LIMIT") != "" { + hostsLimit, _ = strconv.Atoi(os.Getenv("HOSTS_LIMIT")) + } else { + hostsLimit = config.Config.Server.HostsLimit + } + return hostsLimit +} + +// DeployedByOperator - returns true if the instance is deployed by netmaker operator +func DeployedByOperator() bool { + if os.Getenv("DEPLOYED_BY_OPERATOR") != "" { + return os.Getenv("DEPLOYED_BY_OPERATOR") == "true" + } + return config.Config.Server.DeployedByOperator +} + // GetDefaultProxyMode - default proxy mode for a server func GetDefaultProxyMode() config.ProxyMode { var ( diff --git a/swagger.yaml b/swagger.yaml index d4ab4f59..88ac6c2a 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -704,7 +704,7 @@ info: API calls must be authenticated via a header of the format -H “Authorization: Bearer ” 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.20.2 + version: 0.20.3 paths: /api/dns: get: