From 5665fa42e55e23788a3015d94137c4bfff95aba2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:12:45 +0000 Subject: [PATCH 01/52] Bump github.com/urfave/cli/v2 from 2.14.1 to 2.16.3 Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.14.1 to 2.16.3. - [Release notes](https://github.com/urfave/cli/releases) - [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/urfave/cli/compare/v2.14.1...v2.16.3) --- updated-dependencies: - dependency-name: github.com/urfave/cli/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 03f994b7..13c56b72 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.8.0 github.com/txn2/txeh v1.3.0 - github.com/urfave/cli/v2 v2.14.1 + github.com/urfave/cli/v2 v2.16.3 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect diff --git a/go.sum b/go.sum index 79bb120f..fef47d49 100644 --- a/go.sum +++ b/go.sum @@ -448,8 +448,8 @@ github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= -github.com/urfave/cli/v2 v2.14.1 h1:0Sx+C9404t2+DPuIJ3UpZFOEFhNG3wPxMj7uZHyZKFA= -github.com/urfave/cli/v2 v2.14.1/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk= +github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= From ac8c29488cd839e344f5e52012239b6ac018c851 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:12:50 +0000 Subject: [PATCH 02/52] Bump github.com/lib/pq from 1.10.6 to 1.10.7 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.6 to 1.10.7. - [Release notes](https://github.com/lib/pq/releases) - [Commits](https://github.com/lib/pq/compare/v1.10.6...v1.10.7) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 03f994b7..94b2771f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/lib/pq v1.10.6 + github.com/lib/pq v1.10.7 github.com/mattn/go-sqlite3 v1.14.15 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e diff --git a/go.sum b/go.sum index 79bb120f..569889ef 100644 --- a/go.sum +++ b/go.sum @@ -325,8 +325,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= From b49ff4895ff201a2c84b60d74501c533ff7b3101 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:12:56 +0000 Subject: [PATCH 03/52] Bump github.com/coreos/go-oidc/v3 from 3.3.0 to 3.4.0 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.3.0 to 3.4.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.3.0...v3.4.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 03f994b7..f23eec52 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( ) require ( - github.com/coreos/go-oidc/v3 v3.3.0 + github.com/coreos/go-oidc/v3 v3.4.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e ) diff --git a/go.sum b/go.sum index 79bb120f..39331cae 100644 --- a/go.sum +++ b/go.sum @@ -103,8 +103,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc/v3 v3.3.0 h1:Y1LV3mP+QT3MEycATZpAiwfyN+uxZLqVbAHJUuOJEe4= -github.com/coreos/go-oidc/v3 v3.3.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= +github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g= +github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= From f47d89ef629361a3a422891aa7d0df6b63a34556 Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Tue, 13 Sep 2022 13:56:47 -0400 Subject: [PATCH 04/52] remove unnecessary nft rule --- logic/gateway.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/logic/gateway.go b/logic/gateway.go index 83148b95..25945f3c 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "net" "strings" "time" @@ -334,12 +333,6 @@ func firewallNFTCommandsCreateEgress(networkInterface string, gatewayInterface s postUp += "nft add table nat ; " postUp += "nft 'add chain ip nat prerouting { type nat hook prerouting priority 0 ;}' ; " postUp += "nft 'add chain ip nat postrouting { type nat hook postrouting priority 0 ;}' ; " - for _, networkCIDR := range gatewayranges { - if net.ParseIP(networkCIDR).To16() != nil { - continue - } - postUp += "nft add rule nat postrouting iifname " + networkInterface + " oifname " + gatewayInterface + " ip saddr " + networkCIDR + " masquerade ; " - } postDown += "nft flush table filter ; " From a8f35e4980dca06c014b35f4792c32af70a0c3f6 Mon Sep 17 00:00:00 2001 From: cameronts Date: Tue, 13 Sep 2022 11:11:04 -0700 Subject: [PATCH 05/52] Versions for v0.15.2 --- .github/ISSUE_TEMPLATE/bug-report.yml | 3 ++- README.md | 2 +- compose/docker-compose.reference.yml | 6 +++--- compose/docker-compose.yml | 4 ++-- k8s/client/netclient-daemonset.yaml | 2 +- k8s/client/netclient.yaml | 4 ++-- k8s/server/netmaker-server.yaml | 4 ++-- k8s/server/netmaker-ui.yaml | 4 ++-- netclient/netclient.exe.manifest.xml | 2 +- netclient/versioninfo.json | 8 ++++---- 10 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index d41f694d..acfb1090 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -31,7 +31,8 @@ body: label: Version description: What version are you running? options: - - v0.15.1 + - v0.15.2 + - v0.15.1 - v0.15.0 - v0.14.6 - v0.14.5 diff --git a/README.md b/README.md index c037c69f..3b409658 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@

- + diff --git a/compose/docker-compose.reference.yml b/compose/docker-compose.reference.yml index 9ec7f103..3091d18f 100644 --- a/compose/docker-compose.reference.yml +++ b/compose/docker-compose.reference.yml @@ -3,7 +3,7 @@ version: "3.4" services: netmaker: # The Primary Server for running Netmaker container_name: netmaker - image: gravitl/netmaker:v0.15.1 + image: gravitl/netmaker:v0.15.2 cap_add: - NET_ADMIN - NET_RAW @@ -62,7 +62,7 @@ services: - traefik.http.services.netmaker-api.loadbalancer.server.port=8081 netmaker-ui: # The Netmaker UI Component container_name: netmaker-ui - image: gravitl/netmaker-ui:v0.15.1 + image: gravitl/netmaker-ui:v0.15.2 depends_on: - netmaker links: @@ -140,4 +140,4 @@ volumes: sqldata: {} # storage for embedded sqlite dnsconfig: {} # storage for coredns mosquitto_data: {} # storage for mqtt data - mosquitto_logs: {} # storage for mqtt logs \ No newline at end of file + mosquitto_logs: {} # storage for mqtt logs diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index d9d1c47e..a78aed34 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.4" services: netmaker: container_name: netmaker - image: gravitl/netmaker:v0.15.1 + image: gravitl/netmaker:v0.15.2 cap_add: - NET_ADMIN - NET_RAW @@ -51,7 +51,7 @@ services: - traefik.http.services.netmaker-api.loadbalancer.server.port=8081 netmaker-ui: container_name: netmaker-ui - image: gravitl/netmaker-ui:v0.15.1 + image: gravitl/netmaker-ui:v0.15.2 depends_on: - netmaker links: diff --git a/k8s/client/netclient-daemonset.yaml b/k8s/client/netclient-daemonset.yaml index 1f1ab484..c4c20ce5 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-go:v0.15.1 + image: gravitl/netclient-go:v0.15.2 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/client/netclient.yaml b/k8s/client/netclient.yaml index 4b47aa77..fa8307ff 100644 --- a/k8s/client/netclient.yaml +++ b/k8s/client/netclient.yaml @@ -28,7 +28,7 @@ spec: # - "" containers: - name: netclient - image: gravitl/netclient:v0.15.1 + image: gravitl/netclient:v0.15.2 env: - name: TOKEN value: "TOKEN_VALUE" @@ -41,4 +41,4 @@ spec: - hostPath: path: /etc/netclient type: DirectoryOrCreate - name: etc-netclient \ No newline at end of file + name: etc-netclient diff --git a/k8s/server/netmaker-server.yaml b/k8s/server/netmaker-server.yaml index e2c6070b..ed2530b5 100644 --- a/k8s/server/netmaker-server.yaml +++ b/k8s/server/netmaker-server.yaml @@ -83,7 +83,7 @@ spec: value: "Kubernetes" - name: VERBOSITY value: "3" - image: gravitl/netmaker:v0.15.1 + image: gravitl/netmaker:v0.15.2 imagePullPolicy: Always name: netmaker ports: @@ -225,4 +225,4 @@ spec: # service: # name: netmaker-rest # port: -# number: 8081 \ No newline at end of file +# number: 8081 diff --git a/k8s/server/netmaker-ui.yaml b/k8s/server/netmaker-ui.yaml index 87727e0d..326d94a0 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.15.1 + image: gravitl/netmaker-ui:v0.15.2 ports: - containerPort: 443 env: @@ -61,4 +61,4 @@ spec: # service: # name: netmaker-ui # port: -# number: 80 \ No newline at end of file +# number: 80 diff --git a/netclient/netclient.exe.manifest.xml b/netclient/netclient.exe.manifest.xml index 1f332fef..285f21be 100644 --- a/netclient/netclient.exe.manifest.xml +++ b/netclient/netclient.exe.manifest.xml @@ -1,7 +1,7 @@ Date: Tue, 13 Sep 2022 15:25:56 -0400 Subject: [PATCH 06/52] initial commit --- Dockerfile-quick | 14 + auth/auth.go | 53 ++- auth/azure-ad.go | 16 +- auth/github.go | 18 +- auth/google.go | 19 +- auth/nodecallback.go | 259 ++++++++++++++ auth/nodesession.go | 155 +++++++++ auth/oidc.go | 25 +- auth/templates.go | 81 +++++ compose/docker-compose.yml | 63 ++++ config/config.go | 4 + controllers/controller.go | 4 + controllers/ext_client.go | 133 +++++++- controllers/limits.go | 59 ++++ controllers/logger.go | 22 ++ controllers/metrics.go | 102 ++++++ controllers/network.go | 31 +- controllers/network_test.go | 35 +- controllers/networkusers.go | 359 ++++++++++++++++++++ controllers/node.go | 66 +++- controllers/security.go | 74 ++++ controllers/user.go | 44 ++- controllers/user_test.go | 28 +- controllers/usergroups.go | 71 ++++ database/database.go | 16 + docker/mosquitto.conf | 4 + docker/mosquitto.passwords | 1 + ee/LICENSE | 10 + ee/license.go | 216 ++++++++++++ ee/types.go | 87 +++++ ee/util.go | 54 +++ go.mod | 10 +- go.sum | 6 + logic/accesskeys.go | 6 +- logic/auth.go | 178 ++++++++-- logic/extpeers.go | 37 ++ logic/jwts.go | 24 ++ logic/metrics.go | 65 ++++ logic/networks.go | 39 ++- logic/nodes.go | 56 +++ logic/peers.go | 37 +- logic/pro/license.go | 66 ++++ logic/pro/metrics/metrics.go | 121 +++++++ logic/pro/netcache/netcache.go | 57 ++++ logic/pro/networks.go | 62 ++++ logic/pro/networks_test.go | 64 ++++ logic/pro/networkuser.go | 247 ++++++++++++++ logic/pro/networkuser_test.go | 98 ++++++ logic/pro/proacls/nodes.go | 35 ++ logic/pro/types.go | 20 ++ logic/pro/usergroups.go | 80 +++++ logic/pro/usergroups_test.go | 43 +++ logic/users.go | 50 +++ logic/util.go | 15 + main.go | 31 +- main_ee.go | 30 ++ models/extclient.go | 1 + models/metrics.go | 45 +++ models/mqtt.go | 1 + models/names.go | 11 + models/network.go | 47 +-- models/node.go | 19 ++ models/promodels/networkuser.go | 27 ++ models/promodels/pro.go | 19 ++ models/promodels/usergroups.go | 9 + models/structs.go | 14 +- mq/handlers.go | 91 +++++ mq/mq.go | 4 + mq/publishers.go | 76 ++++- netclient/cli_options/flags.go | 14 + netclient/command/commands.go | 20 ++ netclient/config/config.go | 6 + netclient/functions/join.go | 153 +++++++++ netclient/functions/mqpublish.go | 92 ++++- netclient/global_settings/globalsettings.go | 3 + scripts/nm-quick.sh | 2 + servercfg/serverconf.go | 44 +++ serverctl/serverctl.go | 3 + validation/validation.go | 5 + 79 files changed, 4146 insertions(+), 160 deletions(-) create mode 100644 Dockerfile-quick create mode 100644 auth/nodecallback.go create mode 100644 auth/nodesession.go create mode 100644 auth/templates.go create mode 100644 controllers/limits.go create mode 100644 controllers/logger.go create mode 100644 controllers/metrics.go create mode 100644 controllers/networkusers.go create mode 100644 controllers/usergroups.go create mode 100644 docker/mosquitto.passwords create mode 100644 ee/LICENSE create mode 100644 ee/license.go create mode 100644 ee/types.go create mode 100644 ee/util.go create mode 100644 logic/metrics.go create mode 100644 logic/pro/license.go create mode 100644 logic/pro/metrics/metrics.go create mode 100644 logic/pro/netcache/netcache.go create mode 100644 logic/pro/networks.go create mode 100644 logic/pro/networks_test.go create mode 100644 logic/pro/networkuser.go create mode 100644 logic/pro/networkuser_test.go create mode 100644 logic/pro/proacls/nodes.go create mode 100644 logic/pro/types.go create mode 100644 logic/pro/usergroups.go create mode 100644 logic/pro/usergroups_test.go create mode 100644 main_ee.go create mode 100644 models/metrics.go create mode 100644 models/promodels/networkuser.go create mode 100644 models/promodels/pro.go create mode 100644 models/promodels/usergroups.go diff --git a/Dockerfile-quick b/Dockerfile-quick new file mode 100644 index 00000000..08f5bc74 --- /dev/null +++ b/Dockerfile-quick @@ -0,0 +1,14 @@ +#first stage - builder +FROM alpine:3.15.2 +ARG version +WORKDIR /app +COPY ./netmaker /root/netmaker +ENV GO111MODULE=auto + +# add a c lib +RUN apk add gcompat iptables wireguard-tools +# set the working directory +WORKDIR /root/ +RUN mkdir -p /etc/netclient/config +EXPOSE 8081 +ENTRYPOINT ["./netmaker"] diff --git a/auth/auth.go b/auth/auth.go index bd7bcd3d..cdc2581b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -9,6 +9,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro/netcache" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" "golang.org/x/crypto/bcrypt" @@ -27,8 +28,19 @@ const ( oidc_provider_name = "oidc" verify_user = "verifyuser" auth_key = "netmaker_auth" + user_signin_length = 16 + node_signin_length = 64 ) +// OAuthUser - generic OAuth strategy user +type OAuthUser struct { + Name string `json:"name" bson:"name"` + Email string `json:"email" bson:"email"` + Login string `json:"login" bson:"login"` + UserPrincipalName string `json:"userPrincipalName" bson:"userPrincipalName"` + AccessToken string `json:"accesstoken" bson:"accesstoken"` +} + var auth_provider *oauth2.Config func getCurrentAuthFunctions() map[string]interface{} { @@ -94,7 +106,14 @@ func HandleAuthCallback(w http.ResponseWriter, r *http.Request) { if functions == nil { return } - functions[handle_callback].(func(http.ResponseWriter, *http.Request))(w, r) + state, _ := getStateAndCode(r) + _, err := netcache.Get(state) // if in netcache proceeed with node registration login + if err == nil || len(state) == node_signin_length || (err != nil && strings.Contains(err.Error(), "expired")) { + logger.Log(0, "proceeding with node SSO callback") + HandleNodeSSOCallback(w, r) + } else { // handle normal login + functions[handle_callback].(func(http.ResponseWriter, *http.Request))(w, r) + } } // swagger:route GET /api/oauth/login nodes HandleAuthLogin @@ -197,3 +216,35 @@ func fetchPassValue(newValue string) (string, error) { } return string(b64CurrentValue), nil } + +func getStateAndCode(r *http.Request) (string, string) { + var state, code string + if r.FormValue("state") != "" && r.FormValue("code") != "" { + state = r.FormValue("state") + code = r.FormValue("code") + } else if r.URL.Query().Get("state") != "" && r.URL.Query().Get("code") != "" { + state = r.URL.Query().Get("state") + code = r.URL.Query().Get("code") + } + + return state, code +} + +func (user *OAuthUser) getUserName() string { + var userName string + if user.Email != "" { + userName = user.Email + } else if user.Login != "" { + userName = user.Login + } else if user.UserPrincipalName != "" { + userName = user.UserPrincipalName + } else if user.Name != "" { + userName = user.Name + } + return userName +} + +func isStateCached(state string) bool { + _, err := netcache.Get(state) + return err == nil || strings.Contains(err.Error(), "expired") +} diff --git a/auth/azure-ad.go b/auth/azure-ad.go index b2931b50..ac150d60 100644 --- a/auth/azure-ad.go +++ b/auth/azure-ad.go @@ -23,11 +23,6 @@ var azure_ad_functions = map[string]interface{}{ verify_user: verifyAzureUser, } -type azureOauthUser struct { - UserPrincipalName string `json:"userPrincipalName" bson:"userPrincipalName"` - AccessToken string `json:"accesstoken" bson:"accesstoken"` -} - // == handle azure ad authentication here == func initAzureAD(redirectURL string, clientID string, clientSecret string) { @@ -41,7 +36,7 @@ func initAzureAD(redirectURL string, clientID string, clientSecret string) { } func handleAzureLogin(w http.ResponseWriter, r *http.Request) { - var oauth_state_string = logic.RandomString(16) + var oauth_state_string = logic.RandomString(user_signin_length) if auth_provider == nil && servercfg.GetFrontendURL() != "" { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) return @@ -61,7 +56,8 @@ func handleAzureLogin(w http.ResponseWriter, r *http.Request) { func handleAzureCallback(w http.ResponseWriter, r *http.Request) { - var content, err = getAzureUserInfo(r.FormValue("state"), r.FormValue("code")) + var rState, rCode = getStateAndCode(r) + var content, err = getAzureUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from azure:", err.Error()) http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) @@ -93,9 +89,9 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.UserPrincipalName, http.StatusPermanentRedirect) } -func getAzureUserInfo(state string, code string) (*azureOauthUser, error) { +func getAzureUserInfo(state string, code string) (*OAuthUser, error) { oauth_state_string, isValid := logic.IsStateValid(state) - if !isValid || state != oauth_state_string { + if (!isValid || state != oauth_state_string) && !isStateCached(state) { return nil, fmt.Errorf("invalid oauth state") } var token, err = auth_provider.Exchange(context.Background(), code) @@ -121,7 +117,7 @@ func getAzureUserInfo(state string, code string) (*azureOauthUser, error) { if err != nil { return nil, fmt.Errorf("failed reading response body: %s", err.Error()) } - var userInfo = &azureOauthUser{} + var userInfo = &OAuthUser{} if err = json.Unmarshal(contents, userInfo); err != nil { return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) } diff --git a/auth/github.go b/auth/github.go index 2bbdfdea..b333f50c 100644 --- a/auth/github.go +++ b/auth/github.go @@ -23,11 +23,6 @@ var github_functions = map[string]interface{}{ verify_user: verifyGithubUser, } -type githubOauthUser struct { - Login string `json:"login" bson:"login"` - AccessToken string `json:"accesstoken" bson:"accesstoken"` -} - // == handle github authentication here == func initGithub(redirectURL string, clientID string, clientSecret string) { @@ -41,7 +36,7 @@ func initGithub(redirectURL string, clientID string, clientSecret string) { } func handleGithubLogin(w http.ResponseWriter, r *http.Request) { - var oauth_state_string = logic.RandomString(16) + var oauth_state_string = logic.RandomString(user_signin_length) if auth_provider == nil && servercfg.GetFrontendURL() != "" { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) return @@ -61,7 +56,8 @@ func handleGithubLogin(w http.ResponseWriter, r *http.Request) { func handleGithubCallback(w http.ResponseWriter, r *http.Request) { - var content, err = getGithubUserInfo(r.URL.Query().Get("state"), r.URL.Query().Get("code")) + var rState, rCode = getStateAndCode(r) + var content, err = getGithubUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from github:", err.Error()) http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) @@ -93,10 +89,10 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Login, http.StatusPermanentRedirect) } -func getGithubUserInfo(state string, code string) (*githubOauthUser, error) { +func getGithubUserInfo(state string, code string) (*OAuthUser, error) { oauth_state_string, isValid := logic.IsStateValid(state) - if !isValid || state != oauth_state_string { - return nil, fmt.Errorf("invalid OAuth state") + if (!isValid || state != oauth_state_string) && !isStateCached(state) { + return nil, fmt.Errorf("invalid oauth state") } var token, err = auth_provider.Exchange(context.Background(), code) if err != nil { @@ -125,7 +121,7 @@ func getGithubUserInfo(state string, code string) (*githubOauthUser, error) { if err != nil { return nil, fmt.Errorf("failed reading response body: %s", err.Error()) } - var userInfo = &githubOauthUser{} + var userInfo = &OAuthUser{} if err = json.Unmarshal(contents, userInfo); err != nil { return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) } diff --git a/auth/google.go b/auth/google.go index 344c9938..22be2a47 100644 --- a/auth/google.go +++ b/auth/google.go @@ -24,11 +24,6 @@ var google_functions = map[string]interface{}{ verify_user: verifyGoogleUser, } -type googleOauthUser struct { - Email string `json:"email" bson:"email"` - AccessToken string `json:"accesstoken" bson:"accesstoken"` -} - // == handle google authentication here == func initGoogle(redirectURL string, clientID string, clientSecret string) { @@ -42,7 +37,7 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) { } func handleGoogleLogin(w http.ResponseWriter, r *http.Request) { - var oauth_state_string = logic.RandomString(16) + var oauth_state_string = logic.RandomString(user_signin_length) if auth_provider == nil && servercfg.GetFrontendURL() != "" { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) return @@ -62,7 +57,9 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) { func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { - var content, err = getGoogleUserInfo(r.FormValue("state"), r.FormValue("code")) + var rState, rCode = getStateAndCode(r) + + var content, err = getGoogleUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from google:", err.Error()) http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) @@ -94,10 +91,10 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect) } -func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) { +func getGoogleUserInfo(state string, code string) (*OAuthUser, error) { oauth_state_string, isValid := logic.IsStateValid(state) - if !isValid || state != oauth_state_string { - return nil, fmt.Errorf("invalid OAuth state") + if (!isValid || state != oauth_state_string) && !isStateCached(state) { + return nil, fmt.Errorf("invalid oauth state") } var token, err = auth_provider.Exchange(context.Background(), code) if err != nil { @@ -120,7 +117,7 @@ func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) { if err != nil { return nil, fmt.Errorf("failed reading response body: %s", err.Error()) } - var userInfo = &googleOauthUser{} + var userInfo = &OAuthUser{} if err = json.Unmarshal(contents, userInfo); err != nil { return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) } diff --git a/auth/nodecallback.go b/auth/nodecallback.go new file mode 100644 index 00000000..d7bfae9c --- /dev/null +++ b/auth/nodecallback.go @@ -0,0 +1,259 @@ +package auth + +import ( + "bytes" + "fmt" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" + "github.com/gravitl/netmaker/logic/pro/netcache" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" + "github.com/gravitl/netmaker/servercfg" +) + +var ( + redirectUrl string +) + +// HandleNodeSSOCallback handles the callback from the sso endpoint +// It is the analogue of auth.handleNodeSSOCallback but takes care of the end point flow +// Retrieves the mkey from the state cache and adds the machine to the users email namespace +// TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities +// TODO: Add groups information from OIDC tokens into machine HostInfo +// Listens in /oidc/callback. +func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) { + + var functions = getCurrentAuthFunctions() + if functions == nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("bad conf")) + logger.Log(0, "Missing Oauth config in HandleNodeSSOCallback") + return + } + + state, code := getStateAndCode(r) + + var userClaims, err = functions[get_user_info].(func(string, string) (*OAuthUser, error))(state, code) + if err != nil { + logger.Log(0, "error when getting user info from callback:", err.Error()) + http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) + return + } + + if code == "" || state == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Wrong params")) + logger.Log(0, "Missing params in HandleSSOCallback") + return + } + + // all responses should be in html format from here on out + w.Header().Add("content-type", "text/html; charset=utf-8") + + // retrieve machinekey from state cache + reqKeyIf, machineKeyFoundErr := netcache.Get(state) + if machineKeyFoundErr != nil { + logger.Log(0, "requested machine state key expired before authorisation completed -", err.Error()) + reqKeyIf = &netcache.CValue{ + Network: "invalid", + Value: state, + Pass: "", + User: "netmaker", + Expiration: time.Now(), + } + response := returnErrTemplate("", "requested machine state key expired before authorisation completed", state, reqKeyIf) + w.WriteHeader(http.StatusInternalServerError) + w.Write(response) + return + } + + user, err := isUserIsAllowed(userClaims.getUserName(), reqKeyIf.Network, true) + if err != nil { + logger.Log(0, "error occurred during SSO node join for user", userClaims.getUserName(), "on network", reqKeyIf.Network, "-", err.Error()) + response := returnErrTemplate(user.UserName, err.Error(), state, reqKeyIf) + w.WriteHeader(http.StatusNotAcceptable) + w.Write(response) + return + } + + logger.Log(1, "registering new node for user:", user.UserName, "on network", reqKeyIf.Network) + + // Send OK to user in the browser + var response bytes.Buffer + if err := ssoCallbackTemplate.Execute(&response, ssoCallbackTemplateConfig{ + User: userClaims.getUserName(), + Verb: "Authenticated", + }); err != nil { + logger.Log(0, "Could not render SSO callback template ", err.Error()) + response := returnErrTemplate(user.UserName, "Could not render SSO callback template", state, reqKeyIf) + w.WriteHeader(http.StatusInternalServerError) + w.Write(response) + + } else { + w.WriteHeader(http.StatusOK) + w.Write(response.Bytes()) + } + + // Need to send access key to the client + logger.Log(1, "Handling new machine addition to network", + reqKeyIf.Network, "with key", + reqKeyIf.Value, " identity:", userClaims.getUserName(), "claims:", fmt.Sprintf("%+v", userClaims)) + + var answer string + // The registation logic is starting here: + // we request access key with 1 use for the required network + accessToken, err := requestAccessKey(reqKeyIf.Network, 1, userClaims.getUserName()) + if err != nil { + answer = fmt.Sprintf("Error from the netmaker controller %s", err.Error()) + } else { + answer = fmt.Sprintf("AccessToken: %s", accessToken) + } + logger.Log(0, "Updating the token for the client request ... ") + // Give the user the access token via Pass in the DB + reqKeyIf.Pass = answer + if err = netcache.Set(state, reqKeyIf); err != nil { + logger.Log(0, "machine failed to complete join on network,", reqKeyIf.Network, "-", err.Error()) + return + } +} + +func setNetcache(ncache *netcache.CValue, state string) error { + if ncache == nil { + return fmt.Errorf("cache miss") + } + var err error + if err = netcache.Set(state, ncache); err != nil { + logger.Log(0, "machine failed to complete join on network,", ncache.Network, "-", err.Error()) + } + return err +} + +func returnErrTemplate(uname, message, state string, ncache *netcache.CValue) []byte { + var response bytes.Buffer + ncache.Pass = message + err := ssoErrCallbackTemplate.Execute(&response, ssoCallbackTemplateConfig{ + User: uname, + Verb: message, + }) + if err != nil { + return []byte(err.Error()) + } + err = setNetcache(ncache, state) + if err != nil { + return []byte(err.Error()) + } + return response.Bytes() +} + +// RegisterNodeSSO redirects to the IDP for authentication +// Puts machine key in cache so the callback can retrieve it using the oidc state param +// Listens in /oidc/register/:regKey. +func RegisterNodeSSO(w http.ResponseWriter, r *http.Request) { + + logger.Log(1, "RegisterNodeSSO\n") + + vars := mux.Vars(r) + + // machineKeyStr this is not key but state + machineKeyStr := vars["regKey"] + logger.Log(1, "requested key:", machineKeyStr) + + if machineKeyStr == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Wrong params")) + logger.Log(0, "Wrong params ", machineKeyStr) + return + } + + // machineKeyStr this not key but state + authURL := auth_provider.AuthCodeURL(machineKeyStr) + //authURL = authURL + "&connector_id=" + "google" + logger.Log(0, "Redirecting to ", authURL, " for authentication") + + http.Redirect(w, r, authURL, http.StatusSeeOther) + +} + +// == private == +// API to create an access key for a given network with a given name +func requestAccessKey(network string, uses int, name string) (accessKey string, err error) { + + var sAccessKey models.AccessKey + var sNetwork models.Network + + sNetwork, err = logic.GetParentNetwork(network) + if err != nil { + logger.Log(0, "err calling GetParentNetwork API=%s", err.Error()) + return "", fmt.Errorf("internal controller error %s", err.Error()) + } + // If a key already exists, we recreate it. + // @TODO Is that a preferred handling ? We could also trying to re-use. + // can happen if user started log in but did not finish + for _, currentkey := range sNetwork.AccessKeys { + if currentkey.Name == name { + logger.Log(0, "erasing existing AccessKey for: ", name) + err = logic.DeleteKey(currentkey.Name, network) + if err != nil { + logger.Log(0, "err calling CreateAccessKey API ", err.Error()) + return "", fmt.Errorf("key already exists. Contact admin to resolve") + } + break + } + } + // Only one usage is needed - for the next time new access key will be required + // it will be created next time after another IdP approval + sAccessKey.Uses = 1 + sAccessKey.Name = name + + accessToken, err := logic.CreateAccessKey(sAccessKey, sNetwork) + if err != nil { + logger.Log(0, "err calling CreateAccessKey API ", err.Error()) + return "", fmt.Errorf("error from the netmaker controller %s", err.Error()) + } else { + logger.Log(1, "created access key", sAccessKey.Name, "on", network) + } + return accessToken.AccessString, nil +} + +func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User, error) { + + user, err := logic.GetUser(username) + if err != nil && shouldAddUser { // user must not exist, so try to make one + if err = addUser(username); err != nil { + logger.Log(0, "failed to add user", username, "during a node SSO network join on network", network) + // response := returnErrTemplate(user.UserName, "failed to add user", state, reqKeyIf) + // w.WriteHeader(http.StatusInternalServerError) + // w.Write(response) + return nil, fmt.Errorf("failed to add user to system") + } + logger.Log(0, "user", username, "was added during a node SSO network join on network", network) + user, _ = logic.GetUser(username) + } + + if !user.IsAdmin { // perform check to see if user is allowed to join a node to network + netUser, err := pro.GetNetworkUser(network, promodels.NetworkUserID(user.UserName)) + if err != nil { + logger.Log(0, "failed to get net user details for user", user.UserName, "during node SSO") + return nil, fmt.Errorf("failed to verify network user") + } + if netUser.AccessLevel != pro.NET_ADMIN { // if user is a net admin on network, good to go + // otherwise, check if they have node access + haven't reached node limit on network + if netUser.AccessLevel == pro.NODE_ACCESS { + if len(netUser.Nodes) >= netUser.NodeLimit { + logger.Log(0, "user", user.UserName, "has reached their node limit on network", network) + return nil, fmt.Errorf("user node limit exceeded") + } + } else { + logger.Log(0, "user", user.UserName, "attempted to access network", network, "via node SSO") + return nil, fmt.Errorf("network user not allowed") + } + } + } + + return &user, nil +} diff --git a/auth/nodesession.go b/auth/nodesession.go new file mode 100644 index 00000000..b848c2b8 --- /dev/null +++ b/auth/nodesession.go @@ -0,0 +1,155 @@ +package auth + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/gorilla/websocket" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro/netcache" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" + "github.com/gravitl/netmaker/servercfg" +) + +// SessionHandler - called by the HTTP router when user +// is calling netclient with --login-server parameter in order to authenticate +// via SSO mechanism by OAuth2 protocol flow. +// This triggers a session start and it is managed by the flow implmented here and callback +// When this method finishes - the auth flow has finished either OK or by timeout or any other error occured +func SessionHandler(conn *websocket.Conn) { + defer conn.Close() + logger.Log(1, "Running sessionHandler") + + // If reached here we have a session from user to handle... + messageType, message, err := conn.ReadMessage() + if err != nil { + logger.Log(0, "Error during message reading:", err.Error()) + return + } + var loginMessage promodels.LoginMsg + + err = json.Unmarshal(message, &loginMessage) + if err != nil { + logger.Log(0, "Failed to unmarshall data err=", err.Error()) + return + } + logger.Log(1, "SSO node join attempted with info network:", loginMessage.Network, "node identifier:", loginMessage.Mac, "user:", loginMessage.User) + + req := new(netcache.CValue) + req.Value = string(loginMessage.Mac) + req.Network = loginMessage.Network + req.Pass = "" + req.User = "" + // Add any extra parameter provided in the configuration to the Authorize Endpoint request?? + stateStr := hex.EncodeToString([]byte(logic.RandomString(node_signin_length))) + if err := netcache.Set(stateStr, req); err != nil { + logger.Log(0, "Failed to process sso request -", err.Error()) + return + } + // Wait for the user to finish his auth flow... + // TBD: what should be the timeout here ? + timeout := make(chan bool, 1) + answer := make(chan string, 1) + + if loginMessage.User != "" { // handle basic auth + // verify that server supports basic auth, then authorize the request with given credentials + // check if user is allowed to join via node sso + // i.e. user is admin or user has network permissions + if !servercfg.IsBasicAuthEnabled() { + err = conn.WriteMessage(messageType, []byte("Basic Auth Disabled")) + if err != nil { + logger.Log(0, "error during message writing:", err.Error()) + } + } + _, err := logic.VerifyAuthRequest(models.UserAuthParams{ + UserName: loginMessage.User, + Password: loginMessage.Password, + }) + if err != nil { + err = conn.WriteMessage(messageType, []byte(fmt.Sprintf("Failed to authenticate, %s.", loginMessage.User))) + if err != nil { + logger.Log(0, "error during message writing:", err.Error()) + } + return + } + user, err := isUserIsAllowed(loginMessage.User, loginMessage.Network, false) + if err != nil { + err = conn.WriteMessage(messageType, []byte(fmt.Sprintf("%s lacks permission to join.", loginMessage.User))) + if err != nil { + logger.Log(0, "error during message writing:", err.Error()) + } + return + } + accessToken, err := requestAccessKey(loginMessage.Network, 1, user.UserName) + if err != nil { + req.Pass = fmt.Sprintf("Error from the netmaker controller %s", err.Error()) + } else { + req.Pass = fmt.Sprintf("AccessToken: %s", accessToken) + } + // Give the user the access token via Pass in the DB + if err = netcache.Set(stateStr, req); err != nil { + logger.Log(0, "machine failed to complete join on network,", loginMessage.Network, "-", err.Error()) + return + } + } else { // handle SSO / OAuth + redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr) + err = conn.WriteMessage(messageType, []byte(redirectUrl)) + if err != nil { + logger.Log(0, "error during message writing:", err.Error()) + } + } + + go func() { + for { + cachedReq, err := netcache.Get(stateStr) + if err != nil { + if strings.Contains(err.Error(), "expired") { + logger.Log(0, "timeout occurred while waiting for SSO on network", loginMessage.Network) + timeout <- true + break + } + continue + } else if cachedReq.Pass != "" { + logger.Log(0, "node SSO process completed for user", cachedReq.User, "on network", loginMessage.Network) + answer <- cachedReq.Pass + break + } + time.Sleep(500) // try it 2 times per second to see if auth is completed + } + }() + + select { + case result := <-answer: + // a read from req.answerCh has occurred + err = conn.WriteMessage(messageType, []byte(result)) + if err != nil { + logger.Log(0, "Error during message writing:", err.Error()) + } + case <-timeout: + logger.Log(0, "Authentication server time out for a node on network", loginMessage.Network) + // the read from req.answerCh has timed out + err = conn.WriteMessage(messageType, []byte("Authentication server time out")) + if err != nil { + logger.Log(0, "Error during message writing:", err.Error()) + } + } + // The entry is not needed anymore, but we will let the producer to close it to avoid panic cases + if err = netcache.Del(stateStr); err != nil { + logger.Log(0, "failed to remove node SSO cache entry", err.Error()) + } + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + logger.Log(0, "write close:", err.Error()) + return + } + time.After(time.Second) + close(answer) + close(timeout) +} diff --git a/auth/oidc.go b/auth/oidc.go index 77e26ad9..ad03b1c9 100644 --- a/auth/oidc.go +++ b/auth/oidc.go @@ -26,11 +26,6 @@ var oidc_functions = map[string]interface{}{ var oidc_verifier *oidc.IDTokenVerifier -type OIDCUser struct { - Name string `json:"name" bson:"name"` - Email string `json:"email" bson:"email"` -} - // == handle OIDC authentication here == func initOIDC(redirectURL string, clientID string, clientSecret string, issuer string) { @@ -54,7 +49,7 @@ func initOIDC(redirectURL string, clientID string, clientSecret string, issuer s } func handleOIDCLogin(w http.ResponseWriter, r *http.Request) { - var oauth_state_string = logic.RandomString(16) + var oauth_state_string = logic.RandomString(user_signin_length) if auth_provider == nil && servercfg.GetFrontendURL() != "" { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) return @@ -67,14 +62,16 @@ func handleOIDCLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) return } - + logger.Log(3, "using state string:", oauth_state_string) var url = auth_provider.AuthCodeURL(oauth_state_string) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { - var content, err = getOIDCUserInfo(r.FormValue("state"), r.FormValue("code")) + var rState, rCode = getStateAndCode(r) + + var content, err = getOIDCUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from callback:", err.Error()) http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) @@ -98,7 +95,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { var jwt, jwtErr = logic.VerifyAuthRequest(authRequest) if jwtErr != nil { - logger.Log(1, "could not parse jwt for user", authRequest.UserName) + logger.Log(1, "could not parse jwt for user", authRequest.UserName, jwtErr.Error()) return } @@ -106,10 +103,12 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect) } -func getOIDCUserInfo(state string, code string) (u *OIDCUser, e error) { +func getOIDCUserInfo(state string, code string) (u *OAuthUser, e error) { oauth_state_string, isValid := logic.IsStateValid(state) - if !isValid || state != oauth_state_string { - return nil, fmt.Errorf("invalid OAuth state") + logger.Log(3, "using oauth state string:,", oauth_state_string) + logger.Log(3, " state string:,", state) + if (!isValid || state != oauth_state_string) && !isStateCached(state) { + return nil, fmt.Errorf("invalid oauth state") } defer func() { @@ -136,7 +135,7 @@ func getOIDCUserInfo(state string, code string) (u *OIDCUser, e error) { return nil, fmt.Errorf("failed to verify raw id_token: \"%s\"", err.Error()) } - u = &OIDCUser{} + u = &OAuthUser{} if err := idToken.Claims(u); err != nil { e = fmt.Errorf("error when claiming OIDCUser: \"%s\"", err.Error()) } diff --git a/auth/templates.go b/auth/templates.go new file mode 100644 index 00000000..65f5d124 --- /dev/null +++ b/auth/templates.go @@ -0,0 +1,81 @@ +package auth + +import "html/template" + +type ssoCallbackTemplateConfig struct { + User string + Verb string +} + +var ssoCallbackTemplate = template.Must( + template.New("ssocallback").Parse(` + + + + + + + Netmaker + + + +

+ +
+
+

{{.User}} has been successfully {{.Verb}}

+
+

You may now close this window.

+
+
+
+ + `), +) + +var ssoErrCallbackTemplate = template.Must( + template.New("ssocallback").Parse(` + + + + + + + Netmaker + + + +
+
+ + Netmaker + +
+
+
+

{{.User}} unable to join network: {{.Verb}}

+
+

If you feel this is a mistake, please contact your network administrator.

+
+
+
+ + `), +) diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index a78aed34..45c4a8ea 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -39,6 +39,7 @@ services: VERBOSITY: "1" MANAGE_IPTABLES: "on" PORT_FORWARD_SERVICES: "dns" + METRICS_EXPORTER: "on" ports: - "51821-51830:51821-51830/udp" expose: @@ -111,6 +112,7 @@ services: restart: unless-stopped volumes: - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf + - /root/mosquitto.passwords:/etc/mosquitto.passwords - mosquitto_data:/mosquitto/data - mosquitto_logs:/mosquitto/log - shared_certs:/mosquitto/certs @@ -123,6 +125,66 @@ services: - traefik.tcp.services.mqtts-svc.loadbalancer.server.port=8883 - traefik.tcp.routers.mqtts.service=mqtts-svc - traefik.tcp.routers.mqtts.entrypoints=websecure + prometheus: + container_name: prometheus + image: gravitl/netmaker-prometheus:latest + environment: + NETMAKER_METRICS_TARGET: "netmaker-exporter.NETMAKER_BASE_DOMAIN" + labels: + - traefik.enable=true + - traefik.http.routers.prometheus.entrypoints=websecure + - traefik.http.routers.prometheus.rule=Host(`prometheus.NETMAKER_BASE_DOMAIN`) + - traefik.http.services.prometheus.loadbalancer.server.port=9090 + - traefik.http.routers.prometheus.service=prometheus + restart: always + volumes: + - prometheus_data:/prometheus + depends_on: + - netmaker + ports: + - 9090:9090 + grafana: + container_name: grafana + image: gravitl/netmaker-grafana:latest + labels: + - traefik.enable=true + - traefik.http.routers.grafana.entrypoints=websecure + - traefik.http.routers.grafana.rule=Host(`grafana.NETMAKER_BASE_DOMAIN`) + - traefik.http.services.grafana.loadbalancer.server.port=3000 + - traefik.http.routers.grafana.service=grafana + environment: + PROMETHEUS_HOST: "prometheus.NETMAKER_BASE_DOMAIN" + NETMAKER_METRICS_TARGET: "netmaker-exporter.NETMAKER_BASE_DOMAIN" + ports: + - 3000:3000 + restart: always + links: + - prometheus + depends_on: + - prometheus + - netmaker + netmaker-exporter: + container_name: netmaker-exporter + image: gravitl/netmaker-exporter:latest + labels: + - traefik.enable=true + - traefik.http.routers.netmaker-exporter.entrypoints=websecure + - traefik.http.routers.netmaker-exporter.rule=Host(`netmaker-exporter.NETMAKER_BASE_DOMAIN`) + - traefik.http.services.netmaker-exporter.loadbalancer.server.port=8085 + - traefik.http.routers.netmaker-exporter.service=netmaker-exporter + restart: always + depends_on: + - netmaker + environment: + MQ_HOST: "mq" + MQ_PORT: "443" + MQ_SERVER_PORT: "1884" + PROMETHEUS: "on" + VERBOSITY: "1" + API_PORT: "8085" + PROMETHEUS_HOST: https://prometheus.NETMAKER_BASE_DOMAIN + expose: + - "8085" volumes: traefik_certs: {} shared_certs: {} @@ -130,3 +192,4 @@ volumes: dnsconfig: {} mosquitto_data: {} mosquitto_logs: {} + prometheus_data: {} diff --git a/config/config.go b/config/config.go index 71dd0e44..973c1d5d 100644 --- a/config/config.go +++ b/config/config.go @@ -70,6 +70,10 @@ type ServerConfig struct { MQServerPort string `yaml:"mqserverport"` Server string `yaml:"server"` PublicIPService string `yaml:"publicipservice"` + MetricsExporter string `yaml:"metrics_exporter"` + BasicAuth string `yaml:"basic_auth"` + LicenseValue string `yaml:"license_value"` + NetmakerAccountID string `yaml:"netmaker_account_id"` } // SQLConfig - Generic SQL Config diff --git a/controllers/controller.go b/controllers/controller.go index 7e3ea624..f43897c3 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -25,6 +25,10 @@ var HttpHandlers = []interface{}{ serverHandlers, extClientHandlers, ipHandlers, + metricHandlers, + loggerHandlers, + userGroupsHandlers, + networkUsersHandlers, } // HandleRESTRequests - handles the rest requests diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 767a2ecb..05cdad33 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -12,7 +12,9 @@ import ( "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" "github.com/gravitl/netmaker/mq" "github.com/skip2/go-qrcode" ) @@ -22,10 +24,10 @@ func extClientHandlers(r *mux.Router) { r.HandleFunc("/api/extclients", securityCheck(false, http.HandlerFunc(getAllExtClients))).Methods("GET") r.HandleFunc("/api/extclients/{network}", securityCheck(false, http.HandlerFunc(getNetworkExtClients))).Methods("GET") r.HandleFunc("/api/extclients/{network}/{clientid}", securityCheck(false, http.HandlerFunc(getExtClient))).Methods("GET") - r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", securityCheck(false, http.HandlerFunc(getExtClientConf))).Methods("GET") - r.HandleFunc("/api/extclients/{network}/{clientid}", securityCheck(false, http.HandlerFunc(updateExtClient))).Methods("PUT") - r.HandleFunc("/api/extclients/{network}/{clientid}", securityCheck(false, http.HandlerFunc(deleteExtClient))).Methods("DELETE") - r.HandleFunc("/api/extclients/{network}/{nodeid}", securityCheck(false, http.HandlerFunc(createExtClient))).Methods("POST") + r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", netUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods("GET") + r.HandleFunc("/api/extclients/{network}/{clientid}", netUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods("PUT") + r.HandleFunc("/api/extclients/{network}/{clientid}", netUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods("DELETE") + r.HandleFunc("/api/extclients/{network}/{nodeid}", netUserSecurityCheck(false, true, checkFreeTierLimits(clients_l, http.HandlerFunc(createExtClient)))).Methods("POST") } func checkIngressExists(nodeID string) bool { @@ -337,6 +339,8 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { if err == nil { // check if parent network default ACL is enabled (yes) or not (no) extclient.Enabled = parentNetwork.DefaultACL == "yes" } + // check pro settings + err = logic.CreateExtClient(&extclient) if err != nil { logger.Log(0, r.Header.Get("user"), @@ -344,6 +348,27 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } + + var isAdmin bool + if r.Header.Get("ismaster") != "yes" { + userID := r.Header.Get("user") + if isAdmin, err = checkProClientAccess(userID, extclient.ClientID, &parentNetwork); err != nil { + logger.Log(0, userID, "attempted to create a client on network", networkName, "but they lack access") + logic.DeleteExtClient(networkName, extclient.ClientID) + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + if !isAdmin { + if err = pro.AssociateNetworkUserClient(userID, networkName, extclient.ClientID); err != nil { + logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID) + } + extclient.OwnerID = userID + if _, err := logic.UpdateExtClient(extclient.ClientID, extclient.Network, extclient.Enabled, &extclient); err != nil { + logger.Log(0, "failed to add owner id", userID, "to client", extclient.ClientID) + } + } + } + logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName) w.WriteHeader(http.StatusOK) err = mq.PublishExtPeerUpdate(&node) @@ -402,7 +427,31 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } + + // == PRO == + networkName := params["network"] + var changedID = newExtClient.ClientID != oldExtClient.ClientID + if r.Header.Get("ismaster") != "yes" { + userID := r.Header.Get("user") + _, doesOwn := doesUserOwnClient(userID, params["clientid"], networkName) + if !doesOwn { + returnErrorResponse(w, r, formatError(fmt.Errorf("user not permitted"), "internal")) + return + } + } + + if changedID && oldExtClient.OwnerID != "" { + if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil { + logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID) + } + if err := pro.AssociateNetworkUserClient(oldExtClient.OwnerID, networkName, newExtClient.ClientID); err != nil { + logger.Log(0, "failed to associate client", newExtClient.ClientID, "to user", oldExtClient.OwnerID) + } + } + // == END PRO == + var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement + newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient) if err != nil { logger.Log(0, r.Header.Get("user"), @@ -459,6 +508,24 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { return } + // == PRO == + if r.Header.Get("ismaster") != "yes" { + userID, clientID, networkName := r.Header.Get("user"), params["clientid"], params["network"] + _, doesOwn := doesUserOwnClient(userID, clientID, networkName) + if !doesOwn { + returnErrorResponse(w, r, formatError(fmt.Errorf("user not permitted"), "internal")) + return + } + } + + if extclient.OwnerID != "" { + if err = pro.DissociateNetworkUserClient(extclient.OwnerID, extclient.Network, extclient.ClientID); err != nil { + logger.Log(0, "failed to dissociate client", extclient.ClientID, "from user", extclient.OwnerID) + } + } + + // == END PRO == + err = logic.DeleteExtClient(params["network"], params["clientid"]) if err != nil { logger.Log(0, r.Header.Get("user"), @@ -472,7 +539,65 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(1, "error setting ext peers on "+ingressnode.ID+": "+err.Error()) } + logger.Log(0, r.Header.Get("user"), "Deleted extclient client", params["clientid"], "from network", params["network"]) returnSuccessResponse(w, r, params["clientid"]+" deleted.") } + +func checkProClientAccess(username, clientID string, network *models.Network) (bool, error) { + u, err := logic.GetUser(username) + if err != nil { + return false, err + } + if u.IsAdmin { + return true, nil + } + + netUser, err := pro.GetNetworkUser(network.NetID, promodels.NetworkUserID(u.UserName)) + if err != nil { + return false, err + } + + if netUser.AccessLevel == pro.NET_ADMIN { + return false, nil + } + + if netUser.AccessLevel == pro.NO_ACCESS { + return false, fmt.Errorf("user does not have access") + } + + if !(len(netUser.Clients) < netUser.ClientLimit) { + return false, fmt.Errorf("user can not create more clients") + } + + if netUser.AccessLevel < pro.NO_ACCESS { + netUser.Clients = append(netUser.Clients, clientID) + if err = pro.UpdateNetworkUser(network.NetID, netUser); err != nil { + return false, err + } + } + return false, nil +} + +// checks if net user owns an ext client or is an admin +func doesUserOwnClient(username, clientID, network string) (bool, bool) { + u, err := logic.GetUser(username) + if err != nil { + return false, false + } + if u.IsAdmin { + return true, true + } + + netUser, err := pro.GetNetworkUser(network, promodels.NetworkUserID(u.UserName)) + if err != nil { + return false, false + } + + if netUser.AccessLevel == pro.NET_ADMIN { + return false, true + } + + return false, logic.StringSliceContains(netUser.Clients, clientID) +} diff --git a/controllers/limits.go b/controllers/limits.go new file mode 100644 index 00000000..ddbff298 --- /dev/null +++ b/controllers/limits.go @@ -0,0 +1,59 @@ +package controller + +import ( + "net/http" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/ee" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" +) + +// limit consts +const ( + node_l = 0 + networks_l = 1 + users_l = 2 + clients_l = 3 +) + +func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var errorResponse = models.ErrorResponse{ + Code: http.StatusUnauthorized, Message: "free tier limits exceeded on networks", + } + + if ee.Limits.FreeTier { // check that free tier limits not exceeded + if limit_choice == networks_l { + currentNetworks, err := logic.GetNetworks() + if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= ee.Limits.Networks { + returnErrorResponse(w, r, errorResponse) + return + } + } else if limit_choice == node_l { + nodes, err := logic.GetAllNodes() + if (err != nil && !database.IsEmptyRecord(err)) || len(nodes) >= ee.Limits.Nodes { + errorResponse.Message = "free tier limits exceeded on nodes" + returnErrorResponse(w, r, errorResponse) + return + } + } else if limit_choice == users_l { + users, err := logic.GetUsers() + if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= ee.Limits.Users { + errorResponse.Message = "free tier limits exceeded on users" + returnErrorResponse(w, r, errorResponse) + return + } + } else if limit_choice == clients_l { + clients, err := logic.GetAllExtClients() + if (err != nil && !database.IsEmptyRecord(err)) || len(clients) >= ee.Limits.Clients { + errorResponse.Message = "free tier limits exceeded on external clients" + returnErrorResponse(w, r, errorResponse) + return + } + } + } + + next.ServeHTTP(w, r) + } +} diff --git a/controllers/logger.go b/controllers/logger.go new file mode 100644 index 00000000..316783fc --- /dev/null +++ b/controllers/logger.go @@ -0,0 +1,22 @@ +package controller + +import ( + "fmt" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" +) + +func loggerHandlers(r *mux.Router) { + r.HandleFunc("/api/logs", securityCheck(true, http.HandlerFunc(getLogs))).Methods("GET") +} + +func getLogs(w http.ResponseWriter, r *http.Request) { + var currentTime = time.Now().Format(logger.TimeFormatDay) + var currentFilePath = fmt.Sprintf("data/netmaker.log.%s", currentTime) + logger.DumpFile(currentFilePath) + w.WriteHeader(http.StatusOK) + w.Write([]byte(logger.Retrieve(currentFilePath))) +} diff --git a/controllers/metrics.go b/controllers/metrics.go new file mode 100644 index 00000000..1c08350f --- /dev/null +++ b/controllers/metrics.go @@ -0,0 +1,102 @@ +package controller + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" +) + +func metricHandlers(r *mux.Router) { + r.HandleFunc("/api/metrics/{network}/{nodeid}", securityCheck(true, http.HandlerFunc(getNodeMetrics))).Methods("GET") + r.HandleFunc("/api/metrics/{network}", securityCheck(true, http.HandlerFunc(getNetworkNodesMetrics))).Methods("GET") + r.HandleFunc("/api/metrics", securityCheck(true, http.HandlerFunc(getAllMetrics))).Methods("GET") +} + +// get the metrics of a given node +func getNodeMetrics(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + nodeID := params["nodeid"] + + logger.Log(1, r.Header.Get("user"), "requested fetching metrics for node", nodeID, "on network", params["network"]) + metrics, err := logic.GetMetrics(nodeID) + if err != nil { + logger.Log(1, r.Header.Get("user"), "failed to fetch metrics of node", nodeID, err.Error()) + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + logger.Log(1, r.Header.Get("user"), "fetched metrics for node", params["nodeid"]) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(metrics) +} + +// get the metrics of all nodes in given network +func getNetworkNodesMetrics(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + network := params["network"] + + logger.Log(1, r.Header.Get("user"), "requested fetching network node metrics on network", network) + networkNodes, err := logic.GetNetworkNodes(network) + if err != nil { + logger.Log(1, r.Header.Get("user"), "failed to fetch metrics of all nodes in network", network, err.Error()) + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + networkMetrics := models.NetworkMetrics{} + networkMetrics.Nodes = make(models.MetricsMap) + + for i := range networkNodes { + id := networkNodes[i].ID + metrics, err := logic.GetMetrics(id) + if err != nil { + logger.Log(1, r.Header.Get("user"), "failed to append metrics of node", id, "during network metrics fetch", err.Error()) + continue + } + networkMetrics.Nodes[id] = *metrics + } + + logger.Log(1, r.Header.Get("user"), "fetched metrics for network", network) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(networkMetrics) +} + +// get Metrics of all nodes on server, lots of data +func getAllMetrics(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + logger.Log(1, r.Header.Get("user"), "requested fetching all metrics") + + allNodes, err := logic.GetAllNodes() + if err != nil { + logger.Log(1, r.Header.Get("user"), "failed to fetch metrics of all nodes on server", err.Error()) + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + networkMetrics := models.NetworkMetrics{} + networkMetrics.Nodes = make(models.MetricsMap) + + for i := range allNodes { + id := allNodes[i].ID + metrics, err := logic.GetMetrics(id) + if err != nil { + logger.Log(1, r.Header.Get("user"), "failed to append metrics of node", id, "during all nodes metrics fetch", err.Error()) + continue + } + networkMetrics.Nodes[id] = *metrics + } + + logger.Log(1, r.Header.Get("user"), "fetched metrics for all nodes on server") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(networkMetrics) +} diff --git a/controllers/network.go b/controllers/network.go index b94a230c..7c9c71f1 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -25,7 +25,7 @@ const NO_NETWORKS_PRESENT = "THIS_USER_HAS_NONE" func networkHandlers(r *mux.Router) { r.HandleFunc("/api/networks", securityCheck(false, http.HandlerFunc(getNetworks))).Methods("GET") - r.HandleFunc("/api/networks", securityCheck(true, http.HandlerFunc(createNetwork))).Methods("POST") + r.HandleFunc("/api/networks", securityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods("POST") r.HandleFunc("/api/networks/{networkname}", securityCheck(false, http.HandlerFunc(getNetwork))).Methods("GET") r.HandleFunc("/api/networks/{networkname}", securityCheck(false, http.HandlerFunc(updateNetwork))).Methods("PUT") r.HandleFunc("/api/networks/{networkname}/nodelimit", securityCheck(true, http.HandlerFunc(updateNetworkNodeLimit))).Methods("PUT") @@ -199,7 +199,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { newNetwork.DefaultPostUp = network.DefaultPostUp } - rangeupdate4, rangeupdate6, localrangeupdate, holepunchupdate, err := logic.UpdateNetwork(&network, &newNetwork) + rangeupdate4, rangeupdate6, localrangeupdate, holepunchupdate, groupsDelta, userDelta, err := logic.UpdateNetwork(&network, &newNetwork) if err != nil { logger.Log(0, r.Header.Get("user"), "failed to update network: ", err.Error()) @@ -207,6 +207,24 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { return } + if len(groupsDelta) > 0 { + for _, g := range groupsDelta { + users, err := logic.GetGroupUsers(g) + if err == nil { + for _, user := range users { + logic.AdjustNetworkUserPermissions(&user, &newNetwork) + } + } + } + } + if len(userDelta) > 0 { + for _, uname := range userDelta { + user, err := logic.GetReturnUser(uname) + if err == nil { + logic.AdjustNetworkUserPermissions(&user, &newNetwork) + } + } + } if rangeupdate4 { err = logic.UpdateNetworkNodeAddresses(network.NetID) if err != nil { @@ -536,6 +554,15 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "badrequest")) return } + + // do not allow access key creations view API with user names + if _, err = logic.GetUser(key.Name); err == nil { + logger.Log(0, "access key creation with invalid name attempted by", r.Header.Get("user")) + returnErrorResponse(w, r, formatError(fmt.Errorf("cannot create access key with user name"), "badrequest")) + logic.DeleteKey(key.Name, network.NetID) + return + } + logger.Log(1, r.Header.Get("user"), "created access key", accesskey.Name, "on", netname) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(key) diff --git a/controllers/network_test.go b/controllers/network_test.go index fee91b9f..a85b03e3 100644 --- a/controllers/network_test.go +++ b/controllers/network_test.go @@ -17,7 +17,7 @@ type NetworkValidationTestCase struct { } func TestCreateNetwork(t *testing.T) { - database.InitializeDatabase() + initialize() deleteAllNetworks() var network models.Network @@ -30,7 +30,7 @@ func TestCreateNetwork(t *testing.T) { assert.Nil(t, err) } func TestGetNetwork(t *testing.T) { - database.InitializeDatabase() + initialize() createNet() t.Run("GetExistingNetwork", func(t *testing.T) { @@ -46,7 +46,7 @@ func TestGetNetwork(t *testing.T) { } func TestDeleteNetwork(t *testing.T) { - database.InitializeDatabase() + initialize() createNet() //create nodes t.Run("NetworkwithNodes", func(t *testing.T) { @@ -62,7 +62,7 @@ func TestDeleteNetwork(t *testing.T) { } func TestCreateKey(t *testing.T) { - database.InitializeDatabase() + initialize() createNet() keys, _ := logic.GetKeys("skynet") for _, key := range keys { @@ -74,7 +74,7 @@ func TestCreateKey(t *testing.T) { t.Run("NameTooLong", func(t *testing.T) { network, err := logic.GetNetwork("skynet") assert.Nil(t, err) - accesskey.Name = "Thisisareallylongkeynamethatwillfail" + accesskey.Name = "ThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfailThisisareallylongkeynamethatwillfail" _, err = logic.CreateAccessKey(accesskey, network) assert.NotNil(t, err) assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag") @@ -134,7 +134,7 @@ func TestCreateKey(t *testing.T) { } func TestGetKeys(t *testing.T) { - database.InitializeDatabase() + initialize() deleteAllNetworks() createNet() network, err := logic.GetNetwork("skynet") @@ -157,7 +157,7 @@ func TestGetKeys(t *testing.T) { }) } func TestDeleteKey(t *testing.T) { - database.InitializeDatabase() + initialize() createNet() network, err := logic.GetNetwork("skynet") assert.Nil(t, err) @@ -179,7 +179,7 @@ func TestDeleteKey(t *testing.T) { func TestSecurityCheck(t *testing.T) { //these seem to work but not sure it the tests are really testing the functionality - database.InitializeDatabase() + initialize() os.Setenv("MASTER_KEY", "secretkey") t.Run("NoNetwork", func(t *testing.T) { networks, username, err := SecurityCheck(false, "", "Bearer secretkey") @@ -210,7 +210,7 @@ func TestValidateNetwork(t *testing.T) { //t.Skip() //This functions is not called by anyone //it panics as validation function 'display_name_valid' is not defined - database.InitializeDatabase() + initialize() //yes := true //no := false //deleteNet(t) @@ -295,7 +295,7 @@ func TestValidateNetwork(t *testing.T) { func TestIpv6Network(t *testing.T) { //these seem to work but not sure it the tests are really testing the functionality - database.InitializeDatabase() + initialize() os.Setenv("MASTER_KEY", "secretkey") deleteAllNetworks() createNet() @@ -321,6 +321,21 @@ func deleteAllNetworks() { } } +func initialize() { + database.InitializeDatabase() + createAdminUser() +} + +func createAdminUser() { + logic.CreateAdmin(models.User{ + UserName: "admin", + Password: "password", + IsAdmin: true, + Networks: []string{}, + Groups: []string{}, + }) +} + func createNet() { var network models.Network network.NetID = "skynet" diff --git a/controllers/networkusers.go b/controllers/networkusers.go new file mode 100644 index 00000000..1e6c37d4 --- /dev/null +++ b/controllers/networkusers.go @@ -0,0 +1,359 @@ +package controller + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" +) + +func networkUsersHandlers(r *mux.Router) { + r.HandleFunc("/api/networkusers", securityCheck(true, http.HandlerFunc(getAllNetworkUsers))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}", securityCheck(true, http.HandlerFunc(getNetworkUsers))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}/{networkuser}", securityCheck(true, http.HandlerFunc(getNetworkUser))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}", securityCheck(true, http.HandlerFunc(createNetworkUser))).Methods("POST") + r.HandleFunc("/api/networkusers/{network}", securityCheck(true, http.HandlerFunc(updateNetworkUser))).Methods("PUT") + r.HandleFunc("/api/networkusers/data/{networkuser}/me", netUserSecurityCheck(false, false, http.HandlerFunc(getNetworkUserData))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}/{networkuser}", securityCheck(true, http.HandlerFunc(deleteNetworkUser))).Methods("DELETE") +} + +// == RETURN TYPES == + +// NetworkName - represents a network name/ID +type NetworkName string + +// NetworkUserDataMap - map of all data per network for a user +type NetworkUserDataMap map[NetworkName]NetworkUserData + +// NetworkUserData - data struct for network users +type NetworkUserData struct { + Nodes []models.Node `json:"nodes" bson:"nodes" yaml:"nodes"` + Clients []models.ExtClient `json:"clients" bson:"clients" yaml:"clients"` + Vpn []models.Node `json:"vpns" bson:"vpns" yaml:"vpns"` + Networks []models.Network `json:"networks" bson:"networks" yaml:"networks"` + User promodels.NetworkUser `json:"user" bson:"user" yaml:"user"` +} + +// == END RETURN TYPES == + +// returns a map of a network user's data across all networks +func getNetworkUserData(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + networkUserName := params["networkuser"] + logger.Log(1, r.Header.Get("user"), "requested fetching network user data for user", networkUserName) + + networks, err := logic.GetNetworks() + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + if networkUserName == "" { + returnErrorResponse(w, r, formatError(errors.New("netuserToGet"), "badrequest")) + return + } + + u, err := logic.GetUser(networkUserName) + if err != nil { + returnErrorResponse(w, r, formatError(errors.New("could not find user"), "badrequest")) + return + } + + // initialize the return data of network users + returnData := make(NetworkUserDataMap) + + // go through each network and get that user's data + // if user has no access, give no data + // if user is a net admin, give all nodes + // if user has node access, give user's nodes if any + // if user has client access, git user's clients if any + for i := range networks { + + netID := networks[i].NetID + newData := NetworkUserData{ + Nodes: []models.Node{}, + Clients: []models.ExtClient{}, + Vpn: []models.Node{}, + Networks: []models.Network{}, + } + netUser, err := pro.GetNetworkUser(netID, promodels.NetworkUserID(networkUserName)) + // check if user has access + if err == nil && netUser.AccessLevel != pro.NO_ACCESS { + newData.User = promodels.NetworkUser{ + AccessLevel: netUser.AccessLevel, + ClientLimit: netUser.ClientLimit, + NodeLimit: netUser.NodeLimit, + Nodes: netUser.Nodes, + Clients: netUser.Clients, + } + // check network level permissions + if doesNetworkAllow := pro.IsUserAllowed(&networks[i], networkUserName, u.Groups); doesNetworkAllow { + netNodes, err := logic.GetNetworkNodes(netID) + if err != nil { + logger.Log(0, "failed to retrieve nodes on network", netID, "for user", string(netUser.ID)) + } else { + if netUser.AccessLevel <= pro.NODE_ACCESS { // handle nodes + // if access level is NODE_ACCESS, filter nodes + if netUser.AccessLevel == pro.NODE_ACCESS { + for i := range netNodes { + if logic.StringSliceContains(netUser.Nodes, netNodes[i].ID) { + newData.Nodes = append(newData.Nodes, netNodes[i]) + } + } + } else { // net admin so, get all nodes and ext clients on network... + newData.Nodes = netNodes + for i := range netNodes { + if netNodes[i].IsIngressGateway == "yes" { + newData.Vpn = append(newData.Vpn, netNodes[i]) + if clients, err := logic.GetExtClientsByID(netNodes[i].ID, netID); err == nil { + newData.Clients = append(newData.Clients, clients...) + } + } + } + newData.Networks = append(newData.Networks, networks[i]) + } + } + if netUser.AccessLevel <= pro.CLIENT_ACCESS && netUser.AccessLevel != pro.NET_ADMIN { + for _, c := range netUser.Clients { + if client, err := logic.GetExtClient(c, netID); err == nil { + newData.Clients = append(newData.Clients, client) + } + } + for i := range netNodes { + if netNodes[i].IsIngressGateway == "yes" { + newData.Vpn = append(newData.Vpn, netNodes[i]) + } + } + } + } + } + returnData[NetworkName(netID)] = newData + } + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(returnData) +} + +// returns a map of all network users mapped to each network +func getAllNetworkUsers(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + logger.Log(1, r.Header.Get("user"), "requested fetching all network users") + type allNetworkUsers = map[string][]promodels.NetworkUser + + networks, err := logic.GetNetworks() + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + var allNetUsers = make(allNetworkUsers, len(networks)) + + for i := range networks { + netusers, err := pro.GetNetworkUsers(networks[i].NetID) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + for _, v := range netusers { + allNetUsers[networks[i].NetID] = append(allNetUsers[networks[i].NetID], v) + } + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(allNetUsers) +} + +func getNetworkUsers(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + netname := params["network"] + logger.Log(1, r.Header.Get("user"), "requested fetching network users for network", netname) + + _, err := logic.GetNetwork(netname) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + netusers, err := pro.GetNetworkUsers(netname) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(netusers) +} + +func getNetworkUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + netname := params["network"] + logger.Log(1, r.Header.Get("user"), "requested fetching network user", params["networkuser"], "on network", netname) + + _, err := logic.GetNetwork(netname) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + netuserToGet := params["networkuser"] + if netuserToGet == "" { + returnErrorResponse(w, r, formatError(errors.New("netuserToGet"), "badrequest")) + return + } + + netuser, err := pro.GetNetworkUser(netname, promodels.NetworkUserID(netuserToGet)) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(netuser) +} + +func createNetworkUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + netname := params["network"] + logger.Log(1, r.Header.Get("user"), "requested creating a network user on network", netname) + + network, err := logic.GetNetwork(netname) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + var networkuser promodels.NetworkUser + + // we decode our body request params + err = json.NewDecoder(r.Body).Decode(&networkuser) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + err = pro.CreateNetworkUser(&network, &networkuser) + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + + w.WriteHeader(http.StatusOK) +} + +func updateNetworkUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + netname := params["network"] + logger.Log(1, r.Header.Get("user"), "requested updating a network user on network", netname) + + network, err := logic.GetNetwork(netname) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + var networkuser promodels.NetworkUser + + // we decode our body request params + err = json.NewDecoder(r.Body).Decode(&networkuser) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + if networkuser.ID == "" || !pro.DoesNetworkUserExist(netname, networkuser.ID) { + returnErrorResponse(w, r, formatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest")) + return + } + if networkuser.AccessLevel < pro.NET_ADMIN || networkuser.AccessLevel > pro.NO_ACCESS { + returnErrorResponse(w, r, formatError(errors.New("invalid user access level provided"), "badrequest")) + return + } + + if networkuser.ClientLimit < 0 || networkuser.NodeLimit < 0 { + returnErrorResponse(w, r, formatError(errors.New("negative user limit provided"), "badrequest")) + return + } + + u, err := logic.GetUser(string(networkuser.ID)) + if err != nil { + returnErrorResponse(w, r, formatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest")) + return + } + + if !pro.IsUserAllowed(&network, u.UserName, u.Groups) { + returnErrorResponse(w, r, formatError(errors.New("user must be in allowed groups or users"), "badrequest")) + return + } + + if networkuser.AccessLevel == pro.NET_ADMIN { + currentUser, err := logic.GetUser(string(networkuser.ID)) + if err != nil { + returnErrorResponse(w, r, formatError(errors.New("user model not found for "+string(networkuser.ID)), "badrequest")) + return + } + + if !logic.StringSliceContains(currentUser.Networks, netname) { + // append network name to user model to conform to old model + if err = logic.UpdateUserNetworks( + append(currentUser.Networks, netname), + currentUser.Groups, + currentUser.IsAdmin, + &models.ReturnUser{ + Groups: currentUser.Groups, + IsAdmin: currentUser.IsAdmin, + Networks: currentUser.Networks, + UserName: currentUser.UserName, + }, + ); err != nil { + returnErrorResponse(w, r, formatError(errors.New("user model failed net admin update "+string(networkuser.ID)+" (are they an admin?"), "badrequest")) + return + } + } + } + + err = pro.UpdateNetworkUser(netname, &networkuser) + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + + w.WriteHeader(http.StatusOK) +} + +func deleteNetworkUser(w http.ResponseWriter, r *http.Request) { + + var params = mux.Vars(r) + netname := params["network"] + + logger.Log(1, r.Header.Get("user"), "requested deleting network user", params["networkuser"], "on network", netname) + + _, err := logic.GetNetwork(netname) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + netuserToDelete := params["networkuser"] + if netuserToDelete == "" { + returnErrorResponse(w, r, formatError(errors.New("no group name provided"), "badrequest")) + return + } + + if err := pro.DeleteNetworkUser(netname, netuserToDelete); err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/controllers/node.go b/controllers/node.go index 146f5681..14075dbe 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -11,7 +11,9 @@ import ( "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" "github.com/gravitl/netmaker/mq" "github.com/gravitl/netmaker/servercfg" "golang.org/x/crypto/bcrypt" @@ -31,7 +33,7 @@ func nodeHandlers(r *mux.Router) { r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", securityCheck(false, http.HandlerFunc(createIngressGateway))).Methods("POST") r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", securityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods("DELETE") r.HandleFunc("/api/nodes/{network}/{nodeid}/approve", authorize(false, true, "user", http.HandlerFunc(uncordonNode))).Methods("POST") - r.HandleFunc("/api/nodes/{network}", nodeauth(http.HandlerFunc(createNode))).Methods("POST") + r.HandleFunc("/api/nodes/{network}", nodeauth(checkFreeTierLimits(node_l, http.HandlerFunc(createNode)))).Methods("POST") r.HandleFunc("/api/nodes/adm/{network}/lastmodified", authorize(false, true, "network", http.HandlerFunc(getLastModified))).Methods("GET") r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods("POST") } @@ -237,6 +239,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha returnErrorResponse(w, r, errorResponse) return } + isnetadmin := isadmin if errN == nil && isadmin { nodeID = "mastermac" @@ -244,7 +247,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha r.Header.Set("ismasterkey", "yes") } if !isadmin && params["network"] != "" { - if logic.StringSliceContains(networks, params["network"]) { + if logic.StringSliceContains(networks, params["network"]) && pro.IsUserNetAdmin(params["network"], username) { isnetadmin = true } } @@ -435,6 +438,7 @@ func getNode(w http.ResponseWriter, r *http.Request) { Node: node, Peers: peerUpdate.Peers, ServerConfig: servercfg.GetServerInfo(), + PeerIDs: peerUpdate.PeerIDs, } logger.Log(2, r.Header.Get("user"), "fetched node", params["nodeid"]) @@ -537,7 +541,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } - validKey := logic.IsKeyValid(networkName, node.AccessKey) + keyName, validKey := logic.IsKeyValid(networkName, node.AccessKey) if !validKey { // Check to see if network will allow manual sign up // may want to switch this up with the valid key check and avoid a DB call that way. @@ -554,6 +558,14 @@ func createNode(w http.ResponseWriter, r *http.Request) { return } } + user, err := pro.GetNetworkUser(networkName, promodels.NetworkUserID(keyName)) + if err == nil { + if user.ID != "" { + logger.Log(1, "associating new node with user", keyName) + node.OwnerID = string(user.ID) + } + } + key, keyErr := logic.RetrievePublicTrafficKey() if keyErr != nil { logger.Log(0, "error retrieving key: ", keyErr.Error()) @@ -584,6 +596,24 @@ func createNode(w http.ResponseWriter, r *http.Request) { return } + // check if key belongs to a user + // if so add to their netuser data + // if it fails remove the node and fail request + if user != nil { + var updatedUserNode bool + user.Nodes = append(user.Nodes, node.ID) // add new node to user + if err = pro.UpdateNetworkUser(networkName, user); err == nil { + logger.Log(1, "added node", node.ID, node.Name, "to user", string(user.ID)) + updatedUserNode = true + } + if !updatedUserNode { // user was found but not updated, so delete node + logger.Log(0, "failed to add node to user", keyName) + logic.DeleteNodeByID(&node, true) + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + } + peerUpdate, err := logic.GetPeerUpdate(&node) if err != nil && !database.IsEmptyRecord(err) { logger.Log(0, r.Header.Get("user"), @@ -596,6 +626,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { Node: node, Peers: peerUpdate.Peers, ServerConfig: servercfg.GetServerInfo(), + PeerIDs: peerUpdate.PeerIDs, } logger.Log(1, r.Header.Get("user"), "created new node", node.Name, "on network", node.Network) @@ -911,6 +942,13 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "badrequest")) return } + if r.Header.Get("ismaster") != "yes" { + username := r.Header.Get("user") + if username != "" && !doesUserOwnNode(username, params["network"], nodeid) { + returnErrorResponse(w, r, formatError(fmt.Errorf("user not permitted"), "badrequest")) + return + } + } //send update to node to be deleted before deleting on server otherwise message cannot be sent node.Action = models.NODE_DELETE @@ -919,6 +957,7 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } + returnSuccessResponse(w, r, nodeid+" deleted.") logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"]) @@ -1006,3 +1045,24 @@ func updateRelay(oldnode, newnode *models.Node) { } logic.UpdateNode(relay, newrelay) } + +func doesUserOwnNode(username, network, nodeID string) bool { + u, err := logic.GetUser(username) + if err != nil { + return false + } + if u.IsAdmin { + return true + } + + netUser, err := pro.GetNetworkUser(network, promodels.NetworkUserID(u.UserName)) + if err != nil { + return false + } + + if netUser.AccessLevel == pro.NET_ADMIN { + return true + } + + return logic.StringSliceContains(netUser.Nodes, nodeID) +} diff --git a/controllers/security.go b/controllers/security.go index 5c447dda..f793da5d 100644 --- a/controllers/security.go +++ b/controllers/security.go @@ -9,7 +9,9 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" "github.com/gravitl/netmaker/servercfg" ) @@ -58,6 +60,75 @@ func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { } } +func netUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var errorResponse = models.ErrorResponse{ + Code: http.StatusUnauthorized, Message: "unauthorized", + } + r.Header.Set("ismaster", "no") + + var params = mux.Vars(r) + var netUserName = params["networkuser"] + var network = params["network"] + + bearerToken := r.Header.Get("Authorization") + + var tokenSplit = strings.Split(bearerToken, " ") + var authToken = "" + + if len(tokenSplit) < 2 { + returnErrorResponse(w, r, errorResponse) + return + } else { + authToken = tokenSplit[1] + } + + isMasterAuthenticated := authenticateMaster(authToken) + if isMasterAuthenticated { + r.Header.Set("user", "master token user") + r.Header.Set("ismaster", "yes") + next.ServeHTTP(w, r) + return + } + + userName, _, isadmin, err := logic.VerifyUserToken(authToken) + if err != nil { + returnErrorResponse(w, r, errorResponse) + return + } + r.Header.Set("user", userName) + + if isadmin { + next.ServeHTTP(w, r) + return + } + + if isNodes || isClients { + necessaryAccess := pro.NET_ADMIN + if isClients { + necessaryAccess = pro.CLIENT_ACCESS + } + if isNodes { + necessaryAccess = pro.NODE_ACCESS + } + u, err := pro.GetNetworkUser(network, promodels.NetworkUserID(userName)) + if err != nil { + returnErrorResponse(w, r, errorResponse) + return + } + if u.AccessLevel > necessaryAccess { + returnErrorResponse(w, r, errorResponse) + return + } + } else if netUserName != userName { + returnErrorResponse(w, r, errorResponse) + return + } + + next.ServeHTTP(w, r) + } +} + // SecurityCheck - checks token stuff func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, string, error) { var tokenSplit = strings.Split(token, " ") @@ -88,6 +159,9 @@ func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, strin if len(netname) > 0 && (!authenticateNetworkUser(netname, userNetworks) || len(userNetworks) == 0) { return nil, username, unauthorized_err } + if !pro.IsUserNetAdmin(netname, username) { + return nil, "", unauthorized_err + } return userNetworks, username, nil } diff --git a/controllers/user.go b/controllers/user.go index eddc7cef..9c6dbb57 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -7,11 +7,17 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/gorilla/websocket" "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" +) + +var ( + upgrader = websocket.Upgrader{} ) func userHandlers(r *mux.Router) { @@ -22,12 +28,14 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(updateUser)))).Methods("PUT") r.HandleFunc("/api/users/networks/{username}", securityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods("PUT") r.HandleFunc("/api/users/{username}/adm", securityCheck(true, http.HandlerFunc(updateUserAdm))).Methods("PUT") - r.HandleFunc("/api/users/{username}", securityCheck(true, http.HandlerFunc(createUser))).Methods("POST") + r.HandleFunc("/api/users/{username}", securityCheck(true, checkFreeTierLimits(users_l, http.HandlerFunc(createUser)))).Methods("POST") r.HandleFunc("/api/users/{username}", securityCheck(true, http.HandlerFunc(deleteUser))).Methods("DELETE") r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(getUser)))).Methods("GET") r.HandleFunc("/api/users", securityCheck(true, http.HandlerFunc(getUsers))).Methods("GET") r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods("GET") r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods("GET") + r.HandleFunc("/api/oauth/node-handler", socketHandler) + r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterNodeSSO).Methods("GET") } // swagger:route POST /api/users/adm/authenticate nodes authenticateUser @@ -50,6 +58,11 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.", } + if !servercfg.IsBasicAuthEnabled() { + returnErrorResponse(response, request, formatError(fmt.Errorf("basic auth is disabled"), "badrequest")) + return + } + decoder := json.NewDecoder(request.Body) decoderErr := decoder.Decode(&authRequest) defer request.Body.Close() @@ -216,14 +229,20 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "badrequest")) return } - admin, err = logic.CreateAdmin(admin) + if !servercfg.IsBasicAuthEnabled() { + returnErrorResponse(w, r, formatError(fmt.Errorf("basic auth is disabled"), "badrequest")) + return + } + + admin, err = logic.CreateAdmin(admin) if err != nil { logger.Log(0, admin.UserName, "failed to create admin: ", err.Error()) returnErrorResponse(w, r, formatError(err, "badrequest")) return } + logger.Log(1, admin.UserName, "was made a new admin") json.NewEncoder(w).Encode(admin) } @@ -250,6 +269,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "badrequest")) return } + user, err = logic.CreateUser(user) if err != nil { logger.Log(0, user.UserName, "error creating new user: ", @@ -294,7 +314,13 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "badrequest")) return } - err = logic.UpdateUserNetworks(userchange.Networks, userchange.IsAdmin, &user) + err = logic.UpdateUserNetworks(userchange.Networks, userchange.Groups, userchange.IsAdmin, &models.ReturnUser{ + Groups: user.Groups, + IsAdmin: user.IsAdmin, + Networks: user.Networks, + UserName: user.UserName, + }) + if err != nil { logger.Log(0, username, "failed to update user networks: ", err.Error()) @@ -444,3 +470,15 @@ func deleteUser(w http.ResponseWriter, r *http.Request) { logger.Log(1, username, "was deleted") json.NewEncoder(w).Encode(params["username"] + " deleted.") } + +// Called when vpn client dials in to start the auth flow and first stage is to get register URL itself +func socketHandler(w http.ResponseWriter, r *http.Request) { + // Upgrade our raw HTTP connection to a websocket based one + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + logger.Log(0, "error during connection upgrade for node SSO sign-in:", err.Error()) + return + } + // Start handling the session + go auth.SessionHandler(conn) +} diff --git a/controllers/user_test.go b/controllers/user_test.go index 7d47dad7..71a919b1 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -31,7 +31,7 @@ func TestHasAdmin(t *testing.T) { assert.False(t, found) }) t.Run("No admin user", func(t *testing.T) { - var user = models.User{"noadmin", "password", nil, false} + var user = models.User{"noadmin", "password", nil, false, nil} _, err := logic.CreateUser(user) assert.Nil(t, err) found, err := logic.HasAdmin() @@ -39,7 +39,7 @@ func TestHasAdmin(t *testing.T) { assert.False(t, found) }) t.Run("admin user", func(t *testing.T) { - var user = models.User{"admin", "password", nil, true} + var user = models.User{"admin", "password", nil, true, nil} _, err := logic.CreateUser(user) assert.Nil(t, err) found, err := logic.HasAdmin() @@ -47,7 +47,7 @@ func TestHasAdmin(t *testing.T) { assert.True(t, found) }) t.Run("multiple admins", func(t *testing.T) { - var user = models.User{"admin1", "password", nil, true} + var user = models.User{"admin1", "password", nil, true, nil} _, err := logic.CreateUser(user) assert.Nil(t, err) found, err := logic.HasAdmin() @@ -59,7 +59,7 @@ func TestHasAdmin(t *testing.T) { func TestCreateUser(t *testing.T) { database.InitializeDatabase() deleteAllUsers() - user := models.User{"admin", "password", nil, true} + user := models.User{"admin", "password", nil, true, nil} t.Run("NoUser", func(t *testing.T) { admin, err := logic.CreateUser(user) assert.Nil(t, err) @@ -101,7 +101,7 @@ func TestDeleteUser(t *testing.T) { assert.False(t, deleted) }) t.Run("Existing User", func(t *testing.T) { - user := models.User{"admin", "password", nil, true} + user := models.User{"admin", "password", nil, true, nil} logic.CreateUser(user) deleted, err := logic.DeleteUser("admin") assert.Nil(t, err) @@ -166,7 +166,7 @@ func TestGetUser(t *testing.T) { assert.Equal(t, "", admin.UserName) }) t.Run("UserExisits", func(t *testing.T) { - user := models.User{"admin", "password", nil, true} + user := models.User{"admin", "password", nil, true, nil} logic.CreateUser(user) admin, err := logic.GetUser("admin") assert.Nil(t, err) @@ -183,7 +183,7 @@ func TestGetUserInternal(t *testing.T) { assert.Equal(t, "", admin.UserName) }) t.Run("UserExisits", func(t *testing.T) { - user := models.User{"admin", "password", nil, true} + user := models.User{"admin", "password", nil, true, nil} logic.CreateUser(user) admin, err := GetUserInternal("admin") assert.Nil(t, err) @@ -200,14 +200,14 @@ func TestGetUsers(t *testing.T) { assert.Equal(t, []models.ReturnUser(nil), admin) }) t.Run("UserExisits", func(t *testing.T) { - user := models.User{"admin", "password", nil, true} + user := models.User{"admin", "password", nil, true, nil} logic.CreateUser(user) admins, err := logic.GetUsers() assert.Nil(t, err) assert.Equal(t, user.UserName, admins[0].UserName) }) t.Run("MulipleUsers", func(t *testing.T) { - user := models.User{"user", "password", nil, true} + user := models.User{"user", "password", nil, true, nil} logic.CreateUser(user) admins, err := logic.GetUsers() assert.Nil(t, err) @@ -225,8 +225,8 @@ func TestGetUsers(t *testing.T) { func TestUpdateUser(t *testing.T) { database.InitializeDatabase() deleteAllUsers() - user := models.User{"admin", "password", nil, true} - newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true} + user := models.User{"admin", "password", nil, true, nil} + newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true, []string{}} t.Run("NonExistantUser", func(t *testing.T) { admin, err := logic.UpdateUser(newuser, user) assert.EqualError(t, err, "could not find any records") @@ -288,10 +288,10 @@ func TestVerifyAuthRequest(t *testing.T) { authRequest.Password = "password" jwt, err := logic.VerifyAuthRequest(authRequest) assert.Equal(t, "", jwt) - assert.EqualError(t, err, "incorrect credentials") + assert.EqualError(t, err, "error retrieving user from db: could not find any records") }) t.Run("Non-Admin", func(t *testing.T) { - user := models.User{"nonadmin", "somepass", nil, false} + user := models.User{"nonadmin", "somepass", nil, false, []string{}} logic.CreateUser(user) authRequest := models.UserAuthParams{"nonadmin", "somepass"} jwt, err := logic.VerifyAuthRequest(authRequest) @@ -299,7 +299,7 @@ func TestVerifyAuthRequest(t *testing.T) { assert.Nil(t, err) }) t.Run("WrongPassword", func(t *testing.T) { - user := models.User{"admin", "password", nil, false} + user := models.User{"admin", "password", nil, false, []string{}} logic.CreateUser(user) authRequest := models.UserAuthParams{"admin", "badpass"} jwt, err := logic.VerifyAuthRequest(authRequest) diff --git a/controllers/usergroups.go b/controllers/usergroups.go new file mode 100644 index 00000000..a73dc1f8 --- /dev/null +++ b/controllers/usergroups.go @@ -0,0 +1,71 @@ +package controller + +import ( + "encoding/json" + "errors" + "github.com/gravitl/netmaker/logger" + "net/http" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logic/pro" + "github.com/gravitl/netmaker/models/promodels" +) + +func userGroupsHandlers(r *mux.Router) { + r.HandleFunc("/api/usergroups", securityCheck(true, http.HandlerFunc(getUserGroups))).Methods("GET") + r.HandleFunc("/api/usergroups/{usergroup}", securityCheck(true, http.HandlerFunc(createUserGroup))).Methods("POST") + r.HandleFunc("/api/usergroups/{usergroup}", securityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods("DELETE") +} + +func getUserGroups(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + logger.Log(1, r.Header.Get("user"), "requested fetching user groups") + + userGroups, err := pro.GetUserGroups() + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + // Returns all the groups in JSON format + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(userGroups) +} + +func createUserGroup(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + newGroup := params["usergroup"] + + logger.Log(1, r.Header.Get("user"), "requested creating user group", newGroup) + + if newGroup == "" { + returnErrorResponse(w, r, formatError(errors.New("no group name provided"), "badrequest")) + return + } + + err := pro.InsertUserGroup(promodels.UserGroupName(newGroup)) + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + + w.WriteHeader(http.StatusOK) +} + +func deleteUserGroup(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + groupToDelete := params["usergroup"] + logger.Log(1, r.Header.Get("user"), "requested deleting user group", groupToDelete) + + if groupToDelete == "" { + returnErrorResponse(w, r, formatError(errors.New("no group name provided"), "badrequest")) + return + } + + if err := pro.DeleteUserGroup(promodels.UserGroupName(groupToDelete)); err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/database/database.go b/database/database.go index 2b3b0d4a..dea641be 100644 --- a/database/database.go +++ b/database/database.go @@ -59,6 +59,18 @@ const NODE_ACLS_TABLE_NAME = "nodeacls" // SSO_STATE_CACHE - holds sso session information for OAuth2 sign-ins const SSO_STATE_CACHE = "ssostatecache" +// METRICS_TABLE_NAME - stores network metrics +const METRICS_TABLE_NAME = "metrics" + +// NETWORK_USER_TABLE_NAME - network user table tracks stats for a network user per network +const NETWORK_USER_TABLE_NAME = "networkusers" + +// USER_GROUPS_TABLE_NAME - table for storing usergroups +const USER_GROUPS_TABLE_NAME = "usergroups" + +// CACHE_TABLE_NAME - caching table +const CACHE_TABLE_NAME = "cache" + // == ERROR CONSTS == // NO_RECORD - no singular result found @@ -139,6 +151,10 @@ func createTables() { createTable(GENERATED_TABLE_NAME) createTable(NODE_ACLS_TABLE_NAME) createTable(SSO_STATE_CACHE) + createTable(METRICS_TABLE_NAME) + createTable(NETWORK_USER_TABLE_NAME) + createTable(USER_GROUPS_TABLE_NAME) + createTable(CACHE_TABLE_NAME) } func createTable(tableName string) error { diff --git a/docker/mosquitto.conf b/docker/mosquitto.conf index 8d3ab239..6ee92ddc 100644 --- a/docker/mosquitto.conf +++ b/docker/mosquitto.conf @@ -10,3 +10,7 @@ keyfile /mosquitto/certs/server.key listener 1883 allow_anonymous true + +listener 1884 +allow_anonymous false +password_file /etc/mosquitto.passwords diff --git a/docker/mosquitto.passwords b/docker/mosquitto.passwords new file mode 100644 index 00000000..d6966cf3 --- /dev/null +++ b/docker/mosquitto.passwords @@ -0,0 +1 @@ +netmaker-exporter:$7$101$9kcXwXP+nUMh06gm$MND2YjtRSvcZTXjMn7xYKoqUFQxG6NOgqWmXIcxxxZksM9cA8732URQWOsPHqpGEvVF9mSVagM1MBEMIKwZm2A== diff --git a/ee/LICENSE b/ee/LICENSE new file mode 100644 index 00000000..4de7fe6c --- /dev/null +++ b/ee/LICENSE @@ -0,0 +1,10 @@ +The Netmaker Enterprise license (the “Enterprise License”) +Copyright (c) 2022 Netmaker, Inc. + +With regard to the Netmaker Software: + +This software and associated documentation files (the "Software") may only be used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Netmaker Subscription Terms of Service, available at https://netmaker.io/terms (the “Enterprise Terms”), or other agreement governing the use of the Software, as agreed by you and Netmaker, and otherwise have a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients. Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients as allocated by the license. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For all third party components incorporated into the Netmaker Software, those components are licensed under the original license provided by the owner of the applicable component. diff --git a/ee/license.go b/ee/license.go new file mode 100644 index 00000000..f34e50f8 --- /dev/null +++ b/ee/license.go @@ -0,0 +1,216 @@ +package ee + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "math" + "net/http" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" + "github.com/gravitl/netmaker/netclient/ncutils" + "github.com/gravitl/netmaker/servercfg" +) + +// AddLicenseHooks - adds the validation and cache clear hooks +func AddLicenseHooks() { + logic.AddHook(ValidateLicense) + logic.AddHook(ClearLicenseCache) +} + +// ValidateLicense - the initial license check for netmaker server +// checks if a license is valid + limits are not exceeded +// if license is free_tier and limits exceeds, then server should terminate +// if license is not valid, server should terminate +func ValidateLicense() error { + licenseKeyValue := servercfg.GetLicenseKey() + netmakerAccountID := servercfg.GetNetmakerAccountID() + logger.Log(0, "proceeding with Netmaker license validation...") + if len(licenseKeyValue) == 0 || len(netmakerAccountID) == 0 { + logger.FatalLog(errValidation.Error()) + } + + apiPublicKey, err := getLicensePublicKey(licenseKeyValue) + if err != nil { + logger.FatalLog(errValidation.Error()) + } + + tempPubKey, tempPrivKey, err := pro.FetchApiServerKeys() + if err != nil { + logger.FatalLog(errValidation.Error()) + } + + licenseSecret := LicenseSecret{ + UserID: netmakerAccountID, + Limits: getCurrentServerLimit(), + } + + secretData, err := json.Marshal(&licenseSecret) + if err != nil { + logger.FatalLog(errValidation.Error()) + } + + encryptedData, err := ncutils.BoxEncrypt(secretData, apiPublicKey, tempPrivKey) + if err != nil { + logger.FatalLog(errValidation.Error()) + } + + validationResponse, err := validateLicenseKey(encryptedData, tempPubKey) + if err != nil || len(validationResponse) == 0 { + logger.FatalLog(errValidation.Error()) + } + + var licenseResponse ValidatedLicense + if err = json.Unmarshal(validationResponse, &licenseResponse); err != nil { + logger.FatalLog(errValidation.Error()) + } + + respData, err := ncutils.BoxDecrypt(base64decode(licenseResponse.EncryptedLicense), apiPublicKey, tempPrivKey) + if err != nil { + logger.FatalLog(errValidation.Error()) + } + + license := LicenseKey{} + if err = json.Unmarshal(respData, &license); err != nil { + logger.FatalLog(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 + } + + logger.Log(0, "License validation succeeded!") + return nil +} + +func getLicensePublicKey(licensePubKeyEncoded string) (*[32]byte, error) { + decodedPubKey := base64decode(licensePubKeyEncoded) + return ncutils.ConvertBytesToKey(decodedPubKey) +} + +func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, error) { + + publicKeyBytes, err := ncutils.ConvertKeyToBytes(publicKey) + if err != nil { + return nil, err + } + + msg := ValidateLicenseRequest{ + NmServerPubKey: base64encode(publicKeyBytes), + EncryptedPart: base64encode(encryptedData), + } + + requestBody, err := json.Marshal(msg) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, api_endpoint, bytes.NewReader(requestBody)) + 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{} + var body []byte + validateResponse, err := client.Do(req) + if err != nil { // check cache + body, err = getCachedResponse() + if err != nil { + return nil, err + } + logger.Log(3, "proceeding with cached response, Netmaker API may be down") + } else { + defer validateResponse.Body.Close() + if validateResponse.StatusCode != 200 { + return nil, fmt.Errorf("could not validate license") + } // if you received a 200 cache the response locally + + body, err = ioutil.ReadAll(validateResponse.Body) + if err != nil { + return nil, err + } + cacheResponse(body) + } + + return body, err +} + +func cacheResponse(response []byte) error { + var lrc = licenseResponseCache{ + Body: response, + } + + record, err := json.Marshal(&lrc) + if err != nil { + return err + } + + return database.Insert(license_cache_key, string(record), database.CACHE_TABLE_NAME) +} + +func getCachedResponse() ([]byte, error) { + var lrc licenseResponseCache + record, err := database.FetchRecord(database.CACHE_TABLE_NAME, license_cache_key) + if err != nil { + return nil, err + } + if err = json.Unmarshal([]byte(record), &lrc); err != nil { + return nil, err + } + return lrc.Body, nil +} + +// ClearLicenseCache - clears the cached validate response +func ClearLicenseCache() error { + return database.DeleteRecord(database.CACHE_TABLE_NAME, license_cache_key) +} + +// AddServerIDIfNotPresent - add's current server ID to DB if not present +func AddServerIDIfNotPresent() error { + currentNodeID := servercfg.GetNodeID() + currentServerIDs := serverIDs{} + + record, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, server_id_key) + if err != nil && !database.IsEmptyRecord(err) { + return err + } else if err == nil { + if err = json.Unmarshal([]byte(record), ¤tServerIDs); err != nil { + return err + } + } + + if !logic.StringSliceContains(currentServerIDs.ServerIDs, currentNodeID) { + currentServerIDs.ServerIDs = append(currentServerIDs.ServerIDs, currentNodeID) + data, err := json.Marshal(¤tServerIDs) + if err != nil { + return err + } + return database.Insert(server_id_key, string(data), database.SERVERCONF_TABLE_NAME) + } + + return nil +} + +func getServerCount() int { + if record, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, server_id_key); err == nil { + currentServerIDs := serverIDs{} + if err = json.Unmarshal([]byte(record), ¤tServerIDs); err == nil { + return len(currentServerIDs.ServerIDs) + } + } + return 1 +} diff --git a/ee/types.go b/ee/types.go new file mode 100644 index 00000000..d802e1af --- /dev/null +++ b/ee/types.go @@ -0,0 +1,87 @@ +package ee + +import "fmt" + +const ( + api_endpoint = "https://api.controller.netmaker.io/api/v1/license/validate" + license_cache_key = "license_response_cache" + license_validation_err_msg = "invalid license" + server_id_key = "nm-server-id" +) + +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, + 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 +} + +// ValidatedLicense - the validated license struct +type ValidatedLicense struct { + LicenseValue string `json:"license_value" binding:"required"` // license that validation is being requested for + EncryptedLicense string `json:"encrypted_license" binding:"required"` // to be decrypted by Netmaker using Netmaker server's private key +} + +// 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"` +} + +// 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"` +} + +// LicenseLimits.SetDefaults - sets the default values for limits +func (l *LicenseLimits) SetDefaults() { + l.Clients = 0 + l.Servers = 1 + l.Nodes = 0 + l.Users = 1 +} + +// ValidateLicenseRequest - used for request to validate license endpoint +type ValidateLicenseRequest struct { + 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"` +} + +type licenseResponseCache struct { + Body []byte `json:"body" binding:"required"` +} + +type serverIDs struct { + ServerIDs []string `json:"server_ids" binding:"required"` +} diff --git a/ee/util.go b/ee/util.go new file mode 100644 index 00000000..26e6262f --- /dev/null +++ b/ee/util.go @@ -0,0 +1,54 @@ +package ee + +import ( + "encoding/base64" + + "github.com/gravitl/netmaker/logic" +) + +var isEnterprise bool + +// SetIsEnterprise - sets server to use enterprise features +func SetIsEnterprise() { + isEnterprise = true +} + +// IsEnterprise - checks if enterprise binary or not +func IsEnterprise() bool { + return isEnterprise +} + +// base64encode - base64 encode helper function +func base64encode(input []byte) string { + return base64.StdEncoding.EncodeToString(input) +} + +// base64decode - base64 decode helper function +func base64decode(input string) []byte { + + bytes, err := base64.StdEncoding.DecodeString(input) + + if err != nil { + return nil + } + + return bytes +} + +func getCurrentServerLimit() (limits LicenseLimits) { + limits.SetDefaults() + nodes, err := logic.GetAllNodes() + if err == nil { + limits.Nodes = len(nodes) + } + clients, err := logic.GetAllExtClients() + if err == nil { + limits.Clients = len(clients) + } + users, err := logic.GetUsers() + if err == nil { + limits.Users = len(users) + } + limits.Servers = getServerCount() + return +} diff --git a/go.mod b/go.mod index 03f994b7..aecdff4e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,10 @@ require ( github.com/txn2/txeh v1.3.0 github.com/urfave/cli/v2 v2.14.1 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd + golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/text v0.3.7 // indirect golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31 google.golang.org/protobuf v1.28.1 // indirect @@ -30,6 +33,7 @@ require ( fyne.io/fyne/v2 v2.2.3 github.com/c-robinson/iplib v1.0.3 github.com/cloverstd/tcping v0.1.1 + github.com/go-ping/ping v1.1.0 github.com/guumaster/hostctl v1.1.3 github.com/kr/pretty v0.3.0 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 @@ -37,7 +41,9 @@ require ( require ( github.com/coreos/go-oidc/v3 v3.3.0 + github.com/gorilla/websocket v1.4.2 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e + golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 ) require ( @@ -67,7 +73,6 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/gorilla/websocket v1.4.2 // indirect github.com/josharian/native v1.0.0 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/kr/text v0.2.0 // indirect @@ -91,10 +96,7 @@ require ( github.com/yuin/goldmark v1.4.13 // indirect golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect - golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 79bb120f..8f0568a0 100644 --- a/go.sum +++ b/go.sum @@ -163,6 +163,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= +github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -251,6 +253,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= @@ -664,6 +667,7 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -700,6 +704,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdp golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/logic/accesskeys.go b/logic/accesskeys.go index 895dac18..e62599fd 100644 --- a/logic/accesskeys.go +++ b/logic/accesskeys.go @@ -161,11 +161,11 @@ func DecrimentKey(networkName string, keyvalue string) { } // IsKeyValid - check if key is valid -func IsKeyValid(networkname string, keyvalue string) bool { +func IsKeyValid(networkname string, keyvalue string) (string, bool) { network, err := GetParentNetwork(networkname) if err != nil { - return false + return "", false } accesskeys := network.AccessKeys @@ -185,7 +185,7 @@ func IsKeyValid(networkname string, keyvalue string) bool { isvalid = true } } - return isvalid + return key.Name, isvalid } // RemoveKeySensitiveInfo - remove sensitive key info diff --git a/logic/auth.go b/logic/auth.go index ba8205bc..4282f490 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -9,7 +9,9 @@ import ( "github.com/go-playground/validator/v10" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" "golang.org/x/crypto/bcrypt" ) @@ -95,8 +97,7 @@ func CreateUser(user models.User) (models.User, error) { // set password to encrypted password user.Password = string(hash) - tokenString, _ := CreateUserJWT(user.UserName, user.Networks, user.IsAdmin) - + tokenString, _ := CreateProUserJWT(user.UserName, user.Networks, user.Groups, user.IsAdmin) if tokenString == "" { // returnErrorResponse(w, r, errorResponse) return user, err @@ -108,8 +109,45 @@ func CreateUser(user models.User) (models.User, error) { return user, err } err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME) + if err != nil { + return user, err + } - return user, err + // == PRO == Add user to every network as network user == + currentNets, err := GetNetworks() + if err != nil { + currentNets = []models.Network{} + } + for i := range currentNets { + newUser := promodels.NetworkUser{ + ID: promodels.NetworkUserID(user.UserName), + Clients: []string{}, + Nodes: []string{}, + } + + pro.AddProNetDefaults(¤tNets[i]) + if pro.IsUserAllowed(¤tNets[i], user.UserName, user.Groups) { + newUser.AccessLevel = currentNets[i].ProSettings.DefaultAccessLevel + newUser.ClientLimit = currentNets[i].ProSettings.DefaultUserClientLimit + newUser.NodeLimit = currentNets[i].ProSettings.DefaultUserNodeLimit + } else { + newUser.AccessLevel = pro.NO_ACCESS + newUser.ClientLimit = 0 + newUser.NodeLimit = 0 + } + + // legacy + if StringSliceContains(user.Networks, currentNets[i].NetID) { + newUser.AccessLevel = pro.NET_ADMIN + } + userErr := pro.CreateNetworkUser(¤tNets[i], &newUser) + if userErr != nil { + logger.Log(0, "failed to add network user data on network", currentNets[i].NetID, "for user", user.UserName) + } + } + // == END PRO == + + return user, nil } // CreateAdmin - creates an admin user @@ -136,10 +174,10 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { //Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved). record, err := database.FetchRecord(database.USERS_TABLE_NAME, authRequest.UserName) if err != nil { - return "", errors.New("incorrect credentials") + return "", errors.New("error retrieving user from db: " + err.Error()) } if err = json.Unmarshal([]byte(record), &result); err != nil { - return "", errors.New("incorrect credentials") + return "", errors.New("error unmarshalling user json: " + err.Error()) } // compare password from request to stored password in database @@ -150,14 +188,15 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { } //Create a new JWT for the node - tokenString, _ := CreateUserJWT(authRequest.UserName, result.Networks, result.IsAdmin) + tokenString, _ := CreateProUserJWT(authRequest.UserName, result.Networks, result.Groups, result.IsAdmin) return tokenString, nil } // UpdateUserNetworks - updates the networks of a given user -func UpdateUserNetworks(newNetworks []string, isadmin bool, currentUser *models.User) error { +func UpdateUserNetworks(newNetworks, newGroups []string, isadmin bool, currentUser *models.ReturnUser) error { // check if user exists - if returnedUser, err := GetUser(currentUser.UserName); err != nil { + returnedUser, err := GetUser(currentUser.UserName) + if err != nil { return err } else if returnedUser.IsAdmin { return fmt.Errorf("can not make changes to an admin user, attempted to change %s", returnedUser.UserName) @@ -166,18 +205,46 @@ func UpdateUserNetworks(newNetworks []string, isadmin bool, currentUser *models. currentUser.IsAdmin = true currentUser.Networks = nil } else { + // == PRO == + currentUser.Groups = newGroups + for _, n := range newNetworks { + if !StringSliceContains(currentUser.Networks, n) { + // make net admin of any network not previously assigned + pro.MakeNetAdmin(n, currentUser.UserName) + } + } + // Compare networks, find networks not in previous + for _, n := range currentUser.Networks { + if !StringSliceContains(newNetworks, n) { + // if user was removed from a network, re-assign access to net default level + if network, err := GetNetwork(n); err == nil { + if network.ProSettings != nil { + ok := pro.AssignAccessLvl(n, currentUser.UserName, network.ProSettings.DefaultAccessLevel) + if ok { + logger.Log(0, "changed", currentUser.UserName, "access level on network", network.NetID, "to", fmt.Sprintf("%d", network.ProSettings.DefaultAccessLevel)) + } + } + } + } + } + + if err := AdjustGroupPermissions(currentUser); err != nil { + logger.Log(0, "failed to update user", currentUser.UserName, "after group update", err.Error()) + } + // == END PRO == + currentUser.Networks = newNetworks } - data, err := json.Marshal(currentUser) - if err != nil { - return err - } - if err = database.Insert(currentUser.UserName, string(data), database.USERS_TABLE_NAME); err != nil { - return err - } + _, err = UpdateUser(models.User{ + UserName: currentUser.UserName, + Networks: currentUser.Networks, + IsAdmin: currentUser.IsAdmin, + Password: "", + Groups: currentUser.Groups, + }, returnedUser) - return nil + return err } // UpdateUser - updates a given user @@ -187,11 +254,6 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { return models.User{}, err } - err := ValidateUser(userchange) - if err != nil { - return models.User{}, err - } - queryUser := user.UserName if userchange.UserName != "" { @@ -200,6 +262,9 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { if len(userchange.Networks) > 0 { user.Networks = userchange.Networks } + if len(userchange.Groups) > 0 { + user.Groups = userchange.Groups + } if userchange.Password != "" { // encrypt that password so we never see it again hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5) @@ -212,6 +277,12 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { user.Password = userchange.Password } + + err := ValidateUser(user) + if err != nil { + return models.User{}, err + } + if err = database.DeleteRecord(database.USERS_TABLE_NAME, queryUser); err != nil { return models.User{}, err } @@ -256,6 +327,20 @@ func DeleteUser(user string) (bool, error) { if err != nil { return false, err } + + // == pro - remove user from all network user instances == + currentNets, err := GetNetworks() + if err != nil { + return true, err + } + + for i := range currentNets { + netID := currentNets[i].NetID + if err = pro.DeleteNetworkUser(netID, user); err != nil { + logger.Log(0, "failed to remove", user, "as network user from network", netID, err.Error()) + } + } + return true, nil } @@ -313,6 +398,9 @@ func IsStateValid(state string) (string, bool) { if s.Value != "" { delState(state) } + if err != nil { + logger.Log(2, "error retrieving oauth state:", err.Error()) + } return s.Value, err == nil } @@ -320,3 +408,51 @@ func IsStateValid(state string) (string, bool) { func delState(state string) error { return database.DeleteRecord(database.SSO_STATE_CACHE, state) } + +// PRO + +// AdjustGroupPermissions - adjusts a given user's network access based on group changes +func AdjustGroupPermissions(user *models.ReturnUser) error { + networks, err := GetNetworks() + if err != nil { + return err + } + // UPDATE + // go through all networks and see if new group is in + // if access level of current user is greater (value) than network's default + // assign network's default + // DELETE + // if user not allowed on network a + for i := range networks { + AdjustNetworkUserPermissions(user, &networks[i]) + } + + return nil +} + +// AdjustGroupPermissions - adjusts a given user's network access based on group changes +func AdjustNetworkUserPermissions(user *models.ReturnUser, network *models.Network) error { + networkUser, err := pro.GetNetworkUser( + network.NetID, + promodels.NetworkUserID(user.UserName), + ) + if err == nil && network.ProSettings != nil { + if pro.IsUserAllowed(network, user.UserName, user.Groups) { + if networkUser.AccessLevel > network.ProSettings.DefaultAccessLevel { + networkUser.AccessLevel = network.ProSettings.DefaultAccessLevel + } + if networkUser.NodeLimit < network.ProSettings.DefaultUserNodeLimit { + networkUser.NodeLimit = network.ProSettings.DefaultUserNodeLimit + } + if networkUser.ClientLimit < network.ProSettings.DefaultUserClientLimit { + networkUser.ClientLimit = network.ProSettings.DefaultUserClientLimit + } + } else { + networkUser.AccessLevel = pro.NO_ACCESS + networkUser.NodeLimit = 0 + networkUser.ClientLimit = 0 + } + pro.UpdateNetworkUser(network.NetID, networkUser) + } + return err +} diff --git a/logic/extpeers.go b/logic/extpeers.go index b7fbb299..98a56a6e 100644 --- a/logic/extpeers.go +++ b/logic/extpeers.go @@ -183,3 +183,40 @@ func UpdateExtClient(newclientid string, network string, enabled bool, client *m CreateExtClient(client) return client, err } + +// GetExtClientsByID - gets the clients of attached gateway +func GetExtClientsByID(nodeid, network string) ([]models.ExtClient, error) { + var result []models.ExtClient + currentClients, err := GetNetworkExtClients(network) + if err != nil { + return result, err + } + for i := range currentClients { + if currentClients[i].IngressGatewayID == nodeid { + result = append(result, currentClients[i]) + } + } + return result, nil +} + +// GetAllExtClients - gets all ext clients from DB +func GetAllExtClients() ([]models.ExtClient, error) { + var clients = []models.ExtClient{} + currentNetworks, err := GetNetworks() + if err != nil && database.IsEmptyRecord(err) { + return clients, nil + } else if err != nil { + return clients, err + } + + for i := range currentNetworks { + netName := currentNetworks[i].NetID + netClients, err := GetNetworkExtClients(netName) + if err != nil { + continue + } + clients = append(clients, netClients...) + } + + return clients, nil +} diff --git a/logic/jwts.go b/logic/jwts.go index f91a92be..4ddad9ce 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -53,6 +53,30 @@ func CreateJWT(uuid string, macAddress string, network string) (response string, return "", err } +// CreateProUserJWT - creates a user jwt token +func CreateProUserJWT(username string, networks, groups []string, isadmin bool) (response string, err error) { + expirationTime := time.Now().Add(60 * 12 * time.Minute) + claims := &models.UserClaims{ + UserName: username, + Networks: networks, + IsAdmin: isadmin, + Groups: groups, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "Netmaker", + Subject: fmt.Sprintf("user|%s", username), + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(expirationTime), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(jwtSecretKey) + if err == nil { + return tokenString, nil + } + return "", err +} + // CreateUserJWT - creates a user jwt token func CreateUserJWT(username string, networks []string, isadmin bool) (response string, err error) { expirationTime := time.Now().Add(60 * 12 * time.Minute) diff --git a/logic/metrics.go b/logic/metrics.go new file mode 100644 index 00000000..f60bcd76 --- /dev/null +++ b/logic/metrics.go @@ -0,0 +1,65 @@ +package logic + +import ( + "encoding/json" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models" +) + +// GetMetrics - gets the metrics +func GetMetrics(nodeid string) (*models.Metrics, error) { + var metrics models.Metrics + record, err := database.FetchRecord(database.METRICS_TABLE_NAME, nodeid) + if err != nil { + if database.IsEmptyRecord(err) { + return &metrics, nil + } + return &metrics, err + } + err = json.Unmarshal([]byte(record), &metrics) + if err != nil { + return &metrics, err + } + return &metrics, nil +} + +// UpdateMetrics - updates the metrics of a given client +func UpdateMetrics(nodeid string, metrics *models.Metrics) error { + data, err := json.Marshal(metrics) + if err != nil { + return err + } + return database.Insert(nodeid, string(data), database.METRICS_TABLE_NAME) +} + +// DeleteMetrics - deletes metrics of a given node +func DeleteMetrics(nodeid string) error { + return database.DeleteRecord(database.METRICS_TABLE_NAME, nodeid) +} + +// CollectServerMetrics - collects metrics for given server node +func CollectServerMetrics(serverID string, networkNodes []models.Node) *models.Metrics { + newServerMetrics := models.Metrics{} + newServerMetrics.Connectivity = make(map[string]models.Metric) + for i := range networkNodes { + currNodeID := networkNodes[i].ID + if currNodeID == serverID { + continue + } + if currMetrics, err := GetMetrics(currNodeID); err == nil { + if currMetrics.Connectivity != nil && currMetrics.Connectivity[serverID].Connected { + metrics := currMetrics.Connectivity[serverID] + metrics.NodeName = networkNodes[i].Name + metrics.IsServer = "no" + newServerMetrics.Connectivity[currNodeID] = metrics + } + } else { + newServerMetrics.Connectivity[currNodeID] = models.Metric{ + Connected: false, + Latency: 999, + } + } + } + return &newServerMetrics +} diff --git a/logic/networks.go b/logic/networks.go index 0a3ad2dc..f82f22a0 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -13,6 +13,7 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic/acls/nodeacls" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/validation" @@ -62,6 +63,9 @@ func DeleteNetwork(network string) error { } else { logger.Log(1, "could not remove servers before deleting network", network) } + if err = pro.RemoveAllNetworkUsers(network); err != nil { + logger.Log(0, "failed to remove network users on network delete for network", network, err.Error()) + } return database.DeleteRecord(database.NETWORKS_TABLE_NAME, network) } return errors.New("node check failed. All nodes must be deleted before deleting network") @@ -88,20 +92,32 @@ func CreateNetwork(network models.Network) (models.Network, error) { network.SetNodesLastModified() network.SetNetworkLastModified() + pro.AddProNetDefaults(&network) + err := ValidateNetwork(&network, false) if err != nil { //returnErrorResponse(w, r, formatError(err, "badrequest")) return models.Network{}, err } + if err = pro.InitializeNetworkUsers(network.NetID); err != nil { + return models.Network{}, err + } + data, err := json.Marshal(&network) if err != nil { return models.Network{}, err } + if err = database.Insert(network.NetID, string(data), database.NETWORKS_TABLE_NAME); err != nil { return models.Network{}, err } + // == add all current users to network as network users == + if err = InitializeNetUsers(&network); err != nil { + return network, err + } + return network, nil } @@ -526,25 +542,29 @@ func IsNetworkNameUnique(network *models.Network) (bool, error) { } // UpdateNetwork - updates a network with another network's fields -func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (bool, bool, bool, bool, error) { +func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (bool, bool, bool, bool, []string, []string, error) { if err := ValidateNetwork(newNetwork, true); err != nil { - return false, false, false, false, err + return false, false, false, false, nil, nil, err } if newNetwork.NetID == currentNetwork.NetID { hasrangeupdate4 := newNetwork.AddressRange != currentNetwork.AddressRange hasrangeupdate6 := newNetwork.AddressRange6 != currentNetwork.AddressRange6 localrangeupdate := newNetwork.LocalRange != currentNetwork.LocalRange hasholepunchupdate := newNetwork.DefaultUDPHolePunch != currentNetwork.DefaultUDPHolePunch + groupDelta := append(StringDifference(newNetwork.ProSettings.AllowedGroups, currentNetwork.ProSettings.AllowedGroups), + StringDifference(currentNetwork.ProSettings.AllowedGroups, newNetwork.ProSettings.AllowedGroups)...) + userDelta := append(StringDifference(newNetwork.ProSettings.AllowedUsers, currentNetwork.ProSettings.AllowedUsers), + StringDifference(currentNetwork.ProSettings.AllowedUsers, newNetwork.ProSettings.AllowedUsers)...) data, err := json.Marshal(newNetwork) if err != nil { - return false, false, false, false, err + return false, false, false, false, nil, nil, err } newNetwork.SetNetworkLastModified() err = database.Insert(newNetwork.NetID, string(data), database.NETWORKS_TABLE_NAME) - return hasrangeupdate4, hasrangeupdate6, localrangeupdate, hasholepunchupdate, err + return hasrangeupdate4, hasrangeupdate6, localrangeupdate, hasholepunchupdate, groupDelta, userDelta, err } // copy values - return false, false, false, false, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.") + return false, false, false, false, nil, nil, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.") } // GetNetwork - gets a network from database @@ -596,6 +616,15 @@ func ValidateNetwork(network *models.Network, isUpdate bool) error { } } + if network.ProSettings != nil { + if network.ProSettings.DefaultAccessLevel < pro.NET_ADMIN || network.ProSettings.DefaultAccessLevel > pro.NO_ACCESS { + return fmt.Errorf("invalid access level") + } + if network.ProSettings.DefaultUserClientLimit < 0 || network.ProSettings.DefaultUserNodeLimit < 0 { + return fmt.Errorf("invalid node/client limit provided") + } + } + return err } diff --git a/logic/nodes.go b/logic/nodes.go index b1b6e916..84227f28 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -13,6 +13,8 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic/acls" "github.com/gravitl/netmaker/logic/acls/nodeacls" + "github.com/gravitl/netmaker/logic/pro" + "github.com/gravitl/netmaker/logic/pro/proacls" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/servercfg" @@ -128,6 +130,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error { } } } + nodeACLDelta := currentNode.DefaultACL != newNode.DefaultACL newNode.Fill(currentNode) if currentNode.IsServer == "yes" && !validateServer(currentNode, newNode) { @@ -137,7 +140,15 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error { if err := ValidateNode(newNode, true); err != nil { return err } + if newNode.ID == currentNode.ID { + if nodeACLDelta { + if err := updateProNodeACLS(newNode); err != nil { + logger.Log(1, "failed to apply node level ACLs during creation of node", newNode.ID, "-", err.Error()) + return err + } + } + newNode.SetLastModified() if data, err := json.Marshal(newNode); err != nil { return err @@ -145,6 +156,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error { return database.Insert(newNode.ID, string(data), database.NODES_TABLE_NAME) } } + return fmt.Errorf("failed to update node " + currentNode.ID + ", cannot change ID.") } @@ -176,9 +188,16 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error { if err = database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil { return err } + if servercfg.IsDNSMode() { SetDNS() } + if node.OwnerID != "" { + err = pro.DissociateNetworkUserNode(node.OwnerID, node.Network, node.ID) + if err != nil { + logger.Log(0, "failed to dissasociate", node.OwnerID, "from node", node.ID, ":", err.Error()) + } + } _, err = nodeacls.RemoveNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID)) if err != nil { @@ -186,6 +205,10 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error { logger.Log(2, "attempted to remove node ACL for node", node.Name, node.ID) } // removeZombie <- node.ID + if err = DeleteMetrics(node.ID); err != nil { + logger.Log(1, "unable to remove metrics from DB for node", node.ID, err.Error()) + } + if node.IsServer == "yes" { return removeLocalServer(node) } @@ -219,6 +242,9 @@ func ValidateNode(node *models.Node, isUpdate bool) error { _ = v.RegisterValidation("checkyesorno", func(fl validator.FieldLevel) bool { return validation.CheckYesOrNo(fl) }) + _ = v.RegisterValidation("checkyesornoorunset", func(fl validator.FieldLevel) bool { + return validation.CheckYesOrNoOrUnset(fl) + }) err := v.Struct(node) return err @@ -255,6 +281,10 @@ func CreateNode(node *models.Node) error { } } + if node.DefaultACL == "" { + node.DefaultACL = "unset" + } + reverse := node.IsServer == "yes" if node.Address == "" { if parentNetwork.IsIPv4 == "yes" { @@ -305,9 +335,19 @@ func CreateNode(node *models.Node) error { return err } + if err = updateProNodeACLS(node); err != nil { + logger.Log(1, "failed to apply node level ACLs during creation of node", node.ID, "-", err.Error()) + return err + } + if node.IsPending != "yes" { DecrimentKey(node.Network, node.AccessKey) } + + if err = UpdateMetrics(node.ID, &models.Metrics{Connectivity: make(map[string]models.Metric)}); err != nil { + logger.Log(1, "failed to initialize metrics for node", node.Name, node.ID, err.Error()) + } + SetNetworkNodesLastModified(node.Network) if servercfg.IsDNSMode() { err = SetDNS() @@ -677,3 +717,19 @@ func findNode(ip string) (*models.Node, error) { } return nil, errors.New("node not found") } + +// == PRO == + +func updateProNodeACLS(node *models.Node) error { + // == PRO node ACLs == + networkNodes, err := GetNetworkNodes(node.Network) + if err != nil { + return err + } + if err = proacls.AdjustNodeAcls(node, networkNodes[:]); err != nil { + return err + } + return nil +} + +// == END PRO == diff --git a/logic/peers.go b/logic/peers.go index c197f3f1..16a86fe1 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -32,6 +32,7 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) { } else if network.IsPointToSite == "yes" && node.IsHub != "yes" { isP2S = true } + var peerMap = make(models.PeerMap) // udppeers = the peers parsed from the local interface // gives us correct port to reach @@ -150,14 +151,24 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) { } peers = append(peers, peerData) + peerMap[peer.PublicKey] = models.IDandAddr{ + Name: peer.Name, + ID: peer.ID, + Address: peer.PrimaryAddress(), + IsServer: peer.IsServer, + } + if peer.IsServer == "yes" { serverNodeAddresses = append(serverNodeAddresses, models.ServerAddr{IsLeader: IsLeader(&peer), Address: peer.Address}) } } if node.IsIngressGateway == "yes" { - extPeers, err := getExtPeers(node) + extPeers, idsAndAddr, err := getExtPeers(node) if err == nil { peers = append(peers, extPeers...) + for i := range idsAndAddr { + peerMap[idsAndAddr[i].ID] = idsAndAddr[i] + } } else { log.Println("ERROR RETRIEVING EXTERNAL PEERS", err) } @@ -168,14 +179,16 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) { peerUpdate.Peers = peers peerUpdate.ServerAddrs = serverNodeAddresses peerUpdate.DNS = getPeerDNS(node.Network) + peerUpdate.PeerIDs = peerMap return peerUpdate, nil } -func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, error) { +func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) { var peers []wgtypes.PeerConfig + var idsAndAddr []models.IDandAddr extPeers, err := GetExtPeersList(node) if err != nil { - return peers, err + return peers, idsAndAddr, err } for _, extPeer := range extPeers { pubkey, err := wgtypes.ParseKey(extPeer.PublicKey) @@ -209,14 +222,24 @@ func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, error) { allowedips = append(allowedips, addr6) } } + + primaryAddr := extPeer.Address + if primaryAddr == "" { + primaryAddr = extPeer.Address6 + } + peer = wgtypes.PeerConfig{ PublicKey: pubkey, ReplaceAllowedIPs: true, AllowedIPs: allowedips, } peers = append(peers, peer) + idsAndAddr = append(idsAndAddr, models.IDandAddr{ + ID: peer.PublicKey.String(), + Address: primaryAddr, + }) } - return peers, nil + return peers, idsAndAddr, nil } @@ -282,7 +305,7 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet { // handle ingress gateway peers if peer.IsIngressGateway == "yes" { - extPeers, err := getExtPeers(peer) + extPeers, _, err := getExtPeers(peer) if err != nil { logger.Log(2, "could not retrieve ext peers for ", peer.Name, err.Error()) } @@ -334,7 +357,7 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet { allowedips = append(allowedips, extAllowedIPs...) } if relayedNode.IsIngressGateway == "yes" { - extPeers, err := getExtPeers(relayedNode) + extPeers, _, err := getExtPeers(relayedNode) if err == nil { for _, extPeer := range extPeers { allowedips = append(allowedips, extPeer.AllowedIPs...) @@ -487,7 +510,7 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string) } //if ingress add extclients if node.IsIngressGateway == "yes" { - extPeers, err := getExtPeers(node) + extPeers, _, err := getExtPeers(node) if err == nil { peers = append(peers, extPeers...) } else { diff --git a/logic/pro/license.go b/logic/pro/license.go new file mode 100644 index 00000000..2ca96d50 --- /dev/null +++ b/logic/pro/license.go @@ -0,0 +1,66 @@ +package pro + +import ( + "crypto/rand" + "encoding/json" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/netclient/ncutils" + "golang.org/x/crypto/nacl/box" +) + +const ( + db_license_key = "netmaker-id-key-pair" +) + +type apiServerConf struct { + PrivateKey []byte `json:"private_key" binding:"required"` + PublicKey []byte `json:"public_key" binding:"required"` +} + +// FetchApiServerKeys - fetches netmaker license keys for identification +// as well as secure communication with API +// if none present, it generates a new pair +func FetchApiServerKeys() (pub *[32]byte, priv *[32]byte, err error) { + var returnData = apiServerConf{} + currentData, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, db_license_key) + if err != nil && !database.IsEmptyRecord(err) { + return nil, nil, err + } else if database.IsEmptyRecord(err) { // need to generate a new identifier pair + pub, priv, err = box.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + pubBytes, err := ncutils.ConvertKeyToBytes(pub) + if err != nil { + return nil, nil, err + } + privBytes, err := ncutils.ConvertKeyToBytes(priv) + if err != nil { + return nil, nil, err + } + returnData.PrivateKey = privBytes + returnData.PublicKey = pubBytes + record, err := json.Marshal(&returnData) + if err != nil { + return nil, nil, err + } + if err = database.Insert(db_license_key, string(record), database.SERVERCONF_TABLE_NAME); err != nil { + return nil, nil, err + } + } else { + if err = json.Unmarshal([]byte(currentData), &returnData); err != nil { + return nil, nil, err + } + priv, err = ncutils.ConvertBytesToKey(returnData.PrivateKey) + if err != nil { + return nil, nil, err + } + pub, err = ncutils.ConvertBytesToKey(returnData.PublicKey) + if err != nil { + return nil, nil, err + } + } + + return pub, priv, nil +} diff --git a/logic/pro/metrics/metrics.go b/logic/pro/metrics/metrics.go new file mode 100644 index 00000000..c660b325 --- /dev/null +++ b/logic/pro/metrics/metrics.go @@ -0,0 +1,121 @@ +package metrics + +import ( + "github.com/go-ping/ping" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" + "golang.zx2c4.com/wireguard/wgctrl" +) + +// Collect - collects metrics +func Collect(iface string, peerMap models.PeerMap) (*models.Metrics, error) { + var metrics models.Metrics + metrics.Connectivity = make(map[string]models.Metric) + var wgclient, err = wgctrl.New() + if err != nil { + fillUnconnectedData(&metrics, peerMap) + return &metrics, err + } + defer wgclient.Close() + device, err := wgclient.Device(iface) + if err != nil { + fillUnconnectedData(&metrics, peerMap) + return &metrics, err + } + // TODO handle freebsd?? + for i := range device.Peers { + currPeer := device.Peers[i] + id := peerMap[currPeer.PublicKey.String()].ID + address := peerMap[currPeer.PublicKey.String()].Address + if id == "" || address == "" { + logger.Log(0, "attempted to parse metrics for invalid peer from server", id, address) + continue + } + var newMetric = models.Metric{ + NodeName: peerMap[currPeer.PublicKey.String()].Name, + IsServer: peerMap[currPeer.PublicKey.String()].IsServer, + } + logger.Log(2, "collecting metrics for peer", address) + newMetric.TotalReceived = currPeer.ReceiveBytes + newMetric.TotalSent = currPeer.TransmitBytes + + // get latency + pinger, err := ping.NewPinger(address) + if err != nil { + logger.Log(0, "could not initiliaze ping for metrics on peer address", address, err.Error()) + newMetric.Connected = false + newMetric.Latency = 999 + } else { + pinger.Count = 1 + err = pinger.Run() + if err != nil { + logger.Log(0, "failed ping for metrics on peer address", address, err.Error()) + newMetric.Connected = false + newMetric.Latency = 999 + } else { + pingStats := pinger.Statistics() + newMetric.Uptime = 1 + newMetric.Connected = true + newMetric.Latency = pingStats.AvgRtt.Milliseconds() + } + } + newMetric.TotalTime = 1 + metrics.Connectivity[id] = newMetric + } + + fillUnconnectedData(&metrics, peerMap) + return &metrics, nil +} + +// GetExchangedBytesForNode - get exchanged bytes for current node peers +func GetExchangedBytesForNode(node *models.Node, metrics *models.Metrics) error { + + peers, err := logic.GetPeerUpdate(node) + if err != nil { + logger.Log(0, "Failed to get peers: ", err.Error()) + return err + } + wgclient, err := wgctrl.New() + if err != nil { + return err + } + defer wgclient.Close() + device, err := wgclient.Device(node.Interface) + if err != nil { + return err + } + for _, currPeer := range device.Peers { + id := peers.PeerIDs[currPeer.PublicKey.String()].ID + address := peers.PeerIDs[currPeer.PublicKey.String()].Address + if id == "" || address == "" { + logger.Log(0, "attempted to parse metrics for invalid peer from server", id, address) + continue + } + logger.Log(2, "collecting exchanged bytes info for peer: ", address) + peerMetric := metrics.Connectivity[id] + peerMetric.TotalReceived = currPeer.ReceiveBytes + peerMetric.TotalSent = currPeer.TransmitBytes + metrics.Connectivity[id] = peerMetric + } + return nil +} + +// == used to fill zero value data for non connected peers == +func fillUnconnectedData(metrics *models.Metrics, peerMap models.PeerMap) { + for r := range peerMap { + id := peerMap[r].ID + if !metrics.Connectivity[id].Connected { + newMetric := models.Metric{ + NodeName: peerMap[r].Name, + IsServer: peerMap[r].IsServer, + Uptime: 0, + TotalTime: 1, + Connected: false, + Latency: 999, + PercentUp: 0, + } + metrics.Connectivity[id] = newMetric + } + } +} diff --git a/logic/pro/netcache/netcache.go b/logic/pro/netcache/netcache.go new file mode 100644 index 00000000..901f610c --- /dev/null +++ b/logic/pro/netcache/netcache.go @@ -0,0 +1,57 @@ +package netcache + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gravitl/netmaker/database" +) + +const ( + expirationTime = time.Minute * 5 +) + +// CValue - the cache object for a network +type CValue struct { + Network string `json:"network"` + Value string `json:"value"` + Pass string `json:"pass"` + User string `json:"user"` + Expiration time.Time `json:"expiration"` +} + +var errExpired = fmt.Errorf("expired") + +// Set - sets a value to a key in db +func Set(k string, newValue *CValue) error { + newValue.Expiration = time.Now().Add(expirationTime) + newData, err := json.Marshal(newValue) + if err != nil { + return err + } + + return database.Insert(k, string(newData), database.CACHE_TABLE_NAME) +} + +// Get - gets a value from db, if expired, return err +func Get(k string) (*CValue, error) { + record, err := database.FetchRecord(database.CACHE_TABLE_NAME, k) + if err != nil { + return nil, err + } + var entry CValue + if err := json.Unmarshal([]byte(record), &entry); err != nil { + return nil, err + } + if time.Now().After(entry.Expiration) { + return nil, errExpired + } + + return &entry, nil +} + +// Del - deletes a value from db +func Del(k string) error { + return database.DeleteRecord(database.CACHE_TABLE_NAME, k) +} diff --git a/logic/pro/networks.go b/logic/pro/networks.go new file mode 100644 index 00000000..0b07311c --- /dev/null +++ b/logic/pro/networks.go @@ -0,0 +1,62 @@ +package pro + +import ( + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" +) + +// AddProNetDefaults - adds default values to a network model +func AddProNetDefaults(network *models.Network) { + if network.ProSettings == nil { + newProSettings := promodels.ProNetwork{ + DefaultAccessLevel: NO_ACCESS, + DefaultUserNodeLimit: 0, + DefaultUserClientLimit: 0, + AllowedUsers: []string{}, + AllowedGroups: []string{}, + } + network.ProSettings = &newProSettings + } +} + +// isUserGroupAllowed - checks if a user group is allowed on a network +func isUserGroupAllowed(network *models.Network, groupName string) bool { + if network.ProSettings != nil { + if len(network.ProSettings.AllowedGroups) > 0 { + for i := range network.ProSettings.AllowedGroups { + currentGroup := network.ProSettings.AllowedGroups[i] + if currentGroup == DEFAULT_ALLOWED_GROUPS || currentGroup == groupName { + return true + } + } + } + } + return false +} + +func isUserInAllowedUsers(network *models.Network, userName string) bool { + if network.ProSettings != nil { + if len(network.ProSettings.AllowedUsers) > 0 { + for i := range network.ProSettings.AllowedUsers { + currentUser := network.ProSettings.AllowedUsers[i] + if currentUser == DEFAULT_ALLOWED_USERS || currentUser == userName { + return true + } + } + } + } + return false +} + +// IsUserAllowed - checks if given username + groups if a user is allowed on network +func IsUserAllowed(network *models.Network, userName string, groups []string) bool { + isGroupAllowed := false + for _, g := range groups { + if isUserGroupAllowed(network, g) { + isGroupAllowed = true + break + } + } + + return isUserInAllowedUsers(network, userName) || isGroupAllowed +} diff --git a/logic/pro/networks_test.go b/logic/pro/networks_test.go new file mode 100644 index 00000000..68915a3b --- /dev/null +++ b/logic/pro/networks_test.go @@ -0,0 +1,64 @@ +package pro + +import ( + "testing" + + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" + "github.com/stretchr/testify/assert" +) + +func TestNetworkProSettings(t *testing.T) { + t.Run("Uninitialized with pro", func(t *testing.T) { + network := models.Network{ + NetID: "helloworld", + } + assert.Nil(t, network.ProSettings) + }) + t.Run("Initialized with pro", func(t *testing.T) { + network := models.Network{ + NetID: "helloworld", + } + AddProNetDefaults(&network) + assert.NotNil(t, network.ProSettings) + }) + t.Run("Net Zero Defaults set correctly with Pro", func(t *testing.T) { + network := models.Network{ + NetID: "helloworld", + } + AddProNetDefaults(&network) + assert.NotNil(t, network.ProSettings) + assert.Equal(t, NO_ACCESS, network.ProSettings.DefaultAccessLevel) + assert.Equal(t, 0, network.ProSettings.DefaultUserClientLimit) + assert.Equal(t, 0, network.ProSettings.DefaultUserNodeLimit) + }) + t.Run("Net Defaults set correctly with Pro", func(t *testing.T) { + network := models.Network{ + NetID: "helloworld", + ProSettings: &promodels.ProNetwork{ + DefaultAccessLevel: NET_ADMIN, + DefaultUserNodeLimit: 10, + DefaultUserClientLimit: 25, + }, + } + AddProNetDefaults(&network) + assert.NotNil(t, network.ProSettings) + assert.Equal(t, NET_ADMIN, network.ProSettings.DefaultAccessLevel) + assert.Equal(t, 25, network.ProSettings.DefaultUserClientLimit) + assert.Equal(t, 10, network.ProSettings.DefaultUserNodeLimit) + }) + t.Run("Net Defaults set to allow all groups/users", func(t *testing.T) { + network := models.Network{ + NetID: "helloworld", + ProSettings: &promodels.ProNetwork{ + DefaultAccessLevel: NET_ADMIN, + DefaultUserNodeLimit: 10, + DefaultUserClientLimit: 25, + }, + } + AddProNetDefaults(&network) + assert.NotNil(t, network.ProSettings) + assert.Nil(t, network.ProSettings.AllowedGroups) + assert.Nil(t, network.ProSettings.AllowedUsers) + }) +} diff --git a/logic/pro/networkuser.go b/logic/pro/networkuser.go new file mode 100644 index 00000000..49c33640 --- /dev/null +++ b/logic/pro/networkuser.go @@ -0,0 +1,247 @@ +package pro + +import ( + "encoding/json" + "fmt" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" +) + +// InitializeNetworkUsers - intializes network users for a given network +func InitializeNetworkUsers(network string) error { + newNetUserMap := make(promodels.NetworkUserMap) + netUserData, err := json.Marshal(newNetUserMap) + if err != nil { + return err + } + + return database.Insert(network, string(netUserData), database.NETWORK_USER_TABLE_NAME) +} + +// GetNetworkUsers - gets the network users table +func GetNetworkUsers(network string) (promodels.NetworkUserMap, error) { + currentUsers, err := database.FetchRecord(database.NETWORK_USER_TABLE_NAME, network) + if err != nil { + return nil, err + } + var userMap promodels.NetworkUserMap + if err = json.Unmarshal([]byte(currentUsers), &userMap); err != nil { + return nil, err + } + return userMap, nil +} + +// CreateNetworkUser - adds a network user to db +func CreateNetworkUser(network *models.Network, user *promodels.NetworkUser) error { + + if DoesNetworkUserExist(network.NetID, user.ID) { + return nil + } + + currentUsers, err := GetNetworkUsers(network.NetID) + if err != nil { + return err + } + + currentUsers.Add(user) + data, err := json.Marshal(currentUsers) + if err != nil { + return err + } + + return database.Insert(network.NetID, string(data), database.NETWORK_USER_TABLE_NAME) +} + +// DeleteNetworkUser - deletes a network user and removes from all networks +func DeleteNetworkUser(network, userid string) error { + currentUsers, err := GetNetworkUsers(network) + if err != nil { + return err + } + + currentUsers.Delete(promodels.NetworkUserID(userid)) + data, err := json.Marshal(currentUsers) + if err != nil { + return err + } + + return database.Insert(network, string(data), database.NETWORK_USER_TABLE_NAME) +} + +// DissociateNetworkUserNode - removes a node from a given user's node list +func DissociateNetworkUserNode(userid, networkid, nodeid string) error { + nuser, err := GetNetworkUser(networkid, promodels.NetworkUserID(userid)) + if err != nil { + return err + } + for i, n := range nuser.Nodes { + if n == nodeid { + nuser.Nodes = removeStringIndex(nuser.Nodes, i) + break + } + } + return UpdateNetworkUser(networkid, nuser) +} + +// DissociateNetworkUserClient - removes a client from a given user's client list +func DissociateNetworkUserClient(userid, networkid, clientid string) error { + nuser, err := GetNetworkUser(networkid, promodels.NetworkUserID(userid)) + if err != nil { + return err + } + for i, n := range nuser.Clients { + if n == clientid { + nuser.Clients = removeStringIndex(nuser.Clients, i) + break + } + } + return UpdateNetworkUser(networkid, nuser) +} + +// AssociateNetworkUserClient - removes a client from a given user's client list +func AssociateNetworkUserClient(userid, networkid, clientid string) error { + nuser, err := GetNetworkUser(networkid, promodels.NetworkUserID(userid)) + if err != nil { + return err + } + var found bool + for _, n := range nuser.Clients { + if n == clientid { + found = true + break + } + } + if found { + return nil + } else { + nuser.Clients = append(nuser.Clients, clientid) + } + + return UpdateNetworkUser(networkid, nuser) +} + +func removeStringIndex(s []string, index int) []string { + ret := make([]string, 0) + ret = append(ret, s[:index]...) + return append(ret, s[index+1:]...) +} + +// GetNetworkUser - fetches a network user from a given network +func GetNetworkUser(network string, userID promodels.NetworkUserID) (*promodels.NetworkUser, error) { + currentUsers, err := GetNetworkUsers(network) + if err != nil { + return nil, err + } + if currentUsers[userID].ID == "" { + return nil, fmt.Errorf("user %s does not exist", userID) + } + currentNetUser := currentUsers[userID] + return ¤tNetUser, nil +} + +// DoesNetworkUserExist - check if networkuser exists +func DoesNetworkUserExist(network string, userID promodels.NetworkUserID) bool { + _, err := GetNetworkUser(network, userID) + return err == nil +} + +// UpdateNetworkUser - gets a network user from given network +func UpdateNetworkUser(network string, newUser *promodels.NetworkUser) error { + currentUsers, err := GetNetworkUsers(network) + if err != nil { + return err + } + + currentUsers[newUser.ID] = *newUser + newUsersData, err := json.Marshal(¤tUsers) + if err != nil { + return err + } + + return database.Insert(network, string(newUsersData), database.NETWORK_USER_TABLE_NAME) +} + +// RemoveAllNetworkUsers - removes all network users from given network +func RemoveAllNetworkUsers(network string) error { + return database.DeleteRecord(database.NETWORK_USER_TABLE_NAME, network) +} + +// IsUserNodeAllowed - given a list of nodes, determine if the user's node is allowed based on ID +// Checks if node is in given nodes list as well as being in user's list +func IsUserNodeAllowed(nodes []models.Node, network, userID, nodeID string) bool { + + netUser, err := GetNetworkUser(network, promodels.NetworkUserID(userID)) + if err != nil { + return false + } + + for i := range nodes { + if nodes[i].ID == nodeID { + for j := range netUser.Nodes { + if netUser.Nodes[j] == nodeID { + return true + } + } + } + } + return false +} + +// IsUserClientAllowed - given a list of clients, determine if the user's client is allowed based on ID +// Checks if client is in given ext client list as well as being in user's list +func IsUserClientAllowed(clients []models.ExtClient, network, userID, clientID string) bool { + + netUser, err := GetNetworkUser(network, promodels.NetworkUserID(userID)) + if err != nil { + return false + } + + for i := range clients { + if clients[i].ClientID == clientID { + for j := range netUser.Clients { + if netUser.Clients[j] == clientID { + return true + } + } + } + } + return false +} + +// IsUserNetAdmin - checks if a user is a net admin or not +func IsUserNetAdmin(network, userID string) bool { + var isAdmin bool + user, err := GetNetworkUser(network, promodels.NetworkUserID(userID)) + if err != nil { + return isAdmin + } + return user.AccessLevel == NET_ADMIN +} + +// MakeNetAdmin - makes a given user a network admin on given network +func MakeNetAdmin(network, userID string) (ok bool) { + user, err := GetNetworkUser(network, promodels.NetworkUserID(userID)) + if err != nil { + return ok + } + user.AccessLevel = NET_ADMIN + if err = UpdateNetworkUser(network, user); err != nil { + return ok + } + return true +} + +// AssignAccessLvl - gives a user a specified access level +func AssignAccessLvl(network, userID string, accesslvl int) (ok bool) { + user, err := GetNetworkUser(network, promodels.NetworkUserID(userID)) + if err != nil { + return ok + } + user.AccessLevel = accesslvl + if err = UpdateNetworkUser(network, user); err != nil { + return ok + } + return true +} diff --git a/logic/pro/networkuser_test.go b/logic/pro/networkuser_test.go new file mode 100644 index 00000000..d3955aa1 --- /dev/null +++ b/logic/pro/networkuser_test.go @@ -0,0 +1,98 @@ +package pro + +import ( + "testing" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" + "github.com/stretchr/testify/assert" +) + +func TestNetworkUserLogic(t *testing.T) { + database.InitializeDatabase() + networkUser := promodels.NetworkUser{ + ID: "helloworld", + } + network := models.Network{ + NetID: "skynet", + AddressRange: "192.168.0.0/24", + } + nodes := []models.Node{ + models.Node{ID: "coolnode"}, + } + + clients := []models.ExtClient{ + models.ExtClient{ + ClientID: "coolclient", + }, + } + AddProNetDefaults(&network) + t.Run("Net Users initialized successfully", func(t *testing.T) { + err := InitializeNetworkUsers(network.NetID) + assert.Nil(t, err) + }) + + t.Run("Error when no network users", func(t *testing.T) { + user, err := GetNetworkUser(network.NetID, networkUser.ID) + assert.Nil(t, user) + assert.NotNil(t, err) + }) + + t.Run("Successful net user create", func(t *testing.T) { + DeleteNetworkUser(network.NetID, string(networkUser.ID)) + err := CreateNetworkUser(&network, &networkUser) + assert.Nil(t, err) + user, err := GetNetworkUser(network.NetID, networkUser.ID) + assert.NotNil(t, user) + assert.Nil(t, err) + assert.Equal(t, 0, user.AccessLevel) + assert.Equal(t, 0, user.ClientLimit) + }) + + t.Run("Successful net user update", func(t *testing.T) { + networkUser.AccessLevel = 0 + networkUser.ClientLimit = 1 + err := UpdateNetworkUser(network.NetID, &networkUser) + assert.Nil(t, err) + user, err := GetNetworkUser(network.NetID, networkUser.ID) + assert.NotNil(t, user) + assert.Nil(t, err) + assert.Equal(t, 0, user.AccessLevel) + assert.Equal(t, 1, user.ClientLimit) + }) + + t.Run("Successful net user node isallowed", func(t *testing.T) { + networkUser.Nodes = append(networkUser.Nodes, "coolnode") + err := UpdateNetworkUser(network.NetID, &networkUser) + assert.Nil(t, err) + isUserNodeAllowed := IsUserNodeAllowed(nodes[:], network.NetID, string(networkUser.ID), "coolnode") + assert.True(t, isUserNodeAllowed) + }) + + t.Run("Successful net user node not allowed", func(t *testing.T) { + isUserNodeAllowed := IsUserNodeAllowed(nodes[:], network.NetID, string(networkUser.ID), "notanode") + assert.False(t, isUserNodeAllowed) + }) + + t.Run("Successful net user client isallowed", func(t *testing.T) { + networkUser.Clients = append(networkUser.Clients, "coolclient") + err := UpdateNetworkUser(network.NetID, &networkUser) + assert.Nil(t, err) + isUserClientAllowed := IsUserClientAllowed(clients[:], network.NetID, string(networkUser.ID), "coolclient") + assert.True(t, isUserClientAllowed) + }) + + t.Run("Successful net user client not allowed", func(t *testing.T) { + isUserClientAllowed := IsUserClientAllowed(clients[:], network.NetID, string(networkUser.ID), "notaclient") + assert.False(t, isUserClientAllowed) + }) + + t.Run("Successful net user delete", func(t *testing.T) { + err := DeleteNetworkUser(network.NetID, string(networkUser.ID)) + assert.Nil(t, err) + user, err := GetNetworkUser(network.NetID, networkUser.ID) + assert.Nil(t, user) + assert.NotNil(t, err) + }) +} diff --git a/logic/pro/proacls/nodes.go b/logic/pro/proacls/nodes.go new file mode 100644 index 00000000..d55035e7 --- /dev/null +++ b/logic/pro/proacls/nodes.go @@ -0,0 +1,35 @@ +package proacls + +import ( + "github.com/gravitl/netmaker/logic/acls" + "github.com/gravitl/netmaker/logic/acls/nodeacls" + "github.com/gravitl/netmaker/models" +) + +// AdjustNodeAcls - adjusts ACLs based on a node's default value +func AdjustNodeAcls(node *models.Node, networkNodes []models.Node) error { + networkID := nodeacls.NetworkID(node.Network) + nodeID := nodeacls.NodeID(node.ID) + currentACLs, err := nodeacls.FetchAllACLs(networkID) + if err != nil { + return err + } + + for i := range networkNodes { + currentNodeID := nodeacls.NodeID(networkNodes[i].ID) + if currentNodeID == nodeID { + continue + } + // 2 cases + // both allow - allow + // either 1 denies - deny + if node.DoesACLAllow() { + currentACLs.ChangeAccess(acls.AclID(nodeID), acls.AclID(currentNodeID), acls.Allowed) + } else if node.DoesACLDeny() { + currentACLs.ChangeAccess(acls.AclID(nodeID), acls.AclID(currentNodeID), acls.NotAllowed) + } + } + + _, err = currentACLs.Save(acls.ContainerID(node.Network)) + return err +} diff --git a/logic/pro/types.go b/logic/pro/types.go new file mode 100644 index 00000000..d2063116 --- /dev/null +++ b/logic/pro/types.go @@ -0,0 +1,20 @@ +package pro + +const ( + // == NET ACCESS END == indicates access for system admin (control of netmaker) + // NET_ADMIN - indicates access for network admin (control of network) + NET_ADMIN = 0 + // NODE_ACCESS - indicates access for + NODE_ACCESS = 1 + // CLIENT_ACCESS - indicates access for network user (limited to nodes + ext clients) + CLIENT_ACCESS = 2 + // NO_ACCESS - indicates user has no access to network + NO_ACCESS = 3 + // == NET ACCESS END == + // DEFAULT_ALLOWED_GROUPS - default user group for all networks + DEFAULT_ALLOWED_GROUPS = "*" + // DEFAULT_ALLOWED_USERS - default allowed users for a network + DEFAULT_ALLOWED_USERS = "*" + // DB_GROUPS_KEY - represents db groups + DB_GROUPS_KEY = "netmaker-groups" +) diff --git a/logic/pro/usergroups.go b/logic/pro/usergroups.go new file mode 100644 index 00000000..e7132b3b --- /dev/null +++ b/logic/pro/usergroups.go @@ -0,0 +1,80 @@ +package pro + +import ( + "encoding/json" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models/promodels" +) + +// InitializeGroups - initialize groups data structure if not present in the DB +func InitializeGroups() error { + if !DoesUserGroupExist(DEFAULT_ALLOWED_GROUPS) { + return InsertUserGroup(DEFAULT_ALLOWED_GROUPS) + } + return nil +} + +// InsertUserGroup - inserts a group into the +func InsertUserGroup(groupName promodels.UserGroupName) error { + currentGroups, err := GetUserGroups() + if err != nil { + return err + } + currentGroups[groupName] = promodels.Void{} + newData, err := json.Marshal(¤tGroups) + if err != nil { + return err + } + return database.Insert(DB_GROUPS_KEY, string(newData), database.USER_GROUPS_TABLE_NAME) +} + +// DeleteUserGroup - deletes a group from database +func DeleteUserGroup(groupName promodels.UserGroupName) error { + var newGroups promodels.UserGroups + currentGroupRecords, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, DB_GROUPS_KEY) + if err != nil && !database.IsEmptyRecord(err) { + return err + } + if err = json.Unmarshal([]byte(currentGroupRecords), &newGroups); err != nil { + return err + } + delete(newGroups, groupName) + newData, err := json.Marshal(&newGroups) + if err != nil { + return err + } + return database.Insert(DB_GROUPS_KEY, string(newData), database.USER_GROUPS_TABLE_NAME) +} + +// GetUserGroups - get groups of users +func GetUserGroups() (promodels.UserGroups, error) { + var returnGroups promodels.UserGroups + groupsRecord, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, DB_GROUPS_KEY) + if err != nil { + if database.IsEmptyRecord(err) { + return make(promodels.UserGroups, 1), nil + } + return returnGroups, err + } + + if err = json.Unmarshal([]byte(groupsRecord), &returnGroups); err != nil { + return returnGroups, err + } + + return returnGroups, nil +} + +// DoesUserGroupExist - checks if a user group exists +func DoesUserGroupExist(group promodels.UserGroupName) bool { + currentGroups, err := GetUserGroups() + if err != nil { + return true + } + for k := range currentGroups { + if k == group { + return true + } + } + return false +} diff --git a/logic/pro/usergroups_test.go b/logic/pro/usergroups_test.go new file mode 100644 index 00000000..cd472e25 --- /dev/null +++ b/logic/pro/usergroups_test.go @@ -0,0 +1,43 @@ +package pro + +import ( + "testing" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models/promodels" + "github.com/stretchr/testify/assert" +) + +func TestUserGroupLogic(t *testing.T) { + database.InitializeDatabase() + + t.Run("User Groups initialized successfully", func(t *testing.T) { + err := InitializeGroups() + assert.Nil(t, err) + }) + + t.Run("Check for default group", func(t *testing.T) { + groups, err := GetUserGroups() + assert.Nil(t, err) + var hasdefault bool + for k := range groups { + if string(k) == DEFAULT_ALLOWED_GROUPS { + hasdefault = true + } + } + assert.True(t, hasdefault) + }) + + t.Run("User Groups created successfully", func(t *testing.T) { + err := InsertUserGroup(promodels.UserGroupName("group1")) + assert.Nil(t, err) + err = InsertUserGroup(promodels.UserGroupName("group2")) + assert.Nil(t, err) + }) + + t.Run("User Groups deleted successfully", func(t *testing.T) { + err := DeleteUserGroup(promodels.UserGroupName("group1")) + assert.Nil(t, err) + assert.False(t, DoesUserGroupExist(promodels.UserGroupName("group1"))) + }) +} diff --git a/logic/users.go b/logic/users.go index 8dd7e2a1..f920905f 100644 --- a/logic/users.go +++ b/logic/users.go @@ -4,7 +4,10 @@ import ( "encoding/json" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" ) // GetUser - gets a user @@ -20,3 +23,50 @@ func GetUser(username string) (models.User, error) { } return user, err } + +// GetGroupUsers - gets users in a group +func GetGroupUsers(group string) ([]models.ReturnUser, error) { + var returnUsers []models.ReturnUser + users, err := GetUsers() + if err != nil { + return returnUsers, err + } + for _, user := range users { + if StringSliceContains(user.Groups, group) { + users = append(users, user) + } + } + return users, err +} + +// == PRO == + +// InitializeNetUsers - intializes network users for all users/networks +func InitializeNetUsers(network *models.Network) error { + // == add all current users to network as network users == + currentUsers, err := GetUsers() + if err != nil { + return err + } + + for i := range currentUsers { // add all users to given network + newUser := promodels.NetworkUser{ + ID: promodels.NetworkUserID(currentUsers[i].UserName), + Clients: []string{}, + Nodes: []string{}, + AccessLevel: pro.NO_ACCESS, + ClientLimit: 0, + NodeLimit: 0, + } + if pro.IsUserAllowed(network, currentUsers[i].UserName, currentUsers[i].Groups) { + newUser.AccessLevel = network.ProSettings.DefaultAccessLevel + newUser.ClientLimit = network.ProSettings.DefaultUserClientLimit + newUser.NodeLimit = network.ProSettings.DefaultUserNodeLimit + } + + if err = pro.CreateNetworkUser(network, &newUser); err != nil { + logger.Log(0, "failed to add network user settings to user", string(newUser.ID), "on network", network.NetID) + } + } + return nil +} diff --git a/logic/util.go b/logic/util.go index c22ce818..6fe25af8 100644 --- a/logic/util.go +++ b/logic/util.go @@ -203,3 +203,18 @@ func getNetworkProtocols(cidrs []string) (bool, bool) { } return ipv4, ipv6 } + +// StringDifference - returns the elements in `a` that aren't in `b`. +func StringDifference(a, b []string) []string { + mb := make(map[string]struct{}, len(b)) + for _, x := range b { + mb[x] = struct{}{} + } + var diff []string + for _, x := range a { + if _, found := mb[x]; !found { + diff = append(diff, x) + } + } + return diff +} diff --git a/main.go b/main.go index e0fea725..5c50e0b0 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ +// -build ee package main import ( @@ -19,9 +20,11 @@ import ( "github.com/gravitl/netmaker/config" controller "github.com/gravitl/netmaker/controllers" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/mq" "github.com/gravitl/netmaker/netclient/ncutils" @@ -36,11 +39,11 @@ var version = "dev" func main() { absoluteConfigPath := flag.String("c", "", "absolute path to configuration file") flag.Parse() - setupConfig(*absoluteConfigPath) servercfg.SetVersion(version) fmt.Println(models.RetrieveLogo()) // print the logo - initialize() // initial db and acls; gen cert if required + // fmt.Println(models.ProLogo()) + initialize() // initial db and acls; gen cert if required setGarbageCollection() setVerbosity() defer database.CloseDB() @@ -73,14 +76,34 @@ func initialize() { // Client Mode Prereq Check logger.FatalLog("Error connecting to database") } logger.Log(0, "database successfully connected") - logic.SetJWTSecret() - if err = logic.AddServerIDIfNotPresent(); err != nil { + if err = ee.AddServerIDIfNotPresent(); err != nil { logger.Log(1, "failed to save server ID") } + + logic.SetJWTSecret() + + if err = pro.InitializeGroups(); err != nil { + logger.Log(0, "could not initialize default user group, \"*\"") + } + err = logic.TimerCheckpoint() if err != nil { logger.Log(1, "Timer error occurred: ", err.Error()) } + + if ee.IsEnterprise() { + // == License Handling == + ee.ValidateLicense() + if ee.Limits.FreeTier { + logger.Log(0, "proceeding with Free Tier license") + } else { + logger.Log(0, "proceeding with Paid Tier license") + } + // == End License Handling == + + ee.AddLicenseHooks() + } + var authProvider = auth.InitializeAuthProvider() if authProvider != "" { logger.Log(0, "OAuth provider,", authProvider+",", "initialized") diff --git a/main_ee.go b/main_ee.go new file mode 100644 index 00000000..ba40a39a --- /dev/null +++ b/main_ee.go @@ -0,0 +1,30 @@ +//go:build ee +// +build ee + +package main + +import ( + "github.com/gravitl/netmaker/ee" + "github.com/gravitl/netmaker/models" +) + +func init() { + ee.SetIsEnterprise() + models.SetLogo(retrieveEELogo()) +} + +func retrieveEELogo() string { + return ` + __ __ ______ ______ __ __ ______ __ __ ______ ______ +/\ "-.\ \ /\ ___\ /\__ _\ /\ "-./ \ /\ __ \ /\ \/ / /\ ___\ /\ == \ +\ \ \-. \ \ \ __\ \/_/\ \/ \ \ \-./\ \ \ \ __ \ \ \ _"-. \ \ __\ \ \ __< + \ \_\\"\_\ \ \_____\ \ \_\ \ \_\ \ \_\ \ \_\ \_\ \ \_\ \_\ \ \_____\ \ \_\ \_\ + \/_/ \/_/ \/_____/ \/_/ \/_/ \/_/ \/_/\/_/ \/_/\/_/ \/_____/ \/_/ /_/ + + ___ ___ ____ + ____ ____ ____ / _ \ / _ \ / __ \ ____ ____ ____ + /___/ /___/ /___/ / ___/ / , _// /_/ / /___/ /___/ /___/ + /___/ /___/ /___/ /_/ /_/|_| \____/ /___/ /___/ /___/ + +` +} diff --git a/models/extclient.go b/models/extclient.go index 84593f5f..c984a538 100644 --- a/models/extclient.go +++ b/models/extclient.go @@ -13,4 +13,5 @@ type ExtClient struct { IngressGatewayEndpoint string `json:"ingressgatewayendpoint" bson:"ingressgatewayendpoint"` LastModified int64 `json:"lastmodified" bson:"lastmodified"` Enabled bool `json:"enabled" bson:"enabled"` + OwnerID string `json:"ownerid" bson:"ownerid"` } diff --git a/models/metrics.go b/models/metrics.go new file mode 100644 index 00000000..cb8e2b74 --- /dev/null +++ b/models/metrics.go @@ -0,0 +1,45 @@ +package models + +import "time" + +// Metrics - metrics struct +type Metrics struct { + Network string `json:"network" bson:"network" yaml:"network"` + NodeID string `json:"node_id" bson:"node_id" yaml:"node_id"` + NodeName string `json:"node_name" bson:"node_name" yaml:"node_name"` + IsServer string `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"` + Connectivity map[string]Metric `json:"connectivity" bson:"connectivity" yaml:"connectivity"` +} + +// Metric - holds a metric for data between nodes +type Metric struct { + NodeName string `json:"node_name" bson:"node_name" yaml:"node_name"` + IsServer string `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"` + Uptime int64 `json:"uptime" bson:"uptime" yaml:"uptime"` + TotalTime int64 `json:"totaltime" bson:"totaltime" yaml:"totaltime"` + Latency int64 `json:"latency" bson:"latency" yaml:"latency"` + TotalReceived int64 `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"` + TotalSent int64 `json:"totalsent" bson:"totalsent" yaml:"totalsent"` + ActualUptime time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"` + PercentUp float64 `json:"percentup" bson:"percentup" yaml:"percentup"` + Connected bool `json:"connected" bson:"connected" yaml:"connected"` +} + +// IDandAddr - struct to hold ID and primary Address +type IDandAddr struct { + ID string `json:"id" bson:"id" yaml:"id"` + Address string `json:"address" bson:"address" yaml:"address"` + Name string `json:"name" bson:"name" yaml:"name"` + IsServer string `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"` +} + +// PeerMap - peer map for ids and addresses in metrics +type PeerMap map[string]IDandAddr + +// MetricsMap - map for holding multiple metrics in memory +type MetricsMap map[string]Metrics + +// NetworkMetrics - metrics model for all nodes in a network +type NetworkMetrics struct { + Nodes MetricsMap `json:"nodes" bson:"nodes" yaml:"nodes"` +} diff --git a/models/mqtt.go b/models/mqtt.go index fbb4ee7b..4b52e956 100644 --- a/models/mqtt.go +++ b/models/mqtt.go @@ -9,6 +9,7 @@ type PeerUpdate struct { ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"` Peers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"` DNS string `json:"dns" bson:"dns" yaml:"dns"` + PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"` } // KeyUpdate - key update struct diff --git a/models/names.go b/models/names.go index faa6f8cd..f5c1301f 100644 --- a/models/names.go +++ b/models/names.go @@ -231,6 +231,8 @@ var SMALL_NAMES = []string{ "cold", } +var logoString = retrieveLogo() + // GenerateNodeName - generates a random node name func GenerateNodeName() string { rand.Seed(time.Now().UnixNano()) @@ -239,6 +241,15 @@ func GenerateNodeName() string { // RetrieveLogo - retrieves the ascii art logo for Netmaker func RetrieveLogo() string { + return logoString +} + +// SetLogo - sets the logo ascii art +func SetLogo(logo string) { + logoString = logo +} + +func retrieveLogo() string { return ` __ __ ______ ______ __ __ ______ __ __ ______ ______ /\ "-.\ \ /\ ___\ /\__ _\ /\ "-./ \ /\ __ \ /\ \/ / /\ ___\ /\ == \ diff --git a/models/network.go b/models/network.go index 057a1a56..3442e84d 100644 --- a/models/network.go +++ b/models/network.go @@ -2,33 +2,36 @@ package models import ( "time" + + "github.com/gravitl/netmaker/models/promodels" ) // Network Struct - contains info for a given unique network //At some point, need to replace all instances of Name with something else like Identifier type Network struct { - AddressRange string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"` - AddressRange6 string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"` - NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"` - NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified"` - NetworkLastModified int64 `json:"networklastmodified" bson:"networklastmodified"` - DefaultInterface string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=15"` - DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"` - NodeLimit int32 `json:"nodelimit" bson:"nodelimit"` - DefaultPostUp string `json:"defaultpostup" bson:"defaultpostup"` - DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"` - DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"` - AccessKeys []AccessKey `json:"accesskeys" bson:"accesskeys"` - AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"` - IsLocal string `json:"islocal" bson:"islocal" validate:"checkyesorno"` - IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"` - IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"` - IsPointToSite string `json:"ispointtosite" bson:"ispointtosite" validate:"checkyesorno"` - LocalRange string `json:"localrange" bson:"localrange" validate:"omitempty,cidr"` - DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"` - DefaultExtClientDNS string `json:"defaultextclientdns" bson:"defaultextclientdns"` - DefaultMTU int32 `json:"defaultmtu" bson:"defaultmtu"` - DefaultACL string `json:"defaultacl" bson:"defaultacl" yaml:"defaultacl" validate:"checkyesorno"` + AddressRange string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"` + AddressRange6 string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"` + NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"` + NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified"` + NetworkLastModified int64 `json:"networklastmodified" bson:"networklastmodified"` + DefaultInterface string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=15"` + DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"` + NodeLimit int32 `json:"nodelimit" bson:"nodelimit"` + DefaultPostUp string `json:"defaultpostup" bson:"defaultpostup"` + DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"` + DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"` + AccessKeys []AccessKey `json:"accesskeys" bson:"accesskeys"` + AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"` + IsLocal string `json:"islocal" bson:"islocal" validate:"checkyesorno"` + IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"` + IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"` + IsPointToSite string `json:"ispointtosite" bson:"ispointtosite" validate:"checkyesorno"` + LocalRange string `json:"localrange" bson:"localrange" validate:"omitempty,cidr"` + DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"` + DefaultExtClientDNS string `json:"defaultextclientdns" bson:"defaultextclientdns"` + DefaultMTU int32 `json:"defaultmtu" bson:"defaultmtu"` + DefaultACL string `json:"defaultacl" bson:"defaultacl" yaml:"defaultacl" validate:"checkyesorno"` + ProSettings *promodels.ProNetwork `json:"prosettings,omitempty" bson:"prosettings,omitempty" yaml:"prosettings,omitempty"` } // SaveData - sensitive fields of a network that should be kept the same diff --git a/models/node.go b/models/node.go index c4a477ca..0aebf0a0 100644 --- a/models/node.go +++ b/models/node.go @@ -101,6 +101,9 @@ type Node struct { FirewallInUse string `json:"firewallinuse" bson:"firewallinuse" yaml:"firewallinuse"` InternetGateway string `json:"internetgateway" bson:"internetgateway" yaml:"internetgateway"` Connected string `json:"connected" bson:"connected" yaml:"connected" validate:"checkyesorno"` + // == 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"` } // NodesArray - used for node sorting @@ -438,6 +441,10 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable if newNode.Connected == "" { newNode.Connected = currentNode.Connected } + if newNode.DefaultACL == "" { + newNode.DefaultACL = currentNode.DefaultACL + } + newNode.TrafficKeys = currentNode.TrafficKeys } @@ -469,3 +476,15 @@ func (node *Node) NameInNodeCharSet() bool { } return true } + +// == PRO == + +// Node.DoesACLAllow - checks if default ACL on node is "yes" +func (node *Node) DoesACLAllow() bool { + return node.DefaultACL == "yes" +} + +// Node.DoesACLDeny - checks if default ACL on node is "no" +func (node *Node) DoesACLDeny() bool { + return node.DefaultACL == "no" +} diff --git a/models/promodels/networkuser.go b/models/promodels/networkuser.go new file mode 100644 index 00000000..a6865335 --- /dev/null +++ b/models/promodels/networkuser.go @@ -0,0 +1,27 @@ +package promodels + +// NetworkUserID - ID field for a network user +type NetworkUserID string + +// NetworkUser - holds fields for a network user +type NetworkUser struct { + AccessLevel int `json:"accesslevel" bson:"accesslevel" yaml:"accesslevel"` + ClientLimit int `json:"clientlimit" bson:"clientlimit" yaml:"clientlimit"` + NodeLimit int `json:"nodelimit" bson:"nodelimit" yaml:"nodelimit"` + ID NetworkUserID `json:"id" bson:"id" yaml:"id"` + Clients []string `json:"clients" bson:"clients" yaml:"clients"` + Nodes []string `json:"nodes" bson:"nodes" yaml:"nodes"` +} + +// NetworkUserMap - map of network users +type NetworkUserMap map[NetworkUserID]NetworkUser + +// NetworkUserMap.Delete - deletes a network user struct from a given map in memory +func (N NetworkUserMap) Delete(ID NetworkUserID) { + delete(N, ID) +} + +// NetworkUserMap.Add - adds a network user struct to given network user map in memory +func (N NetworkUserMap) Add(User *NetworkUser) { + N[User.ID] = *User +} diff --git a/models/promodels/pro.go b/models/promodels/pro.go new file mode 100644 index 00000000..47389aec --- /dev/null +++ b/models/promodels/pro.go @@ -0,0 +1,19 @@ +package promodels + +// ProNetwork - struct for all pro Network related fields +type ProNetwork struct { + DefaultAccessLevel int `json:"defaultaccesslevel" bson:"defaultaccesslevel" yaml:"defaultaccesslevel"` + DefaultUserNodeLimit int `json:"defaultusernodelimit" bson:"defaultusernodelimit" yaml:"defaultusernodelimit"` + DefaultUserClientLimit int `json:"defaultuserclientlimit" bson:"defaultuserclientlimit" yaml:"defaultuserclientlimit"` + AllowedUsers []string `json:"allowedusers" bson:"allowedusers" yaml:"allowedusers"` + AllowedGroups []string `json:"allowedgroups" bson:"allowedgroups" yaml:"allowedgroups"` +} + +// LoginMsg - login message struct for nodes to join via SSO login +// Need to change mac to public key for tighter verification ? +type LoginMsg struct { + Mac string `json:"mac"` + Network string `json:"network"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` +} diff --git a/models/promodels/usergroups.go b/models/promodels/usergroups.go new file mode 100644 index 00000000..e01e6e9c --- /dev/null +++ b/models/promodels/usergroups.go @@ -0,0 +1,9 @@ +package promodels + +type Void struct{} + +// UserGroupName - string representing a group name +type UserGroupName string + +// UserGroups - groups type, holds group names +type UserGroups map[UserGroupName]Void diff --git a/models/structs.go b/models/structs.go index 79f72a93..6b80e4f9 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" @@ -28,6 +29,7 @@ type User struct { Password string `json:"password" bson:"password" validate:"required,min=5"` Networks []string `json:"networks" bson:"networks"` IsAdmin bool `json:"isadmin" bson:"isadmin"` + Groups []string `json:"groups" bson:"groups" yaml:"groups"` } // ReturnUser - return user struct @@ -35,6 +37,7 @@ type ReturnUser struct { UserName string `json:"username" bson:"username"` Networks []string `json:"networks" bson:"networks"` IsAdmin bool `json:"isadmin" bson:"isadmin"` + Groups []string `json:"groups" bson:"groups"` } // UserAuthParams - user auth params struct @@ -48,6 +51,7 @@ type UserClaims struct { IsAdmin bool UserName string Networks []string + Groups []string jwt.RegisteredClaims } @@ -95,10 +99,11 @@ type SuccessResponse struct { // AccessKey - access key struct type AccessKey struct { - Name string `json:"name" bson:"name" validate:"omitempty,max=20"` - Value string `json:"value" bson:"value" validate:"omitempty,alphanum,max=16"` - AccessString string `json:"accessstring" bson:"accessstring"` - Uses int `json:"uses" bson:"uses" validate:"numeric,min=0"` + Name string `json:"name" bson:"name" validate:"omitempty,max=345"` + Value string `json:"value" bson:"value" validate:"omitempty,alphanum,max=16"` + AccessString string `json:"accessstring" bson:"accessstring"` + Uses int `json:"uses" bson:"uses" validate:"numeric,min=0"` + Expiration *time.Time `json:"expiration" bson:"expiration"` } // DisplayKey - what is displayed for key @@ -200,6 +205,7 @@ type NodeGet struct { Node Node `json:"node" bson:"node" yaml:"node"` Peers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"` ServerConfig ServerConfig `json:"serverconfig" bson:"serverconfig" yaml:"serverconfig"` + PeerIDs PeerMap `json:"peerids,omitempty" bson:"peerids,omitempty" yaml:"peerids,omitempty"` } // ServerConfig - struct for dealing with the server information for a netclient diff --git a/mq/handlers.go b/mq/handlers.go index e00646b7..99695eb7 100644 --- a/mq/handlers.go +++ b/mq/handlers.go @@ -2,13 +2,17 @@ package mq import ( "encoding/json" + "fmt" + "time" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/ee" "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" ) // DefaultHandler default message queue handler -- NOT USED @@ -93,6 +97,50 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) { }() } +// UpdateMetrics message Handler -- handles updates from client nodes for metrics +func UpdateMetrics(client mqtt.Client, msg mqtt.Message) { + if ee.IsEnterprise() { + go func() { + id, err := getID(msg.Topic()) + if err != nil { + logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error()) + return + } + currentNode, err := logic.GetNodeByID(id) + if err != nil { + logger.Log(1, "error getting node ", id, err.Error()) + return + } + decrypted, decryptErr := decryptMsg(¤tNode, msg.Payload()) + if decryptErr != nil { + logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error()) + return + } + + var newMetrics models.Metrics + if err := json.Unmarshal(decrypted, &newMetrics); err != nil { + logger.Log(1, "error unmarshaling payload ", err.Error()) + return + } + + updateNodeMetrics(¤tNode, &newMetrics) + + if err = logic.UpdateMetrics(id, &newMetrics); err != nil { + logger.Log(1, "faield to update node metrics", id, currentNode.Name, err.Error()) + return + } + if servercfg.IsMetricsExporter() { + if err := pushMetricsToExporter(newMetrics); err != nil { + logger.Log(2, fmt.Sprintf("failed to push node: [%s] metrics to exporter, err: %v", + currentNode.Name, err)) + } + } + + logger.Log(1, "updated node metrics", id, currentNode.Name) + }() + } +} + // ClientPeerUpdate message handler -- handles updating peers after signal from client nodes func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) { go func() { @@ -146,3 +194,46 @@ func updateNodePeers(currentNode *models.Node) { } } } + +func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) { + oldMetrics, err := logic.GetMetrics(currentNode.ID) + if err != nil { + logger.Log(1, "error finding old metrics for node", currentNode.ID, currentNode.Name) + return + } + + var attachedClients []models.ExtClient + if currentNode.IsIngressGateway == "yes" { + clients, err := logic.GetExtClientsByID(currentNode.ID, currentNode.Network) + if err == nil { + attachedClients = clients + } + } + if len(attachedClients) > 0 { + // associate ext clients with IDs + for i := range attachedClients { + extMetric := newMetrics.Connectivity[attachedClients[i].PublicKey] + delete(newMetrics.Connectivity, attachedClients[i].PublicKey) + if extMetric.Connected { // add ext client metrics + newMetrics.Connectivity[attachedClients[i].ClientID] = extMetric + } + } + } + + // run through metrics for each peer + for k := range newMetrics.Connectivity { + currMetric := newMetrics.Connectivity[k] + oldMetric := oldMetrics.Connectivity[k] + currMetric.TotalTime += oldMetric.TotalTime + currMetric.Uptime += oldMetric.Uptime // get the total uptime for this connection + currMetric.PercentUp = 100.0 * (float64(currMetric.Uptime) / float64(currMetric.TotalTime)) + totalUpMinutes := currMetric.Uptime * 5 + currMetric.ActualUptime = time.Duration(totalUpMinutes) * time.Minute + delete(oldMetrics.Connectivity, k) // remove from old data + newMetrics.Connectivity[k] = currMetric + } + + for k := range oldMetrics.Connectivity { // cleanup any left over data, self healing + delete(newMetrics.Connectivity, k) + } +} diff --git a/mq/mq.go b/mq/mq.go index edb48539..8453cb0f 100644 --- a/mq/mq.go +++ b/mq/mq.go @@ -51,6 +51,10 @@ func SetupMQTT() { client.Disconnect(240) logger.Log(0, "node client subscription failed") } + if token := client.Subscribe("metrics/#", 0, mqtt.MessageHandler(UpdateMetrics)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil { + client.Disconnect(240) + logger.Log(0, "node metrics subscription failed") + } opts.SetOrderMatters(true) opts.SetResumeSubs(true) diff --git a/mq/publishers.go b/mq/publishers.go index 423e1461..abdc5167 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -2,11 +2,13 @@ package mq import ( "encoding/json" + "errors" "fmt" + "time" - "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/logic/pro/metrics" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" "github.com/gravitl/netmaker/serverctl" @@ -105,6 +107,11 @@ func NodeUpdate(node *models.Node) error { // sendPeers - retrieve networks, send peer ports to all peers func sendPeers() { + networks, err := logic.GetNetworks() + if err != nil { + logger.Log(1, "error retrieving networks for keepalive", err.Error()) + } + var force bool peer_force_send++ if peer_force_send == 5 { @@ -121,10 +128,8 @@ func sendPeers() { if err != nil { logger.Log(3, "error occurred on timer,", err.Error()) } - } - networks, err := logic.GetNetworks() - if err != nil && !database.IsEmptyRecord(err) { - logger.Log(1, "error retrieving networks for keepalive", err.Error()) + + collectServerMetrics(networks[:]) } for _, network := range networks { @@ -176,3 +181,64 @@ func ServerStartNotify() error { } return nil } + +// function to collect and store metrics for server nodes +func collectServerMetrics(networks []models.Network) { + if len(networks) > 0 { + for i := range networks { + currentNetworkNodes, err := logic.GetNetworkNodes(networks[i].NetID) + if err != nil { + continue + } + currentServerNodes := logic.GetServerNodes(networks[i].NetID) + if len(currentServerNodes) > 0 { + for i := range currentServerNodes { + if logic.IsLocalServer(¤tServerNodes[i]) { + serverMetrics := logic.CollectServerMetrics(currentServerNodes[i].ID, currentNetworkNodes) + if serverMetrics != nil { + serverMetrics.NodeName = currentServerNodes[i].Name + serverMetrics.NodeID = currentServerNodes[i].ID + serverMetrics.IsServer = "yes" + serverMetrics.Network = currentServerNodes[i].Network + if err = metrics.GetExchangedBytesForNode(¤tServerNodes[i], serverMetrics); err != nil { + logger.Log(1, fmt.Sprintf("failed to update exchanged bytes info for server: %s, err: %v", + currentServerNodes[i].Name, err)) + } + updateNodeMetrics(¤tServerNodes[i], serverMetrics) + if err = logic.UpdateMetrics(currentServerNodes[i].ID, serverMetrics); err != nil { + logger.Log(1, "failed to update metrics for server node", currentServerNodes[i].ID) + } + if servercfg.IsMetricsExporter() { + logger.Log(2, "-------------> SERVER METRICS: ", fmt.Sprintf("%+v", serverMetrics)) + if err := pushMetricsToExporter(*serverMetrics); err != nil { + logger.Log(2, "failed to push server metrics to exporter: ", err.Error()) + } + } + + } + + } + } + } + } + } +} + +func pushMetricsToExporter(metrics models.Metrics) error { + logger.Log(2, "----> Pushing metrics to exporter") + SetupMQTT() + data, err := json.Marshal(metrics) + if err != nil { + return errors.New("failed to marshal metrics: " + err.Error()) + } + if token := mqclient.Publish("metrics_exporter", 0, true, data); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil { + var err error + if token.Error() == nil { + err = errors.New("connection timeout") + } else { + err = token.Error() + } + return err + } + return nil +} diff --git a/netclient/cli_options/flags.go b/netclient/cli_options/flags.go index 50ec8725..2887306d 100644 --- a/netclient/cli_options/flags.go +++ b/netclient/cli_options/flags.go @@ -133,6 +133,20 @@ func GetFlags(hostname string) []cli.Flag { Value: "", Usage: "Access Token for signing up machine with Netmaker server during initial 'add'.", }, + &cli.StringFlag{ + Name: "login-server", + Aliases: []string{"l"}, + EnvVars: []string{"LOGIN_SERVER"}, + Value: "", + Usage: "Login server URL, use it for the Single Sign-on along with the network parameter", + }, + &cli.StringFlag{ + Name: "user", + Aliases: []string{"u"}, + EnvVars: []string{"USER_NAME"}, + Value: "", + Usage: "User name provided upon joins if joining over basic auth is desired.", + }, &cli.StringFlag{ Name: "localrange", EnvVars: []string{"NETCLIENT_LOCALRANGE"}, diff --git a/netclient/command/commands.go b/netclient/command/commands.go index dc1195d5..cd163dc3 100644 --- a/netclient/command/commands.go +++ b/netclient/command/commands.go @@ -3,6 +3,7 @@ package command import ( "crypto/ed25519" "crypto/rand" + "errors" "fmt" "strings" @@ -18,6 +19,25 @@ import ( func Join(cfg *config.ClientConfig, privateKey string) error { var err error //join network + if cfg.SsoServer != "" { + // User wants to get access key from the OIDC server + // Do that before the Joining Network flow by performing the end point auth flow + // if performed successfully an access key is obtained from the server and then we + // proceed with the usual flow 'pretending' that user is feeded us with an access token + logger.Log(1, "Logging into %s via:", cfg.Network, cfg.SsoServer) + err = functions.JoinViaSSo(cfg, privateKey) + if err != nil { + logger.Log(0, "Join via OIDC failed: ", err.Error()) + return err + } + + if cfg.AccessKey == "" { + return errors.New("failed to get access key") + } + logger.Log(1, "Got an access key to ", cfg.Network, " via:", cfg.SsoServer) + } + + logger.Log(1, "Joining network: ", cfg.Network) err = functions.JoinNetwork(cfg, privateKey) if err != nil { if !strings.Contains(err.Error(), "ALREADY_INSTALLED") { diff --git a/netclient/config/config.go b/netclient/config/config.go index 5a4dd6cb..5a8276ff 100644 --- a/netclient/config/config.go +++ b/netclient/config/config.go @@ -32,6 +32,7 @@ type ClientConfig struct { OperatingSystem string `yaml:"operatingsystem"` AccessKey string `yaml:"accesskey"` PublicIPService string `yaml:"publicipservice"` + SsoServer string `yaml:"sso"` } // RegisterRequest - struct for registation with netmaker server @@ -239,6 +240,11 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) { if c.String("apiserver") != "" { cfg.Server.API = c.String("apiserver") } + } else if c.String("login-server") != "" { + cfg.SsoServer = c.String("login-server") + cfg.Network = c.String("network") + cfg.Node.Network = c.String("network") + global_settings.User = c.String("user") } else { cfg.AccessKey = c.String("key") cfg.Network = c.String("network") diff --git a/netclient/functions/join.go b/netclient/functions/join.go index 1b0eca60..fc53e295 100644 --- a/netclient/functions/join.go +++ b/netclient/functions/join.go @@ -8,21 +8,174 @@ import ( "io" "log" "net/http" + "os" + "os/signal" "runtime" + "strings" + "syscall" + "time" + "github.com/gorilla/websocket" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/models/promodels" "github.com/gravitl/netmaker/netclient/auth" "github.com/gravitl/netmaker/netclient/config" "github.com/gravitl/netmaker/netclient/daemon" + "github.com/gravitl/netmaker/netclient/global_settings" "github.com/gravitl/netmaker/netclient/local" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/netclient/wireguard" "golang.org/x/crypto/nacl/box" + "golang.org/x/term" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) +// JoinViaSso - Handles the Single Sign-On flow on the end point VPN client side +// Contacts the server provided by the user (and thus specified in cfg.SsoServer) +// get the URL to authenticate with a provider and shows the user the URL. +// Then waits for user to authenticate with the URL. +// Upon user successful auth flow finished - server should return access token to the requested network +// Otherwise the error message is sent which can be displayed to the user +func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { + + // User must tell us which network he is joining + if cfg.Node.Network == "" { + return errors.New("no network provided") + } + + // Prepare a channel for interrupt + // Channel to listen for interrupt signal to terminate gracefully + interrupt := make(chan os.Signal, 1) + // Notify the interrupt channel for SIGINT + signal.Notify(interrupt, os.Interrupt) + + // Web Socket is used, construct the URL accordingly ... + socketUrl := fmt.Sprintf("wss://%s/api/oauth/node-handler", cfg.SsoServer) + // Dial the netmaker server controller + conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil) + if err != nil { + logger.Log(0, fmt.Sprintf("Error connecting to %s : %s", cfg.Server.API, err.Error())) + return err + } + // Don't forget to close when finished + defer conn.Close() + // Find and set node MacAddress + if cfg.Node.MacAddress == "" { + macs, err := ncutils.GetMacAddr() + if err != nil { + //if macaddress can't be found set to random string + cfg.Node.MacAddress = ncutils.MakeRandomString(18) + } else { + cfg.Node.MacAddress = macs[0] + } + } + + var loginMsg promodels.LoginMsg + loginMsg.Mac = cfg.Node.MacAddress + loginMsg.Network = cfg.Node.Network + if global_settings.User != "" { + fmt.Printf("Continuing with user, %s.\nPlease input password:\n", global_settings.User) + pass, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil || string(pass) == "" { + logger.FatalLog("no password provided, exiting") + } + loginMsg.User = global_settings.User + loginMsg.Password = string(pass) + } + + msgTx, err := json.Marshal(loginMsg) + if err != nil { + logger.Log(0, fmt.Sprintf("failed to marshal message %+v", loginMsg)) + return err + } + err = conn.WriteMessage(websocket.TextMessage, []byte(msgTx)) + if err != nil { + logger.FatalLog("Error during writing to websocket:", err.Error()) + return err + } + + // if user provided, server will handle authentication + if loginMsg.User == "" { + // We are going to get instructions on how to authenticate + // Wait to receive something from server + _, msg, err := conn.ReadMessage() + if err != nil { + log.Println("Error in receive:", err) + return err + } + // Print message from the netmaker controller to the user + fmt.Printf("Please visit:\n %s \n to authenticate", string(msg)) + } + + // Now the user is authenticating and we need to block until received + // An answer from the server. + // Server waits ~5 min - If takes too long timeout will be triggered by the server + done := make(chan struct{}) + // Following code will run in a separate go routine + // it reads a message from the server which either contains 'AccessToken:' string or not + // if not - then it contains an Error to display. + // if yes - then AccessToken is to be used to proceed joining the network + go func() { + defer close(done) + for { + _, msg, err := conn.ReadMessage() + if err != nil { + // Error reading a message from the server + if !strings.Contains(err.Error(), "normal") { + logger.Log(0, "read:", err.Error()) + } + return + } + // Get the access token from the response + if strings.Contains(string(msg), "AccessToken: ") { + // Access was granted + rxToken := strings.TrimPrefix(string(msg), "AccessToken: ") + accesstoken, err := config.ParseAccessToken(rxToken) + if err != nil { + log.Printf("Failed to parse received access token %s,err=%s\n", accesstoken, err.Error()) + return + } + + cfg.Network = accesstoken.ClientConfig.Network + cfg.Node.Network = accesstoken.ClientConfig.Network + cfg.AccessKey = accesstoken.ClientConfig.Key + cfg.Node.LocalRange = accesstoken.ClientConfig.LocalRange + //cfg.Server.Server = accesstoken.ServerConfig.Server + cfg.Server.API = accesstoken.APIConnString + } else { + // Access was not granted. Display a message from the server + logger.Log(0, "Message from server:", string(msg)) + cfg.AccessKey = "" + return + } + } + }() + + for { + select { + case <-done: + logger.Log(1, "finished") + return nil + case <-interrupt: + log.Println("interrupt") + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + logger.Log(0, "write close:", err.Error()) + return err + } + select { + case <-done: + case <-time.After(time.Second): + } + return nil + } + } +} + // JoinNetwork - helps a client join a network func JoinNetwork(cfg *config.ClientConfig, privateKey string) error { if cfg.Node.Network == "" { diff --git a/netclient/functions/mqpublish.go b/netclient/functions/mqpublish.go index 8ee1cf6e..5f1fdae6 100644 --- a/netclient/functions/mqpublish.go +++ b/netclient/functions/mqpublish.go @@ -5,7 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "io" "net" + "net/http" "os" "strconv" "sync" @@ -13,6 +15,7 @@ import ( "github.com/cloverstd/tcping/ping" "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic/pro/metrics" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/auth" "github.com/gravitl/netmaker/netclient/config" @@ -20,13 +23,16 @@ import ( "github.com/gravitl/netmaker/tls" ) +var metricsCache = new(sync.Map) + // Checkin -- go routine that checks for public or local ip changes, publishes changes // // if there are no updates, simply "pings" the server as a checkin func Checkin(ctx context.Context, wg *sync.WaitGroup) { logger.Log(2, "starting checkin goroutine") defer wg.Done() - checkin() + currentRun := 0 + checkin(currentRun) ticker := time.NewTicker(time.Second * 60) defer ticker.Stop() for { @@ -36,12 +42,16 @@ func Checkin(ctx context.Context, wg *sync.WaitGroup) { return //delay should be configuraable -> use cfg.Node.NetworkSettings.DefaultCheckInInterval ?? case <-ticker.C: - checkin() + currentRun++ + checkin(currentRun) + if currentRun >= 5 { + currentRun = 0 + } } } } -func checkin() { +func checkin(currentRun int) { networks, _ := ncutils.GetSystemNetworks() logger.Log(3, "checkin with server(s) for all networks") for _, network := range networks { @@ -104,6 +114,10 @@ func checkin() { } Hello(&nodeCfg) checkCertExpiry(&nodeCfg) + if currentRun >= 5 { + logger.Log(0, "collecting metrics for node", nodeCfg.Node.Name) + publishMetrics(&nodeCfg) + } } } @@ -146,6 +160,78 @@ func Hello(nodeCfg *config.ClientConfig) { } } +// publishMetrics - publishes the metrics of a given nodecfg +func publishMetrics(nodeCfg *config.ClientConfig) { + token, err := Authenticate(nodeCfg) + if err != nil { + logger.Log(1, "failed to authenticate when publishing metrics", err.Error()) + return + } + url := "https://" + nodeCfg.Server.API + "/api/nodes/" + nodeCfg.Network + "/" + nodeCfg.Node.ID + response, err := API("", http.MethodGet, url, token) + if err != nil { + logger.Log(1, "failed to read from server during metrics publish", err.Error()) + return + } + if response.StatusCode != http.StatusOK { + bytes, err := io.ReadAll(response.Body) + if err != nil { + fmt.Println(err) + } + logger.Log(0, fmt.Sprintf("%s %s", string(bytes), err.Error())) + return + } + defer response.Body.Close() + var nodeGET models.NodeGet + if err := json.NewDecoder(response.Body).Decode(&nodeGET); err != nil { + logger.Log(0, "failed to decode node when running metrics update", err.Error()) + return + } + + metrics, err := metrics.Collect(nodeCfg.Node.Interface, nodeGET.PeerIDs) + if err != nil { + logger.Log(0, "failed metric collection for node", nodeCfg.Node.Name, err.Error()) + } + metrics.Network = nodeCfg.Node.Network + metrics.NodeName = nodeCfg.Node.Name + metrics.NodeID = nodeCfg.Node.ID + metrics.IsServer = "no" + data, err := json.Marshal(metrics) + if err != nil { + logger.Log(0, "something went wrong when marshalling metrics data for node", nodeCfg.Node.Name, err.Error()) + } + + if err = publish(nodeCfg, fmt.Sprintf("metrics/%s", nodeCfg.Node.ID), data, 1); err != nil { + logger.Log(0, "error occurred during publishing of metrics on node", nodeCfg.Node.Name, err.Error()) + logger.Log(0, "aggregating metrics locally until broker connection re-established") + val, ok := metricsCache.Load(nodeCfg.Node.ID) + if !ok { + metricsCache.Store(nodeCfg.Node.ID, data) + } else { + var oldMetrics models.Metrics + err = json.Unmarshal(val.([]byte), &oldMetrics) + if err == nil { + for k := range oldMetrics.Connectivity { + currentMetric := metrics.Connectivity[k] + if currentMetric.Latency == 0 { + currentMetric.Latency = oldMetrics.Connectivity[k].Latency + } + currentMetric.Uptime += oldMetrics.Connectivity[k].Uptime + currentMetric.TotalTime += oldMetrics.Connectivity[k].TotalTime + metrics.Connectivity[k] = currentMetric + } + newData, err := json.Marshal(metrics) + if err == nil { + metricsCache.Store(nodeCfg.Node.ID, newData) + } + } + } + } else { + metricsCache.Delete(nodeCfg.Node.ID) + logger.Log(0, "published metrics for node", nodeCfg.Node.Name) + } +} + // node cfg is required in order to fetch the traffic keys of that node for encryption func publish(nodeCfg *config.ClientConfig, dest string, msg []byte, qos byte) error { // setup the keys diff --git a/netclient/global_settings/globalsettings.go b/netclient/global_settings/globalsettings.go index 192c884f..086c952b 100644 --- a/netclient/global_settings/globalsettings.go +++ b/netclient/global_settings/globalsettings.go @@ -4,3 +4,6 @@ package global_settings // PublicIPServices - the list of user-specified IP services to use to obtain the node's public IP var PublicIPServices map[string]string = make(map[string]string) + +// User - holds a user string for joins when using basic auth +var User string diff --git a/scripts/nm-quick.sh b/scripts/nm-quick.sh index d1055a8b..50bfeca7 100755 --- a/scripts/nm-quick.sh +++ b/scripts/nm-quick.sh @@ -187,6 +187,8 @@ EOF echo "visit https://dashboard.$NETMAKER_BASE_DOMAIN to log in" +echo "visit https://grafana.$NETMAKER_BASE_DOMAIN to view metrics on grafana dashboard" +echo "visit https://prometheus.$NETMAKER_BASE_DOMAIN to view metrics on prometheus" sleep 7 setup_mesh() {( set -e diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 1b7c1815..262327a5 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -281,6 +281,21 @@ func IsRestBackend() bool { return isrest } +// IsMetricsExporter - checks if metrics exporter is on or off +func IsMetricsExporter() bool { + export := false + if os.Getenv("METRICS_EXPORTER") != "" { + if os.Getenv("METRICS_EXPORTER") == "on" { + export = true + } + } else if config.Config.Server.MetricsExporter != "" { + if config.Config.Server.MetricsExporter == "on" { + export = true + } + } + return export +} + // IsAgentBackend - checks if agent backed is on or off func IsAgentBackend() bool { isagent := true @@ -600,3 +615,32 @@ func GetMQServerPort() string { } return port } + +// IsBasicAuthEnabled - checks if basic auth has been configured to be turned off +func IsBasicAuthEnabled() bool { + var enabled = true //default + if os.Getenv("BASIC_AUTH") != "" { + enabled = os.Getenv("BASIC_AUTH") == "yes" + } else if config.Config.Server.BasicAuth != "" { + enabled = config.Config.Server.BasicAuth == "yes" + } + return enabled +} + +// GetLicenseKey - retrieves pro license value from env or conf files +func GetLicenseKey() string { + licenseKeyValue := os.Getenv("LICENSE_KEY") + if licenseKeyValue == "" { + licenseKeyValue = config.Config.Server.LicenseValue + } + return licenseKeyValue +} + +// GetNetmakerAccountID - get's the associated, Netmaker, account ID to verify ownership +func GetNetmakerAccountID() string { + netmakerAccountID := os.Getenv("NETMAKER_ACCOUNT_ID") + if netmakerAccountID == "" { + netmakerAccountID = config.Config.Server.LicenseValue + } + return netmakerAccountID +} diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index 8ffc76b9..4721d8c3 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -45,6 +45,9 @@ func InitServerNetclient() error { logger.Log(1, "failed pull for network", network.NetID, ", on server node", currentServerNode.ID) } } + if err = logic.InitializeNetUsers(&network); err != nil { + logger.Log(0, "something went wrong syncing usrs on network", network.NetID, "-", err.Error()) + } } } diff --git a/validation/validation.go b/validation/validation.go index 39e256b1..79746efd 100644 --- a/validation/validation.go +++ b/validation/validation.go @@ -11,6 +11,11 @@ func CheckYesOrNo(fl validator.FieldLevel) bool { return fl.Field().String() == "yes" || fl.Field().String() == "no" } +// CheckYesOrNoOrUnset - checks if a field is yes, no or unset +func CheckYesOrNoOrUnset(fl validator.FieldLevel) bool { + return CheckYesOrNo(fl) || fl.Field().String() == "unset" +} + // CheckRegex - check if a struct's field passes regex test func CheckRegex(fl validator.FieldLevel) bool { re := regexp.MustCompile(fl.Param()) From aa0fd638f88dad926cedde61c85c7736e8020e27 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 15:29:16 -0400 Subject: [PATCH 07/52] adding service file change to SetupSystemD --- netclient/daemon/systemd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netclient/daemon/systemd.go b/netclient/daemon/systemd.go index efbf98f0..aa218734 100644 --- a/netclient/daemon/systemd.go +++ b/netclient/daemon/systemd.go @@ -46,7 +46,7 @@ func SetupSystemDDaemon() error { Description=Netclient Daemon Documentation=https://docs.netmaker.org https://k8s.netmaker.org After=network-online.target -Wants=network-online.target systemd-networkd-wait-online.service +Wants=network-online.target [Service] User=root From 96772bb4bd90ab16ae156a1b781120581a77d47b Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 15:41:23 -0400 Subject: [PATCH 08/52] network and ACL initialization --- logic/nodes.go | 1 + logic/pro/networkuser.go | 17 +++++++++++------ models/node.go | 7 +++++++ netclient/command/commands.go | 3 +++ serverctl/serverctl.go | 19 +++++++++++++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/logic/nodes.go b/logic/nodes.go index 84227f28..7b0f6fe9 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -475,6 +475,7 @@ func SetNodeDefaults(node *models.Node) { node.SetDefaultIsK8S() node.SetDefaultIsHub() node.SetDefaultConnected() + node.SetDefaultACL() } // GetRecordKey - get record key diff --git a/logic/pro/networkuser.go b/logic/pro/networkuser.go index 49c33640..1811cc47 100644 --- a/logic/pro/networkuser.go +++ b/logic/pro/networkuser.go @@ -11,13 +11,18 @@ import ( // InitializeNetworkUsers - intializes network users for a given network func InitializeNetworkUsers(network string) error { - newNetUserMap := make(promodels.NetworkUserMap) - netUserData, err := json.Marshal(newNetUserMap) - if err != nil { - return err - } - return database.Insert(network, string(netUserData), database.NETWORK_USER_TABLE_NAME) + _, err := database.FetchRecord(database.NETWORK_USER_TABLE_NAME, network) + if err != nil && database.IsEmptyRecord(err) { + newNetUserMap := make(promodels.NetworkUserMap) + netUserData, err := json.Marshal(newNetUserMap) + if err != nil { + return err + } + + return database.Insert(network, string(netUserData), database.NETWORK_USER_TABLE_NAME) + } + return err } // GetNetworkUsers - gets the network users table diff --git a/models/node.go b/models/node.go index 0aebf0a0..25c01afc 100644 --- a/models/node.go +++ b/models/node.go @@ -142,6 +142,13 @@ func (node *Node) SetDefaultConnected() { } } +// Node.SetDefaultACL +func (node *Node) SetDefaultACL() { + if node.DefaultACL == "" { + node.DefaultACL = "yes" + } +} + // Node.SetDefaultMTU - sets default MTU of a node func (node *Node) SetDefaultMTU() { if node.MTU == 0 { diff --git a/netclient/command/commands.go b/netclient/command/commands.go index cd163dc3..b8c7d082 100644 --- a/netclient/command/commands.go +++ b/netclient/command/commands.go @@ -24,6 +24,9 @@ func Join(cfg *config.ClientConfig, privateKey string) error { // Do that before the Joining Network flow by performing the end point auth flow // if performed successfully an access key is obtained from the server and then we // proceed with the usual flow 'pretending' that user is feeded us with an access token + if len(cfg.Network) == 0 || cfg.Network == "all" { + return fmt.Errorf("no network provided. Specify network with \"-n \"") + } logger.Log(1, "Logging into %s via:", cfg.Network, cfg.SsoServer) err = functions.JoinViaSSo(cfg, privateKey) if err != nil { diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index 4721d8c3..81b657d8 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -10,6 +10,7 @@ import ( "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/acls" "github.com/gravitl/netmaker/logic/acls/nodeacls" + "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/servercfg" ) @@ -89,6 +90,10 @@ func SetDefaults() error { return err } + if err := setNetworkDefaults(); err != nil { + return err + } + return nil } @@ -111,3 +116,17 @@ func setNodeDefaults() error { } return nil } + +func setNetworkDefaults() error { + // upgraded systems will not have NetworkUsers's set, which is why we need this function + networks, err := logic.GetNetworks() + if err != nil && !database.IsEmptyRecord(err) { + return err + } + for _, net := range networks { + if err = pro.InitializeNetworkUsers(net.NetID); err != nil { + logger.Log(0, "could not initialize NetworkUsers on network ", net.NetID) + } + } + return nil +} From 4917c457dba768e24eaea3cc278c65d1a5f9dcda Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 15:44:45 -0400 Subject: [PATCH 09/52] added ee check --- config/config.go | 1 + controllers/server.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/config/config.go b/config/config.go index 973c1d5d..2fd6df25 100644 --- a/config/config.go +++ b/config/config.go @@ -74,6 +74,7 @@ type ServerConfig struct { BasicAuth string `yaml:"basic_auth"` LicenseValue string `yaml:"license_value"` NetmakerAccountID string `yaml:"netmaker_account_id"` + IsEE string `yaml:"is_ee"` } // SQLConfig - Generic SQL Config diff --git a/controllers/server.go b/controllers/server.go index fb7541cc..94e2d366 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/gorilla/mux" + "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" @@ -136,6 +137,10 @@ func getConfig(w http.ResponseWriter, r *http.Request) { // get params scfg := servercfg.GetServerConfig() + scfg.IsEE = "no" + if ee.IsEnterprise() { + scfg.IsEE = "yes" + } json.NewEncoder(w).Encode(scfg) //w.WriteHeader(http.StatusOK) } From 90bb3884e6285aaa85672de92922b8d152734aba Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 16:32:11 -0400 Subject: [PATCH 10/52] initialize network settings to avoid panic --- serverctl/serverctl.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index 81b657d8..2d32edd7 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -125,7 +125,12 @@ func setNetworkDefaults() error { } for _, net := range networks { if err = pro.InitializeNetworkUsers(net.NetID); err != nil { - logger.Log(0, "could not initialize NetworkUsers on network ", net.NetID) + logger.Log(0, "could not initialize NetworkUsers on network", net.NetID) + } + pro.AddProNetDefaults(&net) + _, _, _, _, _, _, err = logic.UpdateNetwork(&net, &net) + if err != nil { + logger.Log(0, "could not set defaults on network", net.NetID) } } return nil From e637c3d5502ba784d2f3b0320bfb4d349d80a439 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 16:35:14 -0400 Subject: [PATCH 11/52] dont publish server metrics --- mq/publishers.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mq/publishers.go b/mq/publishers.go index abdc5167..fd48529f 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/pro/metrics" @@ -184,6 +185,9 @@ func ServerStartNotify() error { // function to collect and store metrics for server nodes func collectServerMetrics(networks []models.Network) { + if !ee.IsEnterprise() { + return + } if len(networks) > 0 { for i := range networks { currentNetworkNodes, err := logic.GetNetworkNodes(networks[i].NetID) From 51bc7c227241dffed8b83f93cca7550bdb8f7999 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 16:42:28 -0400 Subject: [PATCH 12/52] remove bad log --- logic/peers.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/logic/peers.go b/logic/peers.go index c197f3f1..9d79f3f4 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -3,7 +3,6 @@ package logic import ( "errors" "fmt" - "log" "net" "strconv" "strings" @@ -158,8 +157,8 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) { extPeers, err := getExtPeers(node) if err == nil { peers = append(peers, extPeers...) - } else { - log.Println("ERROR RETRIEVING EXTERNAL PEERS", err) + } else if !database.IsEmptyRecord(err) { + logger.Log(1, "error retrieving external clients:", err.Error()) } } From e340b49aa88bd8e12b580f45058ff89bccf3c98e Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 13 Sep 2022 17:03:17 -0400 Subject: [PATCH 13/52] set nil settings --- logic/pro/networks.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/logic/pro/networks.go b/logic/pro/networks.go index 0b07311c..ae29ba6b 100644 --- a/logic/pro/networks.go +++ b/logic/pro/networks.go @@ -17,6 +17,12 @@ func AddProNetDefaults(network *models.Network) { } network.ProSettings = &newProSettings } + if network.ProSettings.AllowedUsers == nil { + network.ProSettings.AllowedUsers = []string{} + } + if network.ProSettings.AllowedGroups == nil { + network.ProSettings.AllowedGroups = []string{} + } } // isUserGroupAllowed - checks if a user group is allowed on a network From 04875c8b5e0eda944898dc831f4fff097459ebbf Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Wed, 14 Sep 2022 08:17:35 -0400 Subject: [PATCH 14/52] add darwin-nogui to release assets --- .github/workflows/buildandrelease.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/buildandrelease.yml b/.github/workflows/buildandrelease.yml index 230c1775..7f001eaa 100644 --- a/.github/workflows/buildandrelease.yml +++ b/.github/workflows/buildandrelease.yml @@ -402,6 +402,7 @@ jobs: cd netclient env GOOS=darwin GOARCH=amd64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient . env CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-arm64/netclient main.go + env GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient-darwin-nogui . - name: Upload darwin-amd64 to Release uses: svenstaro/upload-release-action@v2 with: @@ -421,7 +422,17 @@ jobs: overwrite: true prerelease: true asset_name: netclient-darwin-arm64 - + + - name: Upload darwin-nogui to Release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: netclient/build/netclient-darwin-nogui/netclient + tag: ${{ env.NETMAKER_VERSION }} + overwrite: true + prerelease: true + asset_name: netclient-darwin-nogui + netclient-windows: runs-on: windows-latest needs: version From d0593add15a7d7a3c1dda456d47a05bf8f90135f Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Wed, 14 Sep 2022 09:30:12 -0400 Subject: [PATCH 15/52] nogui --> headless --- .github/workflows/buildandrelease.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/buildandrelease.yml b/.github/workflows/buildandrelease.yml index 7f001eaa..520b87ec 100644 --- a/.github/workflows/buildandrelease.yml +++ b/.github/workflows/buildandrelease.yml @@ -402,7 +402,7 @@ jobs: cd netclient env GOOS=darwin GOARCH=amd64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient . env CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-arm64/netclient main.go - env GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient-darwin-nogui . + env GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient-darwin-headless . - name: Upload darwin-amd64 to Release uses: svenstaro/upload-release-action@v2 with: @@ -423,15 +423,15 @@ jobs: prerelease: true asset_name: netclient-darwin-arm64 - - name: Upload darwin-nogui to Release + - name: Upload darwin-headless to Release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: netclient/build/netclient-darwin-nogui/netclient + file: netclient/build/netclient-darwin-headless/netclient tag: ${{ env.NETMAKER_VERSION }} overwrite: true prerelease: true - asset_name: netclient-darwin-nogui + asset_name: netclient-darwin-headless netclient-windows: runs-on: windows-latest From 8a1ba674a77a8b9a3e7ae3fef5af8fe12ff839f8 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 09:58:01 -0400 Subject: [PATCH 16/52] ee fixes --- controllers/networkusers.go | 2 +- logic/auth.go | 2 ++ logic/pro/networks.go | 4 ++-- logic/users.go | 7 +++++++ serverctl/serverctl.go | 22 ++++++++++++++++++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/controllers/networkusers.go b/controllers/networkusers.go index 1e6c37d4..861ba0f4 100644 --- a/controllers/networkusers.go +++ b/controllers/networkusers.go @@ -95,7 +95,7 @@ func getNetworkUserData(w http.ResponseWriter, r *http.Request) { Clients: netUser.Clients, } // check network level permissions - if doesNetworkAllow := pro.IsUserAllowed(&networks[i], networkUserName, u.Groups); doesNetworkAllow { + if doesNetworkAllow := pro.IsUserAllowed(&networks[i], networkUserName, u.Groups); doesNetworkAllow || netUser.AccessLevel == pro.NET_ADMIN { netNodes, err := logic.GetNetworkNodes(netID) if err != nil { logger.Log(0, "failed to retrieve nodes on network", netID, "for user", string(netUser.ID)) diff --git a/logic/auth.go b/logic/auth.go index 4282f490..f282d534 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -103,6 +103,8 @@ func CreateUser(user models.User) (models.User, error) { return user, err } + SetUserDefaults(&user) + // connect db data, err := json.Marshal(&user) if err != nil { diff --git a/logic/pro/networks.go b/logic/pro/networks.go index ae29ba6b..480c7c1d 100644 --- a/logic/pro/networks.go +++ b/logic/pro/networks.go @@ -13,7 +13,7 @@ func AddProNetDefaults(network *models.Network) { DefaultUserNodeLimit: 0, DefaultUserClientLimit: 0, AllowedUsers: []string{}, - AllowedGroups: []string{}, + AllowedGroups: []string{DEFAULT_ALLOWED_GROUPS}, } network.ProSettings = &newProSettings } @@ -21,7 +21,7 @@ func AddProNetDefaults(network *models.Network) { network.ProSettings.AllowedUsers = []string{} } if network.ProSettings.AllowedGroups == nil { - network.ProSettings.AllowedGroups = []string{} + network.ProSettings.AllowedGroups = []string{DEFAULT_ALLOWED_GROUPS} } } diff --git a/logic/users.go b/logic/users.go index f920905f..6de35e83 100644 --- a/logic/users.go +++ b/logic/users.go @@ -70,3 +70,10 @@ func InitializeNetUsers(network *models.Network) error { } return nil } + +// SetUserDefaults - sets the defaults of a user to avoid empty fields +func SetUserDefaults(user *models.User) { + if user.Groups == nil { + user.Groups = []string{pro.DEFAULT_ALLOWED_GROUPS} + } +} diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index 2d32edd7..b81b95ea 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -94,6 +94,10 @@ func SetDefaults() error { return err } + if err := setUserDefaults(); err != nil { + return err + } + return nil } @@ -135,3 +139,21 @@ func setNetworkDefaults() error { } return nil } + +func setUserDefaults() error { + users, err := logic.GetUsers() + if err != nil && !database.IsEmptyRecord(err) { + return err + } + for _, user := range users { + updateUser, err := logic.GetUser(user.UserName) + if err != nil { + logger.Log(0, "could not update user", updateUser.UserName) + } + logic.SetUserDefaults(&updateUser) + if _, err = logic.UpdateUser(updateUser, updateUser); err != nil { + logger.Log(0, "could not update user", updateUser.UserName) + } + } + return nil +} From b670755cceb80ece76b54f522ed2ba982392e71c Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 13:26:31 -0400 Subject: [PATCH 17/52] refactoring for ee --- controllers/controller.go | 1 - controllers/dns.go | 34 +++--- controllers/ext_client.go | 66 +++++------ controllers/limits.go | 19 ++-- controllers/logger.go | 3 +- controllers/network.go | 92 +++++++-------- controllers/network_test.go | 8 +- controllers/networkusers.go | 66 +++++------ controllers/node.go | 107 +++++++++--------- controllers/relay.go | 6 +- controllers/response_test.go | 7 +- controllers/server.go | 70 ++---------- controllers/user.go | 68 +++++------ controllers/usergroups.go | 20 ++-- {controllers => ee/ee_controllers}/metrics.go | 17 +-- ee/initialize.go | 54 +++++++++ ee/license.go | 91 ++++++++++----- ee/util.go | 2 +- functions/helpers.go | 11 -- functions/helpers_test.go | 4 +- logic/auth.go | 2 +- controllers/response.go => logic/errors.go | 11 +- logic/{pro => }/metrics/metrics.go | 0 logic/networks.go | 13 ++- logic/nodes.go | 2 +- logic/pro/license.go | 66 ----------- logic/pro/networks_test.go | 4 +- {controllers => logic}/security.go | 63 ++++++----- logic/server.go | 9 ++ logic/serverconf.go | 15 +++ main.go | 16 +-- main_ee.go | 20 +--- mq/handlers.go | 3 +- mq/publishers.go | 5 +- netclient/functions/mqpublish.go | 2 +- 35 files changed, 473 insertions(+), 504 deletions(-) rename {controllers => ee/ee_controllers}/metrics.go (81%) create mode 100644 ee/initialize.go rename controllers/response.go => logic/errors.go (77%) rename logic/{pro => }/metrics/metrics.go (100%) delete mode 100644 logic/pro/license.go rename {controllers => logic}/security.go (71%) diff --git a/controllers/controller.go b/controllers/controller.go index f43897c3..015ffdb9 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -25,7 +25,6 @@ var HttpHandlers = []interface{}{ serverHandlers, extClientHandlers, ipHandlers, - metricHandlers, loggerHandlers, userGroupsHandlers, networkUsersHandlers, diff --git a/controllers/dns.go b/controllers/dns.go index c5ae5afa..f9e7a3e5 100644 --- a/controllers/dns.go +++ b/controllers/dns.go @@ -16,13 +16,13 @@ import ( func dnsHandlers(r *mux.Router) { - r.HandleFunc("/api/dns", securityCheck(true, http.HandlerFunc(getAllDNS))).Methods("GET") - r.HandleFunc("/api/dns/adm/{network}/nodes", securityCheck(false, http.HandlerFunc(getNodeDNS))).Methods("GET") - r.HandleFunc("/api/dns/adm/{network}/custom", securityCheck(false, http.HandlerFunc(getCustomDNS))).Methods("GET") - r.HandleFunc("/api/dns/adm/{network}", securityCheck(false, http.HandlerFunc(getDNS))).Methods("GET") - r.HandleFunc("/api/dns/{network}", securityCheck(false, http.HandlerFunc(createDNS))).Methods("POST") - r.HandleFunc("/api/dns/adm/pushdns", securityCheck(false, http.HandlerFunc(pushDNS))).Methods("POST") - r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(false, http.HandlerFunc(deleteDNS))).Methods("DELETE") + r.HandleFunc("/api/dns", logic.SecurityCheck(true, http.HandlerFunc(getAllDNS))).Methods("GET") + r.HandleFunc("/api/dns/adm/{network}/nodes", logic.SecurityCheck(false, http.HandlerFunc(getNodeDNS))).Methods("GET") + r.HandleFunc("/api/dns/adm/{network}/custom", logic.SecurityCheck(false, http.HandlerFunc(getCustomDNS))).Methods("GET") + r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(false, http.HandlerFunc(getDNS))).Methods("GET") + r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(false, http.HandlerFunc(createDNS))).Methods("POST") + r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(false, http.HandlerFunc(pushDNS))).Methods("POST") + r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(false, http.HandlerFunc(deleteDNS))).Methods("DELETE") } // swagger:route GET /api/dns/adm/{network}/nodes dns getNodeDNS @@ -44,7 +44,7 @@ func getNodeDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get node DNS entries for network [%s]: %v", network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.WriteHeader(http.StatusOK) @@ -68,7 +68,7 @@ func getAllDNS(w http.ResponseWriter, r *http.Request) { dns, err := logic.GetAllDNS() if err != nil { logger.Log(0, r.Header.Get("user"), "failed to get all DNS entries: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.WriteHeader(http.StatusOK) @@ -98,7 +98,7 @@ func getCustomDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get custom DNS entries for network [%s]: %v", network, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.WriteHeader(http.StatusOK) @@ -128,7 +128,7 @@ func getDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get all DNS entries for network [%s]: %v", network, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.WriteHeader(http.StatusOK) @@ -160,7 +160,7 @@ func createDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("invalid DNS entry %+v: %v", entry, err)) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -168,14 +168,14 @@ func createDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("Failed to create DNS entry %+v: %v", entry, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = logic.SetDNS() if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("Failed to set DNS entries on file: %v", err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(1, "new DNS record added:", entry.Name) @@ -221,7 +221,7 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, "failed to delete dns entry: ", entrytext) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(1, "deleted dns entry: ", entrytext) @@ -229,7 +229,7 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("Failed to set DNS entries on file: %v", err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } json.NewEncoder(w).Encode(entrytext + " deleted.") @@ -287,7 +287,7 @@ func pushDNS(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("Failed to set DNS entries on file: %v", err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(1, r.Header.Get("user"), "pushed DNS updates to nameserver") diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 05cdad33..3ac9a05f 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -21,13 +21,13 @@ import ( func extClientHandlers(r *mux.Router) { - r.HandleFunc("/api/extclients", securityCheck(false, http.HandlerFunc(getAllExtClients))).Methods("GET") - r.HandleFunc("/api/extclients/{network}", securityCheck(false, http.HandlerFunc(getNetworkExtClients))).Methods("GET") - r.HandleFunc("/api/extclients/{network}/{clientid}", securityCheck(false, http.HandlerFunc(getExtClient))).Methods("GET") - r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", netUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods("GET") - r.HandleFunc("/api/extclients/{network}/{clientid}", netUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods("PUT") - r.HandleFunc("/api/extclients/{network}/{clientid}", netUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods("DELETE") - r.HandleFunc("/api/extclients/{network}/{nodeid}", netUserSecurityCheck(false, true, checkFreeTierLimits(clients_l, http.HandlerFunc(createExtClient)))).Methods("POST") + r.HandleFunc("/api/extclients", logic.SecurityCheck(false, http.HandlerFunc(getAllExtClients))).Methods("GET") + r.HandleFunc("/api/extclients/{network}", logic.SecurityCheck(false, http.HandlerFunc(getNetworkExtClients))).Methods("GET") + r.HandleFunc("/api/extclients/{network}/{clientid}", logic.SecurityCheck(false, http.HandlerFunc(getExtClient))).Methods("GET") + r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods("GET") + r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods("PUT") + r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods("DELETE") + r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.NetUserSecurityCheck(false, true, checkFreeTierLimits(clients_l, http.HandlerFunc(createExtClient)))).Methods("POST") } func checkIngressExists(nodeID string) bool { @@ -62,7 +62,7 @@ func getNetworkExtClients(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get ext clients for network [%s]: %v", network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -96,16 +96,16 @@ func getAllExtClients(w http.ResponseWriter, r *http.Request) { if marshalErr != nil { logger.Log(0, "error unmarshalling networks: ", marshalErr.Error()) - returnErrorResponse(w, r, formatError(marshalErr, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(marshalErr, "internal")) return } clients := []models.ExtClient{} var err error - if networksSlice[0] == ALL_NETWORK_ACCESS { + if networksSlice[0] == logic.ALL_NETWORK_ACCESS { clients, err = functions.GetAllExtClients() if err != nil && !database.IsEmptyRecord(err) { logger.Log(0, "failed to get all extclients: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } else { @@ -146,7 +146,7 @@ func getExtClient(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get extclient for [%s] on network [%s]: %v", clientid, network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -177,7 +177,7 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get extclient for [%s] on network [%s]: %v", clientid, networkid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -185,14 +185,14 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get ingress gateway node [%s] info: %v", client.IngressGatewayID, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } network, err := logic.GetParentNetwork(client.Network) if err != nil { logger.Log(1, r.Header.Get("user"), "Could not retrieve Ingress Gateway Network", client.Network) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -258,7 +258,7 @@ Endpoint = %s bytes, err := qrcode.Encode(config, qrcode.Medium, 220) if err != nil { logger.Log(1, r.Header.Get("user"), "failed to encode qr code: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.Header().Set("Content-Type", "image/png") @@ -266,7 +266,7 @@ Endpoint = %s _, err = w.Write(bytes) if err != nil { logger.Log(1, r.Header.Get("user"), "response writer error (qr) ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } return @@ -280,7 +280,7 @@ Endpoint = %s _, err := fmt.Fprint(w, config) if err != nil { logger.Log(1, r.Header.Get("user"), "response writer error (file) ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) } return } @@ -310,7 +310,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { err := errors.New("ingress does not exist") logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to create extclient on network [%s]: %v", networkName, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -329,7 +329,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get ingress gateway node [%s] info: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } extclient.IngressGatewayEndpoint = node.Endpoint + ":" + strconv.FormatInt(int64(node.ListenPort), 10) @@ -345,7 +345,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to create new ext client on network [%s]: %v", networkName, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -355,7 +355,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { if isAdmin, err = checkProClientAccess(userID, extclient.ClientID, &parentNetwork); err != nil { logger.Log(0, userID, "attempted to create a client on network", networkName, "but they lack access") logic.DeleteExtClient(networkName, extclient.ClientID) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if !isAdmin { @@ -400,7 +400,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } clientid := params["clientid"] @@ -410,7 +410,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get record key for client [%s], network [%s]: %v", clientid, network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } data, err := database.FetchRecord(database.EXT_CLIENT_TABLE_NAME, key) @@ -418,13 +418,13 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch ext client record key [%s] from db for client [%s], network [%s]: %v", key, clientid, network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if err = json.Unmarshal([]byte(data), &oldExtClient); err != nil { logger.Log(0, "error unmarshalling extclient: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -435,7 +435,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { userID := r.Header.Get("user") _, doesOwn := doesUserOwnClient(userID, params["clientid"], networkName) if !doesOwn { - returnErrorResponse(w, r, formatError(fmt.Errorf("user not permitted"), "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "internal")) return } } @@ -457,7 +457,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update ext client [%s], network [%s]: %v", clientid, network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(0, r.Header.Get("user"), "updated ext client", newExtClient.ClientID) @@ -497,14 +497,14 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { err = errors.New("Could not delete extclient " + params["clientid"]) logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete extclient [%s],network [%s]: %v", clientid, network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } ingressnode, err := logic.GetNodeByID(extclient.IngressGatewayID) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get ingress gateway node [%s] info: %v", extclient.IngressGatewayID, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -513,7 +513,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { userID, clientID, networkName := r.Header.Get("user"), params["clientid"], params["network"] _, doesOwn := doesUserOwnClient(userID, clientID, networkName) if !doesOwn { - returnErrorResponse(w, r, formatError(fmt.Errorf("user not permitted"), "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "internal")) return } } @@ -531,7 +531,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete extclient [%s],network [%s]: %v", clientid, network, err)) err = errors.New("Could not delete extclient " + params["clientid"]) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -542,7 +542,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), "Deleted extclient client", params["clientid"], "from network", params["network"]) - returnSuccessResponse(w, r, params["clientid"]+" deleted.") + logic.ReturnSuccessResponse(w, r, params["clientid"]+" deleted.") } func checkProClientAccess(username, clientID string, network *models.Network) (bool, error) { diff --git a/controllers/limits.go b/controllers/limits.go index ddbff298..55ccb313 100644 --- a/controllers/limits.go +++ b/controllers/limits.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" ) @@ -23,32 +22,32 @@ func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc { Code: http.StatusUnauthorized, Message: "free tier limits exceeded on networks", } - if ee.Limits.FreeTier { // check that free tier limits not exceeded + if logic.Free_Tier && logic.Is_EE { // check that free tier limits not exceeded if limit_choice == networks_l { currentNetworks, err := logic.GetNetworks() - if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= ee.Limits.Networks { - returnErrorResponse(w, r, errorResponse) + 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) >= ee.Limits.Nodes { + if (err != nil && !database.IsEmptyRecord(err)) || len(nodes) >= logic.Node_Limit { errorResponse.Message = "free tier limits exceeded on nodes" - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } } else if limit_choice == users_l { users, err := logic.GetUsers() - if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= ee.Limits.Users { + if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit { errorResponse.Message = "free tier limits exceeded on users" - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } } else if limit_choice == clients_l { clients, err := logic.GetAllExtClients() - if (err != nil && !database.IsEmptyRecord(err)) || len(clients) >= ee.Limits.Clients { + if (err != nil && !database.IsEmptyRecord(err)) || len(clients) >= logic.Clients_Limit { errorResponse.Message = "free tier limits exceeded on external clients" - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } } diff --git a/controllers/logger.go b/controllers/logger.go index 316783fc..09d740d1 100644 --- a/controllers/logger.go +++ b/controllers/logger.go @@ -7,10 +7,11 @@ import ( "github.com/gorilla/mux" "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" ) func loggerHandlers(r *mux.Router) { - r.HandleFunc("/api/logs", securityCheck(true, http.HandlerFunc(getLogs))).Methods("GET") + r.HandleFunc("/api/logs", logic.SecurityCheck(true, http.HandlerFunc(getLogs))).Methods("GET") } func getLogs(w http.ResponseWriter, r *http.Request) { diff --git a/controllers/network.go b/controllers/network.go index 7c9c71f1..6a5f3f99 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -17,26 +17,20 @@ import ( "github.com/gravitl/netmaker/servercfg" ) -// ALL_NETWORK_ACCESS - represents all networks -const ALL_NETWORK_ACCESS = "THIS_USER_HAS_ALL" - -// NO_NETWORKS_PRESENT - represents no networks -const NO_NETWORKS_PRESENT = "THIS_USER_HAS_NONE" - func networkHandlers(r *mux.Router) { - r.HandleFunc("/api/networks", securityCheck(false, http.HandlerFunc(getNetworks))).Methods("GET") - r.HandleFunc("/api/networks", securityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods("POST") - r.HandleFunc("/api/networks/{networkname}", securityCheck(false, http.HandlerFunc(getNetwork))).Methods("GET") - r.HandleFunc("/api/networks/{networkname}", securityCheck(false, http.HandlerFunc(updateNetwork))).Methods("PUT") - r.HandleFunc("/api/networks/{networkname}/nodelimit", securityCheck(true, http.HandlerFunc(updateNetworkNodeLimit))).Methods("PUT") - r.HandleFunc("/api/networks/{networkname}", securityCheck(true, http.HandlerFunc(deleteNetwork))).Methods("DELETE") - r.HandleFunc("/api/networks/{networkname}/keyupdate", securityCheck(true, http.HandlerFunc(keyUpdate))).Methods("POST") - r.HandleFunc("/api/networks/{networkname}/keys", securityCheck(false, http.HandlerFunc(createAccessKey))).Methods("POST") - r.HandleFunc("/api/networks/{networkname}/keys", securityCheck(false, http.HandlerFunc(getAccessKeys))).Methods("GET") - r.HandleFunc("/api/networks/{networkname}/keys/{name}", securityCheck(false, http.HandlerFunc(deleteAccessKey))).Methods("DELETE") + r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods("GET") + r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods("POST") + r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods("GET") + r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(updateNetwork))).Methods("PUT") + r.HandleFunc("/api/networks/{networkname}/nodelimit", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkNodeLimit))).Methods("PUT") + r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods("DELETE") + r.HandleFunc("/api/networks/{networkname}/keyupdate", logic.SecurityCheck(true, http.HandlerFunc(keyUpdate))).Methods("POST") + r.HandleFunc("/api/networks/{networkname}/keys", logic.SecurityCheck(false, http.HandlerFunc(createAccessKey))).Methods("POST") + r.HandleFunc("/api/networks/{networkname}/keys", logic.SecurityCheck(false, http.HandlerFunc(getAccessKeys))).Methods("GET") + r.HandleFunc("/api/networks/{networkname}/keys/{name}", logic.SecurityCheck(false, http.HandlerFunc(deleteAccessKey))).Methods("DELETE") // ACLs - r.HandleFunc("/api/networks/{networkname}/acls", securityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods("PUT") - r.HandleFunc("/api/networks/{networkname}/acls", securityCheck(true, http.HandlerFunc(getNetworkACL))).Methods("GET") + r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods("PUT") + r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods("GET") } // swagger:route GET /api/networks networks getNetworks @@ -58,16 +52,16 @@ func getNetworks(w http.ResponseWriter, r *http.Request) { if marshalErr != nil { logger.Log(0, r.Header.Get("user"), "error unmarshalling networks: ", marshalErr.Error()) - returnErrorResponse(w, r, formatError(marshalErr, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(marshalErr, "badrequest")) return } allnetworks := []models.Network{} var err error - if networksSlice[0] == ALL_NETWORK_ACCESS { + if networksSlice[0] == logic.ALL_NETWORK_ACCESS { allnetworks, err = logic.GetNetworks() if err != nil && !database.IsEmptyRecord(err) { logger.Log(0, r.Header.Get("user"), "failed to fetch networks: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } else { @@ -110,7 +104,7 @@ func getNetwork(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch network [%s] info: %v", netname, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if !servercfg.IsDisplayKeys() { @@ -140,7 +134,7 @@ func keyUpdate(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update keys for network [%s]: %v", netname, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "updated key on network", netname) @@ -182,7 +176,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "failed to get network info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } var newNetwork models.Network @@ -190,7 +184,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -203,7 +197,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "failed to update network: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -231,7 +225,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update network [%s] ipv4 addresses: %v", network.NetID, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -241,7 +235,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update network [%s] ipv6 addresses: %v", network.NetID, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -251,7 +245,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update network [%s] local addresses: %v", network.NetID, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -261,7 +255,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update network [%s] hole punching: %v", network.NetID, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -271,7 +265,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get network [%s] nodes: %v", network.NetID, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } for _, node := range nodes { @@ -305,7 +299,7 @@ func updateNetworkNodeLimit(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get network [%s] nodes: %v", network.NetID, err.Error())) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -315,7 +309,7 @@ func updateNetworkNodeLimit(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if networkChange.NodeLimit != 0 { @@ -324,7 +318,7 @@ func updateNetworkNodeLimit(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "error marshalling resp: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } database.Insert(network.NetID, string(data), database.NETWORKS_TABLE_NAME) @@ -354,21 +348,21 @@ func updateNetworkACL(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = json.NewDecoder(r.Body).Decode(&networkACLChange) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } newNetACL, err := networkACLChange.Save(acls.ContainerID(netname)) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err)) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname) @@ -412,7 +406,7 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "fetched acl for network", netname) @@ -445,7 +439,7 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) { } logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete network [%s]: %v", network, err)) - returnErrorResponse(w, r, formatError(err, errtype)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype)) return } logger.Log(1, r.Header.Get("user"), "deleted network", network) @@ -475,7 +469,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -483,7 +477,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { err := errors.New("IPv4 or IPv6 CIDR required") logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -491,7 +485,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -504,7 +498,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { } logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -537,28 +531,28 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "failed to get network info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = json.NewDecoder(r.Body).Decode(&accesskey) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } key, err := logic.CreateAccessKey(accesskey, network) if err != nil { logger.Log(0, r.Header.Get("user"), "failed to create access key: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } // do not allow access key creations view API with user names if _, err = logic.GetUser(key.Name); err == nil { logger.Log(0, "access key creation with invalid name attempted by", r.Header.Get("user")) - returnErrorResponse(w, r, formatError(fmt.Errorf("cannot create access key with user name"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("cannot create access key with user name"), "badrequest")) logic.DeleteKey(key.Name, network.NetID) return } @@ -587,7 +581,7 @@ func getAccessKeys(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get keys for network [%s]: %v", network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if !servercfg.IsDisplayKeys() { @@ -621,7 +615,7 @@ func deleteAccessKey(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete key [%s] for network [%s]: %v", keyname, netname, err)) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, r.Header.Get("user"), "deleted access key", keyname, "on network,", netname) diff --git a/controllers/network_test.go b/controllers/network_test.go index a85b03e3..03b11759 100644 --- a/controllers/network_test.go +++ b/controllers/network_test.go @@ -182,24 +182,24 @@ func TestSecurityCheck(t *testing.T) { initialize() os.Setenv("MASTER_KEY", "secretkey") t.Run("NoNetwork", func(t *testing.T) { - networks, username, err := SecurityCheck(false, "", "Bearer secretkey") + networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey") assert.Nil(t, err) t.Log(networks, username) }) t.Run("WithNetwork", func(t *testing.T) { - networks, username, err := SecurityCheck(false, "skynet", "Bearer secretkey") + networks, username, err := logic.UserPermissions(false, "skynet", "Bearer secretkey") assert.Nil(t, err) t.Log(networks, username) }) t.Run("BadNet", func(t *testing.T) { t.Skip() - networks, username, err := SecurityCheck(false, "badnet", "Bearer secretkey") + networks, username, err := logic.UserPermissions(false, "badnet", "Bearer secretkey") assert.NotNil(t, err) t.Log(err) t.Log(networks, username) }) t.Run("BadToken", func(t *testing.T) { - networks, username, err := SecurityCheck(false, "skynet", "Bearer badkey") + networks, username, err := logic.UserPermissions(false, "skynet", "Bearer badkey") assert.NotNil(t, err) t.Log(err) t.Log(networks, username) diff --git a/controllers/networkusers.go b/controllers/networkusers.go index 861ba0f4..8ac4e24f 100644 --- a/controllers/networkusers.go +++ b/controllers/networkusers.go @@ -14,13 +14,13 @@ import ( ) func networkUsersHandlers(r *mux.Router) { - r.HandleFunc("/api/networkusers", securityCheck(true, http.HandlerFunc(getAllNetworkUsers))).Methods("GET") - r.HandleFunc("/api/networkusers/{network}", securityCheck(true, http.HandlerFunc(getNetworkUsers))).Methods("GET") - r.HandleFunc("/api/networkusers/{network}/{networkuser}", securityCheck(true, http.HandlerFunc(getNetworkUser))).Methods("GET") - r.HandleFunc("/api/networkusers/{network}", securityCheck(true, http.HandlerFunc(createNetworkUser))).Methods("POST") - r.HandleFunc("/api/networkusers/{network}", securityCheck(true, http.HandlerFunc(updateNetworkUser))).Methods("PUT") - r.HandleFunc("/api/networkusers/data/{networkuser}/me", netUserSecurityCheck(false, false, http.HandlerFunc(getNetworkUserData))).Methods("GET") - r.HandleFunc("/api/networkusers/{network}/{networkuser}", securityCheck(true, http.HandlerFunc(deleteNetworkUser))).Methods("DELETE") + r.HandleFunc("/api/networkusers", logic.SecurityCheck(true, http.HandlerFunc(getAllNetworkUsers))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkUsers))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}/{networkuser}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkUser))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}", logic.SecurityCheck(true, http.HandlerFunc(createNetworkUser))).Methods("POST") + r.HandleFunc("/api/networkusers/{network}", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkUser))).Methods("PUT") + r.HandleFunc("/api/networkusers/data/{networkuser}/me", logic.NetUserSecurityCheck(false, false, http.HandlerFunc(getNetworkUserData))).Methods("GET") + r.HandleFunc("/api/networkusers/{network}/{networkuser}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetworkUser))).Methods("DELETE") } // == RETURN TYPES == @@ -52,18 +52,18 @@ func getNetworkUserData(w http.ResponseWriter, r *http.Request) { networks, err := logic.GetNetworks() if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if networkUserName == "" { - returnErrorResponse(w, r, formatError(errors.New("netuserToGet"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("netuserToGet"), "badrequest")) return } u, err := logic.GetUser(networkUserName) if err != nil { - returnErrorResponse(w, r, formatError(errors.New("could not find user"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("could not find user"), "badrequest")) return } @@ -151,7 +151,7 @@ func getAllNetworkUsers(w http.ResponseWriter, r *http.Request) { networks, err := logic.GetNetworks() if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -160,7 +160,7 @@ func getAllNetworkUsers(w http.ResponseWriter, r *http.Request) { for i := range networks { netusers, err := pro.GetNetworkUsers(networks[i].NetID) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } for _, v := range netusers { @@ -181,13 +181,13 @@ func getNetworkUsers(w http.ResponseWriter, r *http.Request) { _, err := logic.GetNetwork(netname) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } netusers, err := pro.GetNetworkUsers(netname) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.WriteHeader(http.StatusOK) @@ -203,19 +203,19 @@ func getNetworkUser(w http.ResponseWriter, r *http.Request) { _, err := logic.GetNetwork(netname) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } netuserToGet := params["networkuser"] if netuserToGet == "" { - returnErrorResponse(w, r, formatError(errors.New("netuserToGet"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("netuserToGet"), "badrequest")) return } netuser, err := pro.GetNetworkUser(netname, promodels.NetworkUserID(netuserToGet)) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } w.WriteHeader(http.StatusOK) @@ -230,7 +230,7 @@ func createNetworkUser(w http.ResponseWriter, r *http.Request) { network, err := logic.GetNetwork(netname) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } var networkuser promodels.NetworkUser @@ -238,13 +238,13 @@ func createNetworkUser(w http.ResponseWriter, r *http.Request) { // we decode our body request params err = json.NewDecoder(r.Body).Decode(&networkuser) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = pro.CreateNetworkUser(&network, &networkuser) if err != nil { - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -260,7 +260,7 @@ func updateNetworkUser(w http.ResponseWriter, r *http.Request) { network, err := logic.GetNetwork(netname) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } var networkuser promodels.NetworkUser @@ -268,38 +268,38 @@ func updateNetworkUser(w http.ResponseWriter, r *http.Request) { // we decode our body request params err = json.NewDecoder(r.Body).Decode(&networkuser) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if networkuser.ID == "" || !pro.DoesNetworkUserExist(netname, networkuser.ID) { - returnErrorResponse(w, r, formatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest")) return } if networkuser.AccessLevel < pro.NET_ADMIN || networkuser.AccessLevel > pro.NO_ACCESS { - returnErrorResponse(w, r, formatError(errors.New("invalid user access level provided"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid user access level provided"), "badrequest")) return } if networkuser.ClientLimit < 0 || networkuser.NodeLimit < 0 { - returnErrorResponse(w, r, formatError(errors.New("negative user limit provided"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("negative user limit provided"), "badrequest")) return } u, err := logic.GetUser(string(networkuser.ID)) if err != nil { - returnErrorResponse(w, r, formatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest")) return } if !pro.IsUserAllowed(&network, u.UserName, u.Groups) { - returnErrorResponse(w, r, formatError(errors.New("user must be in allowed groups or users"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user must be in allowed groups or users"), "badrequest")) return } if networkuser.AccessLevel == pro.NET_ADMIN { currentUser, err := logic.GetUser(string(networkuser.ID)) if err != nil { - returnErrorResponse(w, r, formatError(errors.New("user model not found for "+string(networkuser.ID)), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user model not found for "+string(networkuser.ID)), "badrequest")) return } @@ -316,7 +316,7 @@ func updateNetworkUser(w http.ResponseWriter, r *http.Request) { UserName: currentUser.UserName, }, ); err != nil { - returnErrorResponse(w, r, formatError(errors.New("user model failed net admin update "+string(networkuser.ID)+" (are they an admin?"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user model failed net admin update "+string(networkuser.ID)+" (are they an admin?"), "badrequest")) return } } @@ -324,7 +324,7 @@ func updateNetworkUser(w http.ResponseWriter, r *http.Request) { err = pro.UpdateNetworkUser(netname, &networkuser) if err != nil { - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -340,18 +340,18 @@ func deleteNetworkUser(w http.ResponseWriter, r *http.Request) { _, err := logic.GetNetwork(netname) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } netuserToDelete := params["networkuser"] if netuserToDelete == "" { - returnErrorResponse(w, r, formatError(errors.New("no group name provided"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("no group name provided"), "badrequest")) return } if err := pro.DeleteNetworkUser(netname, netuserToDelete); err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } diff --git a/controllers/node.go b/controllers/node.go index 14075dbe..efc75d62 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -8,7 +8,6 @@ import ( "github.com/gorilla/mux" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/pro" @@ -30,8 +29,8 @@ func nodeHandlers(r *mux.Router) { r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods("DELETE") r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods("POST") r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods("DELETE") - r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", securityCheck(false, http.HandlerFunc(createIngressGateway))).Methods("POST") - r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", securityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods("DELETE") + r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, http.HandlerFunc(createIngressGateway))).Methods("POST") + r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods("DELETE") r.HandleFunc("/api/nodes/{network}/{nodeid}/approve", authorize(false, true, "user", http.HandlerFunc(uncordonNode))).Methods("POST") r.HandleFunc("/api/nodes/{network}", nodeauth(checkFreeTierLimits(node_l, http.HandlerFunc(createNode)))).Methods("POST") r.HandleFunc("/api/nodes/adm/{network}/lastmodified", authorize(false, true, "network", http.HandlerFunc(getLastModified))).Methods("GET") @@ -66,19 +65,19 @@ func authenticate(response http.ResponseWriter, request *http.Request) { errorResponse.Message = decoderErr.Error() logger.Log(0, request.Header.Get("user"), "error decoding request body: ", decoderErr.Error()) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } else { errorResponse.Code = http.StatusBadRequest if authRequest.ID == "" { errorResponse.Message = "W1R3: ID can't be empty" logger.Log(0, request.Header.Get("user"), errorResponse.Message) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } else if authRequest.Password == "" { errorResponse.Message = "W1R3: Password can't be empty" logger.Log(0, request.Header.Get("user"), errorResponse.Message) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } else { var err error @@ -89,7 +88,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) { errorResponse.Message = err.Error() logger.Log(0, request.Header.Get("user"), fmt.Sprintf("failed to get node info [%s]: %v", authRequest.ID, err)) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } @@ -99,7 +98,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) { errorResponse.Message = err.Error() logger.Log(0, request.Header.Get("user"), "error validating user password: ", err.Error()) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } else { tokenString, err := logic.CreateJWT(authRequest.ID, authRequest.MacAddress, result.Network) @@ -109,7 +108,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) { errorResponse.Message = "Could not create Token" logger.Log(0, request.Header.Get("user"), fmt.Sprintf("%s: %v", errorResponse.Message, err)) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } @@ -128,7 +127,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) { errorResponse.Message = err.Error() logger.Log(0, request.Header.Get("user"), "error marshalling resp: ", err.Error()) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } response.WriteHeader(http.StatusOK) @@ -149,7 +148,7 @@ func nodeauth(next http.Handler) http.HandlerFunc { errorResponse := models.ErrorResponse{ Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.", } - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } else { token = tokenSplit[1] @@ -161,7 +160,7 @@ func nodeauth(next http.Handler) http.HandlerFunc { errorResponse := models.ErrorResponse{ Code: http.StatusNotFound, Message: "no networks", } - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } for _, network := range networks { @@ -177,7 +176,7 @@ func nodeauth(next http.Handler) http.HandlerFunc { errorResponse := models.ErrorResponse{ Code: http.StatusUnauthorized, Message: "You are unauthorized to access this endpoint.", } - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } next.ServeHTTP(w, r) @@ -194,16 +193,16 @@ func nodeauth(next http.Handler) http.HandlerFunc { func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: unauthorized_msg, + Code: http.StatusUnauthorized, Message: logic.Unauthorized_Msg, } var params = mux.Vars(r) - networkexists, _ := functions.NetworkExists(params["network"]) + networkexists, _ := logic.NetworkExists(params["network"]) //check that the request is for a valid network //if (networkCheck && !networkexists) || err != nil { if networkCheck && !networkexists { - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } else { w.Header().Set("Content-Type", "application/json") @@ -220,7 +219,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha if len(tokenSplit) > 1 { authToken = tokenSplit[1] } else { - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } //check if node instead of user @@ -236,7 +235,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha var nodeID = "" username, networks, isadmin, errN := logic.VerifyUserToken(authToken) if errN != nil { - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } @@ -269,7 +268,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha } else { node, err := logic.GetNodeByID(nodeID) if err != nil { - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } isAuthorized = (node.Network == params["network"]) @@ -287,7 +286,7 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha } } if !isAuthorized { - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } else { //If authorized, this function passes along it's request and output to the appropriate route function. @@ -324,7 +323,7 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching nodes on network %s: %v", networkName, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -358,7 +357,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) { if err != nil && r.Header.Get("ismasterkey") != "yes" { logger.Log(0, r.Header.Get("user"), "error fetching user info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } var nodes []models.Node @@ -366,7 +365,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) { nodes, err = logic.GetAllNodes() if err != nil { logger.Log(0, "error fetching all nodes info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } else { @@ -374,7 +373,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), "error fetching nodes: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -418,7 +417,7 @@ func getNode(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -426,7 +425,7 @@ func getNode(w http.ResponseWriter, r *http.Request) { if err != nil && !database.IsEmptyRecord(err) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching wg peers config for node [ %s ]: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -470,7 +469,7 @@ func getLastModified(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching network [%s] info: %v", networkName, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "called last modified") @@ -498,12 +497,12 @@ func createNode(w http.ResponseWriter, r *http.Request) { Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.", } networkName := params["network"] - networkexists, err := functions.NetworkExists(networkName) + networkexists, err := logic.NetworkExists(networkName) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch network [%s] info: %v", networkName, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } else if !networkexists { errorResponse = models.ErrorResponse{ @@ -511,7 +510,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { } logger.Log(0, r.Header.Get("user"), fmt.Sprintf("network [%s] does not exist", networkName)) - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } @@ -521,7 +520,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(r.Body).Decode(&node) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -531,14 +530,14 @@ func createNode(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get network [%s] info: %v", node.Network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } node.NetworkSettings, err = logic.GetNetworkSettings(node.Network) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get network [%s] settings: %v", node.Network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } keyName, validKey := logic.IsKeyValid(networkName, node.AccessKey) @@ -554,7 +553,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to create node on network [%s]: %s", node.Network, errorResponse.Message)) - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } } @@ -569,17 +568,17 @@ func createNode(w http.ResponseWriter, r *http.Request) { key, keyErr := logic.RetrievePublicTrafficKey() if keyErr != nil { logger.Log(0, "error retrieving key: ", keyErr.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if key == nil { logger.Log(0, "error: server traffic key is nil") - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if node.TrafficKeys.Mine == nil { logger.Log(0, "error: node traffic key is nil") - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } node.TrafficKeys = models.TrafficKeys{ @@ -592,7 +591,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to create node on network [%s]: %s", node.Network, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -609,7 +608,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { if !updatedUserNode { // user was found but not updated, so delete node logger.Log(0, "failed to add node to user", keyName) logic.DeleteNodeByID(&node, true) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } @@ -618,7 +617,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { if err != nil && !database.IsEmptyRecord(err) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching wg peers config for node [ %s ]: %v", node.ID, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -656,7 +655,7 @@ func uncordonNode(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to uncordon node [%s]: %v", node.Name, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(1, r.Header.Get("user"), "uncordoned node", node.Name) @@ -686,7 +685,7 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&gateway) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } gateway.NetID = params["network"] @@ -696,7 +695,7 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to create egress gateway on node [%s] on network [%s]: %v", gateway.NodeID, gateway.NetID, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -728,7 +727,7 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete egress gateway on node [%s] on network [%s]: %v", nodeid, netid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -762,7 +761,7 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to create ingress gateway on node [%s] on network [%s]: %v", nodeid, netid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -794,7 +793,7 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete ingress gateway on node [%s] on network [%s]: %v", nodeid, netid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -828,7 +827,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -837,7 +836,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(r.Body).Decode(&newNode) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } relayupdate := false @@ -885,7 +884,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update node info [ %s ] info: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if relayupdate { @@ -932,20 +931,20 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if isServer(&node) { err := fmt.Errorf("cannot delete server node") logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete node [ %s ]: %v", nodeid, err)) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if r.Header.Get("ismaster") != "yes" { username := r.Header.Get("user") if username != "" && !doesUserOwnNode(username, params["network"], nodeid) { - returnErrorResponse(w, r, formatError(fmt.Errorf("user not permitted"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "badrequest")) return } } @@ -954,11 +953,11 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { err = logic.DeleteNodeByID(&node, false) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - returnSuccessResponse(w, r, nodeid+" deleted.") + logic.ReturnSuccessResponse(w, r, nodeid+" deleted.") logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"]) runUpdates(&node, false) diff --git a/controllers/relay.go b/controllers/relay.go index 28b95506..9f724e36 100644 --- a/controllers/relay.go +++ b/controllers/relay.go @@ -30,7 +30,7 @@ func createRelay(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&relay) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } relay.NetID = params["network"] @@ -39,7 +39,7 @@ func createRelay(w http.ResponseWriter, r *http.Request) { 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)) - returnErrorResponse(w, r, formatError(err, "internal")) + 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) @@ -73,7 +73,7 @@ func deleteRelay(w http.ResponseWriter, r *http.Request) { updatenodes, node, err := logic.DeleteRelay(netid, nodeid) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, r.Header.Get("user"), "deleted relay server", nodeid, "on network", netid) diff --git a/controllers/response_test.go b/controllers/response_test.go index b3948b4a..f2d8fd4d 100644 --- a/controllers/response_test.go +++ b/controllers/response_test.go @@ -7,12 +7,13 @@ import ( "net/http/httptest" "testing" + "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/stretchr/testify/assert" ) func TestFormatError(t *testing.T) { - response := formatError(errors.New("this is a sample error"), "badrequest") + response := logic.FormatError(errors.New("this is a sample error"), "badrequest") assert.Equal(t, http.StatusBadRequest, response.Code) assert.Equal(t, "this is a sample error", response.Message) } @@ -20,7 +21,7 @@ func TestFormatError(t *testing.T) { func TestReturnSuccessResponse(t *testing.T) { var response models.SuccessResponse handler := func(rw http.ResponseWriter, r *http.Request) { - returnSuccessResponse(rw, r, "This is a test message") + logic.ReturnSuccessResponse(rw, r, "This is a test message") } req := httptest.NewRequest(http.MethodGet, "http://example.com", nil) w := httptest.NewRecorder() @@ -42,7 +43,7 @@ func TestReturnErrorResponse(t *testing.T) { errMessage.Code = http.StatusUnauthorized errMessage.Message = "You are not authorized to access this endpoint" handler := func(rw http.ResponseWriter, r *http.Request) { - returnErrorResponse(rw, r, errMessage) + logic.ReturnErrorResponse(rw, r, errMessage) } req := httptest.NewRequest(http.MethodGet, "http://example.com", nil) w := httptest.NewRecorder() diff --git a/controllers/server.go b/controllers/server.go index 94e2d366..3c72198c 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/gorilla/mux" - "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" @@ -22,82 +21,35 @@ import ( func serverHandlers(r *mux.Router) { // r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(true, http.HandlerFunc(addNetwork))).Methods("POST") - r.HandleFunc("/api/server/getconfig", securityCheckServer(false, http.HandlerFunc(getConfig))).Methods("GET") - r.HandleFunc("/api/server/removenetwork/{network}", securityCheckServer(true, http.HandlerFunc(removeNetwork))).Methods("DELETE") + r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods("GET") r.HandleFunc("/api/server/register", authorize(true, false, "node", http.HandlerFunc(register))).Methods("POST") r.HandleFunc("/api/server/getserverinfo", authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods("GET") } -//Security check is middleware for every function and just checks to make sure that its the master calling -//Only admin should have access to all these network-level actions -//or maybe some Users once implemented -func securityCheckServer(adminonly bool, next http.Handler) http.HandlerFunc { +// allowUsers - allow all authenticated (valid) users - only used by getConfig, may be able to remove during refactor +func allowUsers(next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ - Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.", + Code: http.StatusInternalServerError, Message: logic.Unauthorized_Msg, } - bearerToken := r.Header.Get("Authorization") - var tokenSplit = strings.Split(bearerToken, " ") var authToken = "" if len(tokenSplit) < 2 { - errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.", - } - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } else { authToken = tokenSplit[1] } - //all endpoints here require master so not as complicated - //still might not be a good way of doing this - user, _, isadmin, err := logic.VerifyUserToken(authToken) - errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.", - } - if !adminonly && (err != nil || user == "") { - returnErrorResponse(w, r, errorResponse) - return - } - if adminonly && !isadmin && !authenticateMaster(authToken) { - returnErrorResponse(w, r, errorResponse) + user, _, _, err := logic.VerifyUserToken(authToken) + if err != nil || user == "" { + logic.ReturnErrorResponse(w, r, errorResponse) return } next.ServeHTTP(w, r) } } -// swagger:route DELETE /api/server/removenetwork/{network} nodes removeNetwork -// -// Remove a network from the server. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: stringJSONResponse -func removeNetwork(w http.ResponseWriter, r *http.Request) { - // Set header - w.Header().Set("Content-Type", "application/json") - - // get params - var params = mux.Vars(r) - network := params["network"] - err := logic.DeleteNetwork(network) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("failed to delete network [%s]: %v", network, err)) - json.NewEncoder(w).Encode(fmt.Sprintf("could not remove network %s from server", network)) - return - } - logger.Log(1, r.Header.Get("user"), - fmt.Sprintf("deleted network [%s]: %v", network, err)) - json.NewEncoder(w).Encode(fmt.Sprintf("network %s removed from server", network)) -} - // swagger:route GET /api/server/getserverinfo nodes getServerInfo // // Get the server configuration. @@ -138,7 +90,7 @@ func getConfig(w http.ResponseWriter, r *http.Request) { scfg := servercfg.GetServerConfig() scfg.IsEE = "no" - if ee.IsEnterprise() { + if logic.Is_EE { scfg.IsEE = "yes" } json.NewEncoder(w).Encode(scfg) @@ -166,7 +118,7 @@ func register(w http.ResponseWriter, r *http.Request) { errorResponse := models.ErrorResponse{ Code: http.StatusBadRequest, Message: err.Error(), } - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } cert, ca, err := genCerts(&request.Key, &request.CommonName) @@ -175,7 +127,7 @@ func register(w http.ResponseWriter, r *http.Request) { errorResponse := models.ErrorResponse{ Code: http.StatusNotFound, Message: err.Error(), } - returnErrorResponse(w, r, errorResponse) + logic.ReturnErrorResponse(w, r, errorResponse) return } //x509.Certificate.PublicKey is an interface therefore json encoding/decoding result in a string value rather than a []byte diff --git a/controllers/user.go b/controllers/user.go index 9c6dbb57..f53ab2f1 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -25,13 +25,13 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods("GET") r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods("POST") r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods("POST") - r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(updateUser)))).Methods("PUT") - r.HandleFunc("/api/users/networks/{username}", securityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods("PUT") - r.HandleFunc("/api/users/{username}/adm", securityCheck(true, http.HandlerFunc(updateUserAdm))).Methods("PUT") - r.HandleFunc("/api/users/{username}", securityCheck(true, checkFreeTierLimits(users_l, http.HandlerFunc(createUser)))).Methods("POST") - r.HandleFunc("/api/users/{username}", securityCheck(true, http.HandlerFunc(deleteUser))).Methods("DELETE") - r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(getUser)))).Methods("GET") - r.HandleFunc("/api/users", securityCheck(true, http.HandlerFunc(getUsers))).Methods("GET") + r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(updateUser)))).Methods("PUT") + r.HandleFunc("/api/users/networks/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods("PUT") + r.HandleFunc("/api/users/{username}/adm", logic.SecurityCheck(true, http.HandlerFunc(updateUserAdm))).Methods("PUT") + r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(users_l, http.HandlerFunc(createUser)))).Methods("POST") + r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods("DELETE") + r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods("GET") + r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods("GET") r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods("GET") r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods("GET") r.HandleFunc("/api/oauth/node-handler", socketHandler) @@ -59,7 +59,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { } if !servercfg.IsBasicAuthEnabled() { - returnErrorResponse(response, request, formatError(fmt.Errorf("basic auth is disabled"), "badrequest")) + logic.ReturnErrorResponse(response, request, logic.FormatError(fmt.Errorf("basic auth is disabled"), "badrequest")) return } @@ -69,7 +69,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { if decoderErr != nil { logger.Log(0, "error decoding request body: ", decoderErr.Error()) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } username := authRequest.UserName @@ -77,14 +77,14 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { if err != nil { logger.Log(0, username, "user validation failed: ", err.Error()) - returnErrorResponse(response, request, formatError(err, "badrequest")) + logic.ReturnErrorResponse(response, request, logic.FormatError(err, "badrequest")) return } if jwt == "" { // very unlikely that err is !nil and no jwt returned, but handle it anyways. logger.Log(0, username, "jwt token is empty") - returnErrorResponse(response, request, formatError(errors.New("no token returned"), "internal")) + logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("no token returned"), "internal")) return } @@ -102,7 +102,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { if jsonError != nil { logger.Log(0, username, "error marshalling resp: ", err.Error()) - returnErrorResponse(response, request, errorResponse) + logic.ReturnErrorResponse(response, request, errorResponse) return } logger.Log(2, username, "was authenticated") @@ -128,7 +128,7 @@ func hasAdmin(w http.ResponseWriter, r *http.Request) { hasadmin, err := logic.HasAdmin() if err != nil { logger.Log(0, "failed to check for admin: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -171,7 +171,7 @@ func getUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "fetched user", usernameFetched) @@ -197,7 +197,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, "failed to fetch users: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -226,12 +226,12 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { logger.Log(0, admin.UserName, "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if !servercfg.IsBasicAuthEnabled() { - returnErrorResponse(w, r, formatError(fmt.Errorf("basic auth is disabled"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("basic auth is disabled"), "badrequest")) return } @@ -239,7 +239,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, admin.UserName, "failed to create admin: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -266,7 +266,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, user.UserName, "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -274,7 +274,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, user.UserName, "error creating new user: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, user.UserName, "was created") @@ -302,7 +302,7 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "failed to update user networks: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } var userchange models.User @@ -311,7 +311,7 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } err = logic.UpdateUserNetworks(userchange.Networks, userchange.Groups, userchange.IsAdmin, &models.ReturnUser{ @@ -324,7 +324,7 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "failed to update user networks: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, username, "status was updated") @@ -352,13 +352,13 @@ func updateUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "failed to update user info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if auth.IsOauthUser(&user) == nil { err := fmt.Errorf("cannot update user info for oauth user %s", username) logger.Log(0, err.Error()) - returnErrorResponse(w, r, formatError(err, "forbidden")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) return } var userchange models.User @@ -367,7 +367,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } userchange.Networks = nil @@ -375,7 +375,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "failed to update user info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, username, "was updated") @@ -401,13 +401,13 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) { username := params["username"] user, err := GetUserInternal(username) if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } if auth.IsOauthUser(&user) != nil { err := fmt.Errorf("cannot update user info for oauth user %s", username) logger.Log(0, err.Error()) - returnErrorResponse(w, r, formatError(err, "forbidden")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) return } var userchange models.User @@ -416,18 +416,18 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "error decoding request body: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if !user.IsAdmin { logger.Log(0, username, "not an admin user") - returnErrorResponse(w, r, formatError(errors.New("not a admin user"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("not a admin user"), "badrequest")) } user, err = logic.UpdateUser(userchange, user) if err != nil { logger.Log(0, username, "failed to update user (admin) info: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, username, "was updated (admin)") @@ -458,12 +458,12 @@ func deleteUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Log(0, username, "failed to delete user: ", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } else if !success { err := errors.New("delete unsuccessful") logger.Log(0, username, err.Error()) - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } diff --git a/controllers/usergroups.go b/controllers/usergroups.go index a73dc1f8..4ade6f29 100644 --- a/controllers/usergroups.go +++ b/controllers/usergroups.go @@ -3,18 +3,20 @@ package controller import ( "encoding/json" "errors" - "github.com/gravitl/netmaker/logger" "net/http" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gorilla/mux" "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models/promodels" ) func userGroupsHandlers(r *mux.Router) { - r.HandleFunc("/api/usergroups", securityCheck(true, http.HandlerFunc(getUserGroups))).Methods("GET") - r.HandleFunc("/api/usergroups/{usergroup}", securityCheck(true, http.HandlerFunc(createUserGroup))).Methods("POST") - r.HandleFunc("/api/usergroups/{usergroup}", securityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods("DELETE") + r.HandleFunc("/api/usergroups", logic.SecurityCheck(true, http.HandlerFunc(getUserGroups))).Methods("GET") + r.HandleFunc("/api/usergroups/{usergroup}", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods("POST") + r.HandleFunc("/api/usergroups/{usergroup}", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods("DELETE") } func getUserGroups(w http.ResponseWriter, r *http.Request) { @@ -23,7 +25,7 @@ func getUserGroups(w http.ResponseWriter, r *http.Request) { userGroups, err := pro.GetUserGroups() if err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } // Returns all the groups in JSON format @@ -39,13 +41,13 @@ func createUserGroup(w http.ResponseWriter, r *http.Request) { logger.Log(1, r.Header.Get("user"), "requested creating user group", newGroup) if newGroup == "" { - returnErrorResponse(w, r, formatError(errors.New("no group name provided"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("no group name provided"), "badrequest")) return } err := pro.InsertUserGroup(promodels.UserGroupName(newGroup)) if err != nil { - returnErrorResponse(w, r, formatError(err, "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -58,12 +60,12 @@ func deleteUserGroup(w http.ResponseWriter, r *http.Request) { logger.Log(1, r.Header.Get("user"), "requested deleting user group", groupToDelete) if groupToDelete == "" { - returnErrorResponse(w, r, formatError(errors.New("no group name provided"), "badrequest")) + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("no group name provided"), "badrequest")) return } if err := pro.DeleteUserGroup(promodels.UserGroupName(groupToDelete)); err != nil { - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } diff --git a/controllers/metrics.go b/ee/ee_controllers/metrics.go similarity index 81% rename from controllers/metrics.go rename to ee/ee_controllers/metrics.go index 1c08350f..409682a2 100644 --- a/controllers/metrics.go +++ b/ee/ee_controllers/metrics.go @@ -1,4 +1,4 @@ -package controller +package ee_controllers import ( "encoding/json" @@ -10,10 +10,11 @@ import ( "github.com/gravitl/netmaker/models" ) -func metricHandlers(r *mux.Router) { - r.HandleFunc("/api/metrics/{network}/{nodeid}", securityCheck(true, http.HandlerFunc(getNodeMetrics))).Methods("GET") - r.HandleFunc("/api/metrics/{network}", securityCheck(true, http.HandlerFunc(getNetworkNodesMetrics))).Methods("GET") - r.HandleFunc("/api/metrics", securityCheck(true, http.HandlerFunc(getAllMetrics))).Methods("GET") +// MetricHandlers - How we handle EE Metrics +func MetricHandlers(r *mux.Router) { + r.HandleFunc("/api/metrics/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(getNodeMetrics))).Methods("GET") + r.HandleFunc("/api/metrics/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodesMetrics))).Methods("GET") + r.HandleFunc("/api/metrics", logic.SecurityCheck(true, http.HandlerFunc(getAllMetrics))).Methods("GET") } // get the metrics of a given node @@ -28,7 +29,7 @@ func getNodeMetrics(w http.ResponseWriter, r *http.Request) { metrics, err := logic.GetMetrics(nodeID) if err != nil { logger.Log(1, r.Header.Get("user"), "failed to fetch metrics of node", nodeID, err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -49,7 +50,7 @@ func getNetworkNodesMetrics(w http.ResponseWriter, r *http.Request) { networkNodes, err := logic.GetNetworkNodes(network) if err != nil { logger.Log(1, r.Header.Get("user"), "failed to fetch metrics of all nodes in network", network, err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -79,7 +80,7 @@ func getAllMetrics(w http.ResponseWriter, r *http.Request) { allNodes, err := logic.GetAllNodes() if err != nil { logger.Log(1, r.Header.Get("user"), "failed to fetch metrics of all nodes on server", err.Error()) - returnErrorResponse(w, r, formatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } diff --git a/ee/initialize.go b/ee/initialize.go new file mode 100644 index 00000000..665e7729 --- /dev/null +++ b/ee/initialize.go @@ -0,0 +1,54 @@ +//go:build ee +// +build ee + +package ee + +import ( + controller "github.com/gravitl/netmaker/controllers" + "github.com/gravitl/netmaker/ee/ee_controllers" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" +) + +// InitEE - Initialize EE Logic +func InitEE() { + SetIsEnterprise() + models.SetLogo(retrieveEELogo()) + controller.HttpHandlers = append(controller.HttpHandlers, ee_controllers.MetricHandlers) + logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() { + // == License Handling == + ValidateLicense() + if Limits.FreeTier { + logger.Log(0, "proceeding with Free Tier license") + } else { + logger.Log(0, "proceeding with Paid Tier license") + } + // == End License Handling == + AddLicenseHooks() + }) +} + +func setControllerLimits() { + logic.Node_Limit = Limits.Nodes + logic.Users_Limit = Limits.Users + logic.Clients_Limit = Limits.Clients + logic.Free_Tier = Limits.FreeTier + logic.Is_EE = true +} + +func retrieveEELogo() string { + return ` + __ __ ______ ______ __ __ ______ __ __ ______ ______ +/\ "-.\ \ /\ ___\ /\__ _\ /\ "-./ \ /\ __ \ /\ \/ / /\ ___\ /\ == \ +\ \ \-. \ \ \ __\ \/_/\ \/ \ \ \-./\ \ \ \ __ \ \ \ _"-. \ \ __\ \ \ __< + \ \_\\"\_\ \ \_____\ \ \_\ \ \_\ \ \_\ \ \_\ \_\ \ \_\ \_\ \ \_____\ \ \_\ \_\ + \/_/ \/_/ \/_____/ \/_/ \/_/ \/_/ \/_/\/_/ \/_/\/_/ \/_____/ \/_/ /_/ + + ___ ___ ____ + ____ ____ ____ / _ \ / _ \ / __ \ ____ ____ ____ + /___/ /___/ /___/ / ___/ / , _// /_/ / /___/ /___/ /___/ + /___/ /___/ /___/ /_/ /_/|_| \____/ /___/ /___/ /___/ + +` +} diff --git a/ee/license.go b/ee/license.go index f34e50f8..ff4d291c 100644 --- a/ee/license.go +++ b/ee/license.go @@ -1,7 +1,11 @@ +//go:build ee +// +build ee + package ee import ( "bytes" + "crypto/rand" "encoding/json" "fmt" "io/ioutil" @@ -11,11 +15,20 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" - "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/servercfg" + "golang.org/x/crypto/nacl/box" ) +const ( + db_license_key = "netmaker-id-key-pair" +) + +type apiServerConf struct { + PrivateKey []byte `json:"private_key" binding:"required"` + PublicKey []byte `json:"public_key" binding:"required"` +} + // AddLicenseHooks - adds the validation and cache clear hooks func AddLicenseHooks() { logic.AddHook(ValidateLicense) @@ -39,7 +52,7 @@ func ValidateLicense() error { logger.FatalLog(errValidation.Error()) } - tempPubKey, tempPrivKey, err := pro.FetchApiServerKeys() + tempPubKey, tempPrivKey, err := FetchApiServerKeys() if err != nil { logger.FatalLog(errValidation.Error()) } @@ -88,11 +101,59 @@ func ValidateLicense() error { if Limits.FreeTier { Limits.Networks = 3 } + setControllerLimits() logger.Log(0, "License validation succeeded!") return nil } +// FetchApiServerKeys - fetches netmaker license keys for identification +// as well as secure communication with API +// if none present, it generates a new pair +func FetchApiServerKeys() (pub *[32]byte, priv *[32]byte, err error) { + var returnData = apiServerConf{} + currentData, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, db_license_key) + if err != nil && !database.IsEmptyRecord(err) { + return nil, nil, err + } else if database.IsEmptyRecord(err) { // need to generate a new identifier pair + pub, priv, err = box.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + pubBytes, err := ncutils.ConvertKeyToBytes(pub) + if err != nil { + return nil, nil, err + } + privBytes, err := ncutils.ConvertKeyToBytes(priv) + if err != nil { + return nil, nil, err + } + returnData.PrivateKey = privBytes + returnData.PublicKey = pubBytes + record, err := json.Marshal(&returnData) + if err != nil { + return nil, nil, err + } + if err = database.Insert(db_license_key, string(record), database.SERVERCONF_TABLE_NAME); err != nil { + return nil, nil, err + } + } else { + if err = json.Unmarshal([]byte(currentData), &returnData); err != nil { + return nil, nil, err + } + priv, err = ncutils.ConvertBytesToKey(returnData.PrivateKey) + if err != nil { + return nil, nil, err + } + pub, err = ncutils.ConvertBytesToKey(returnData.PublicKey) + if err != nil { + return nil, nil, err + } + } + + return pub, priv, nil +} + func getLicensePublicKey(licensePubKeyEncoded string) (*[32]byte, error) { decodedPubKey := base64decode(licensePubKeyEncoded) return ncutils.ConvertBytesToKey(decodedPubKey) @@ -179,32 +240,6 @@ func ClearLicenseCache() error { return database.DeleteRecord(database.CACHE_TABLE_NAME, license_cache_key) } -// AddServerIDIfNotPresent - add's current server ID to DB if not present -func AddServerIDIfNotPresent() error { - currentNodeID := servercfg.GetNodeID() - currentServerIDs := serverIDs{} - - record, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, server_id_key) - if err != nil && !database.IsEmptyRecord(err) { - return err - } else if err == nil { - if err = json.Unmarshal([]byte(record), ¤tServerIDs); err != nil { - return err - } - } - - if !logic.StringSliceContains(currentServerIDs.ServerIDs, currentNodeID) { - currentServerIDs.ServerIDs = append(currentServerIDs.ServerIDs, currentNodeID) - data, err := json.Marshal(¤tServerIDs) - if err != nil { - return err - } - return database.Insert(server_id_key, string(data), database.SERVERCONF_TABLE_NAME) - } - - return nil -} - func getServerCount() int { if record, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, server_id_key); err == nil { currentServerIDs := serverIDs{} diff --git a/ee/util.go b/ee/util.go index 26e6262f..90705a0b 100644 --- a/ee/util.go +++ b/ee/util.go @@ -49,6 +49,6 @@ func getCurrentServerLimit() (limits LicenseLimits) { if err == nil { limits.Users = len(users) } - limits.Servers = getServerCount() + limits.Servers = logic.GetServerCount() return } diff --git a/functions/helpers.go b/functions/helpers.go index e455d088..d45d3292 100644 --- a/functions/helpers.go +++ b/functions/helpers.go @@ -8,17 +8,6 @@ import ( "github.com/gravitl/netmaker/models" ) -// NetworkExists - check if network exists -func NetworkExists(name string) (bool, error) { - - var network string - var err error - if network, err = database.FetchRecord(database.NETWORKS_TABLE_NAME, name); err != nil { - return false, err - } - return len(network) > 0, nil -} - // NameInDNSCharSet - name in dns char set func NameInDNSCharSet(name string) bool { diff --git a/functions/helpers_test.go b/functions/helpers_test.go index 601747c3..e2737f48 100644 --- a/functions/helpers_test.go +++ b/functions/helpers_test.go @@ -26,7 +26,7 @@ func TestNetworkExists(t *testing.T) { } database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID) defer database.CloseDB() - exists, err := NetworkExists(testNetwork.NetID) + exists, err := logic.NetworkExists(testNetwork.NetID) if err == nil { t.Fatalf("expected error, received nil") } @@ -38,7 +38,7 @@ func TestNetworkExists(t *testing.T) { if err != nil { t.Fatalf("failed to save test network in databse: %s", err) } - exists, err = NetworkExists(testNetwork.NetID) + exists, err = logic.NetworkExists(testNetwork.NetID) if err != nil { t.Fatalf("expected nil, received err: %s", err) } diff --git a/logic/auth.go b/logic/auth.go index f282d534..fb99ba7e 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -99,7 +99,7 @@ func CreateUser(user models.User) (models.User, error) { tokenString, _ := CreateProUserJWT(user.UserName, user.Networks, user.Groups, user.IsAdmin) if tokenString == "" { - // returnErrorResponse(w, r, errorResponse) + // logic.ReturnErrorResponse(w, r, errorResponse) return user, err } diff --git a/controllers/response.go b/logic/errors.go similarity index 77% rename from controllers/response.go rename to logic/errors.go index 5783c002..8259d586 100644 --- a/controllers/response.go +++ b/logic/errors.go @@ -1,4 +1,4 @@ -package controller +package logic import ( "encoding/json" @@ -8,7 +8,8 @@ import ( "github.com/gravitl/netmaker/models" ) -func formatError(err error, errType string) models.ErrorResponse { +// FormatError - takes ErrorResponse and uses correct code +func FormatError(err error, errType string) models.ErrorResponse { var status = http.StatusInternalServerError switch errType { @@ -33,7 +34,8 @@ func formatError(err error, errType string) models.ErrorResponse { return response } -func returnSuccessResponse(response http.ResponseWriter, request *http.Request, message string) { +// ReturnSuccessResponse - processes message and adds header +func ReturnSuccessResponse(response http.ResponseWriter, request *http.Request, message string) { var httpResponse models.SuccessResponse httpResponse.Code = http.StatusOK httpResponse.Message = message @@ -42,7 +44,8 @@ func returnSuccessResponse(response http.ResponseWriter, request *http.Request, json.NewEncoder(response).Encode(httpResponse) } -func returnErrorResponse(response http.ResponseWriter, request *http.Request, errorMessage models.ErrorResponse) { +// ReturnErrorResponse - processes error and adds header +func ReturnErrorResponse(response http.ResponseWriter, request *http.Request, errorMessage models.ErrorResponse) { httpResponse := &models.ErrorResponse{Code: errorMessage.Code, Message: errorMessage.Message} jsonResponse, err := json.Marshal(httpResponse) if err != nil { diff --git a/logic/pro/metrics/metrics.go b/logic/metrics/metrics.go similarity index 100% rename from logic/pro/metrics/metrics.go rename to logic/metrics/metrics.go diff --git a/logic/networks.go b/logic/networks.go index f82f22a0..2103a8fa 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -96,7 +96,7 @@ func CreateNetwork(network models.Network) (models.Network, error) { err := ValidateNetwork(&network, false) if err != nil { - //returnErrorResponse(w, r, formatError(err, "badrequest")) + //logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return models.Network{}, err } @@ -656,6 +656,17 @@ func SaveNetwork(network *models.Network) error { return nil } +// NetworkExists - check if network exists +func NetworkExists(name string) (bool, error) { + + var network string + var err error + if network, err = database.FetchRecord(database.NETWORKS_TABLE_NAME, name); err != nil { + return false, err + } + return len(network) > 0, nil +} + // == Private == func networkNodesUpdateAction(networkName string, action string) error { diff --git a/logic/nodes.go b/logic/nodes.go index 7b0f6fe9..1b3584c4 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -311,7 +311,7 @@ func CreateNode(node *models.Node) error { //Create a JWT for the node tokenString, _ := CreateJWT(node.ID, node.MacAddress, node.Network) if tokenString == "" { - //returnErrorResponse(w, r, errorResponse) + //logic.ReturnErrorResponse(w, r, errorResponse) return err } err = ValidateNode(node, false) diff --git a/logic/pro/license.go b/logic/pro/license.go deleted file mode 100644 index 2ca96d50..00000000 --- a/logic/pro/license.go +++ /dev/null @@ -1,66 +0,0 @@ -package pro - -import ( - "crypto/rand" - "encoding/json" - - "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/netclient/ncutils" - "golang.org/x/crypto/nacl/box" -) - -const ( - db_license_key = "netmaker-id-key-pair" -) - -type apiServerConf struct { - PrivateKey []byte `json:"private_key" binding:"required"` - PublicKey []byte `json:"public_key" binding:"required"` -} - -// FetchApiServerKeys - fetches netmaker license keys for identification -// as well as secure communication with API -// if none present, it generates a new pair -func FetchApiServerKeys() (pub *[32]byte, priv *[32]byte, err error) { - var returnData = apiServerConf{} - currentData, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, db_license_key) - if err != nil && !database.IsEmptyRecord(err) { - return nil, nil, err - } else if database.IsEmptyRecord(err) { // need to generate a new identifier pair - pub, priv, err = box.GenerateKey(rand.Reader) - if err != nil { - return nil, nil, err - } - pubBytes, err := ncutils.ConvertKeyToBytes(pub) - if err != nil { - return nil, nil, err - } - privBytes, err := ncutils.ConvertKeyToBytes(priv) - if err != nil { - return nil, nil, err - } - returnData.PrivateKey = privBytes - returnData.PublicKey = pubBytes - record, err := json.Marshal(&returnData) - if err != nil { - return nil, nil, err - } - if err = database.Insert(db_license_key, string(record), database.SERVERCONF_TABLE_NAME); err != nil { - return nil, nil, err - } - } else { - if err = json.Unmarshal([]byte(currentData), &returnData); err != nil { - return nil, nil, err - } - priv, err = ncutils.ConvertBytesToKey(returnData.PrivateKey) - if err != nil { - return nil, nil, err - } - pub, err = ncutils.ConvertBytesToKey(returnData.PublicKey) - if err != nil { - return nil, nil, err - } - } - - return pub, priv, nil -} diff --git a/logic/pro/networks_test.go b/logic/pro/networks_test.go index 68915a3b..2f674e03 100644 --- a/logic/pro/networks_test.go +++ b/logic/pro/networks_test.go @@ -58,7 +58,7 @@ func TestNetworkProSettings(t *testing.T) { } AddProNetDefaults(&network) assert.NotNil(t, network.ProSettings) - assert.Nil(t, network.ProSettings.AllowedGroups) - assert.Nil(t, network.ProSettings.AllowedUsers) + assert.Equal(t, len(network.ProSettings.AllowedGroups), 1) + assert.Equal(t, len(network.ProSettings.AllowedUsers), 0) }) } diff --git a/controllers/security.go b/logic/security.go similarity index 71% rename from controllers/security.go rename to logic/security.go index f793da5d..2f013804 100644 --- a/controllers/security.go +++ b/logic/security.go @@ -1,4 +1,4 @@ -package controller +package logic import ( "encoding/json" @@ -7,8 +7,6 @@ import ( "github.com/gorilla/mux" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/functions" - "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/pro" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/models/promodels" @@ -16,16 +14,20 @@ import ( ) const ( + // ALL_NETWORK_ACCESS - represents all networks + ALL_NETWORK_ACCESS = "THIS_USER_HAS_ALL" + master_uname = "masteradministrator" - unauthorized_msg = "unauthorized" - unauthorized_err = models.Error(unauthorized_msg) + Unauthorized_Msg = "unauthorized" + Unauthorized_Err = models.Error(Unauthorized_Msg) ) -func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { +// SecurityCheck - Check if user has appropriate permissions +func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: unauthorized_msg, + Code: http.StatusUnauthorized, Message: Unauthorized_Msg, } var params = mux.Vars(r) @@ -44,14 +46,14 @@ func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { if len(networkName) == 0 { networkName = params["network"] } - networks, username, err := SecurityCheck(reqAdmin, networkName, bearerToken) + networks, username, err := UserPermissions(reqAdmin, networkName, bearerToken) if err != nil { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } networksJson, err := json.Marshal(&networks) if err != nil { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } r.Header.Set("user", username) @@ -60,7 +62,8 @@ func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { } } -func netUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.HandlerFunc { +// NetUserSecurityCheck - Check if network user has appropriate permissions +func NetUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ Code: http.StatusUnauthorized, Message: "unauthorized", @@ -77,7 +80,7 @@ func netUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.Handl var authToken = "" if len(tokenSplit) < 2 { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } else { authToken = tokenSplit[1] @@ -91,9 +94,9 @@ func netUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.Handl return } - userName, _, isadmin, err := logic.VerifyUserToken(authToken) + userName, _, isadmin, err := VerifyUserToken(authToken) if err != nil { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } r.Header.Set("user", userName) @@ -113,15 +116,15 @@ func netUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.Handl } u, err := pro.GetNetworkUser(network, promodels.NetworkUserID(userName)) if err != nil { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } if u.AccessLevel > necessaryAccess { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } } else if netUserName != userName { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } @@ -129,14 +132,14 @@ func netUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.Handl } } -// SecurityCheck - checks token stuff -func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, string, error) { +// UserPermissions - checks token stuff +func UserPermissions(reqAdmin bool, netname string, token string) ([]string, string, error) { var tokenSplit = strings.Split(token, " ") var authToken = "" userNetworks := []string{} if len(tokenSplit) < 2 { - return userNetworks, "", unauthorized_err + return userNetworks, "", Unauthorized_Err } else { authToken = tokenSplit[1] } @@ -144,12 +147,12 @@ func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, strin if authenticateMaster(authToken) { return []string{ALL_NETWORK_ACCESS}, master_uname, nil } - username, networks, isadmin, err := logic.VerifyUserToken(authToken) + username, networks, isadmin, err := VerifyUserToken(authToken) if err != nil { - return nil, username, unauthorized_err + return nil, username, Unauthorized_Err } if !isadmin && reqAdmin { - return nil, username, unauthorized_err + return nil, username, Unauthorized_Err } userNetworks = networks if isadmin { @@ -157,10 +160,10 @@ func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, strin } // check network admin access if len(netname) > 0 && (!authenticateNetworkUser(netname, userNetworks) || len(userNetworks) == 0) { - return nil, username, unauthorized_err + return nil, username, Unauthorized_Err } if !pro.IsUserNetAdmin(netname, username) { - return nil, "", unauthorized_err + return nil, "", Unauthorized_Err } return userNetworks, username, nil } @@ -171,11 +174,11 @@ func authenticateMaster(tokenString string) bool { } func authenticateNetworkUser(network string, userNetworks []string) bool { - networkexists, err := functions.NetworkExists(network) + networkexists, err := NetworkExists(network) if (err != nil && !database.IsEmptyRecord(err)) || !networkexists { return false } - return logic.StringSliceContains(userNetworks, network) + return StringSliceContains(userNetworks, network) } //Consider a more secure way of setting master key @@ -187,15 +190,15 @@ func authenticateDNSToken(tokenString string) bool { return tokens[1] == servercfg.GetDNSKey() } -func continueIfUserMatch(next http.Handler) http.HandlerFunc { +func ContinueIfUserMatch(next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: unauthorized_msg, + Code: http.StatusUnauthorized, Message: Unauthorized_Msg, } var params = mux.Vars(r) var requestedUser = params["username"] if requestedUser != r.Header.Get("user") { - returnErrorResponse(w, r, errorResponse) + ReturnErrorResponse(w, r, errorResponse) return } next.ServeHTTP(w, r) diff --git a/logic/server.go b/logic/server.go index 56897351..6e892bfb 100644 --- a/logic/server.go +++ b/logic/server.go @@ -18,6 +18,8 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) +var EnterpriseCheckFuncs []interface{} + // == Join, Checkin, and Leave for Server == // KUBERNETES_LISTEN_PORT - starting port for Kubernetes in order to use NodePort range @@ -164,6 +166,13 @@ func ServerJoin(networkSettings *models.Network) (models.Node, error) { return *node, nil } +// EnterpriseCheck - Runs enterprise functions if presented +func EnterpriseCheck() { + for _, check := range EnterpriseCheckFuncs { + check.(func())() + } +} + // ServerUpdate - updates the server // replaces legacy Checkin code func ServerUpdate(serverNode *models.Node, ifaceDelta bool) error { diff --git a/logic/serverconf.go b/logic/serverconf.go index 68469663..fbd5faf5 100644 --- a/logic/serverconf.go +++ b/logic/serverconf.go @@ -6,6 +6,21 @@ import ( "github.com/gravitl/netmaker/database" ) +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 + // Free_Tier - specifies if free tier + Free_Tier = false + // Is_EE - specifies if enterprise + Is_EE = false +) + // constant for database key for storing server ids const server_id_key = "nm-server-id" diff --git a/main.go b/main.go index 5c50e0b0..a82a07f8 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,6 @@ import ( "github.com/gravitl/netmaker/config" controller "github.com/gravitl/netmaker/controllers" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -76,7 +75,7 @@ func initialize() { // Client Mode Prereq Check logger.FatalLog("Error connecting to database") } logger.Log(0, "database successfully connected") - if err = ee.AddServerIDIfNotPresent(); err != nil { + if err = logic.AddServerIDIfNotPresent(); err != nil { logger.Log(1, "failed to save server ID") } @@ -91,18 +90,7 @@ func initialize() { // Client Mode Prereq Check logger.Log(1, "Timer error occurred: ", err.Error()) } - if ee.IsEnterprise() { - // == License Handling == - ee.ValidateLicense() - if ee.Limits.FreeTier { - logger.Log(0, "proceeding with Free Tier license") - } else { - logger.Log(0, "proceeding with Paid Tier license") - } - // == End License Handling == - - ee.AddLicenseHooks() - } + logic.EnterpriseCheck() var authProvider = auth.InitializeAuthProvider() if authProvider != "" { diff --git a/main_ee.go b/main_ee.go index ba40a39a..dd4bb3a4 100644 --- a/main_ee.go +++ b/main_ee.go @@ -5,26 +5,8 @@ package main import ( "github.com/gravitl/netmaker/ee" - "github.com/gravitl/netmaker/models" ) func init() { - ee.SetIsEnterprise() - models.SetLogo(retrieveEELogo()) -} - -func retrieveEELogo() string { - return ` - __ __ ______ ______ __ __ ______ __ __ ______ ______ -/\ "-.\ \ /\ ___\ /\__ _\ /\ "-./ \ /\ __ \ /\ \/ / /\ ___\ /\ == \ -\ \ \-. \ \ \ __\ \/_/\ \/ \ \ \-./\ \ \ \ __ \ \ \ _"-. \ \ __\ \ \ __< - \ \_\\"\_\ \ \_____\ \ \_\ \ \_\ \ \_\ \ \_\ \_\ \ \_\ \_\ \ \_____\ \ \_\ \_\ - \/_/ \/_/ \/_____/ \/_/ \/_/ \/_/ \/_/\/_/ \/_/\/_/ \/_____/ \/_/ /_/ - - ___ ___ ____ - ____ ____ ____ / _ \ / _ \ / __ \ ____ ____ ____ - /___/ /___/ /___/ / ___/ / , _// /_/ / /___/ /___/ /___/ - /___/ /___/ /___/ /_/ /_/|_| \____/ /___/ /___/ /___/ - -` + ee.InitEE() } diff --git a/mq/handlers.go b/mq/handlers.go index 99695eb7..822955a3 100644 --- a/mq/handlers.go +++ b/mq/handlers.go @@ -7,7 +7,6 @@ import ( mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" @@ -99,7 +98,7 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) { // UpdateMetrics message Handler -- handles updates from client nodes for metrics func UpdateMetrics(client mqtt.Client, msg mqtt.Message) { - if ee.IsEnterprise() { + if logic.Is_EE { go func() { id, err := getID(msg.Topic()) if err != nil { diff --git a/mq/publishers.go b/mq/publishers.go index fd48529f..9e6cc521 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -6,10 +6,9 @@ import ( "fmt" "time" - "github.com/gravitl/netmaker/ee" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" - "github.com/gravitl/netmaker/logic/pro/metrics" + "github.com/gravitl/netmaker/logic/metrics" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" "github.com/gravitl/netmaker/serverctl" @@ -185,7 +184,7 @@ func ServerStartNotify() error { // function to collect and store metrics for server nodes func collectServerMetrics(networks []models.Network) { - if !ee.IsEnterprise() { + if !logic.Is_EE { return } if len(networks) > 0 { diff --git a/netclient/functions/mqpublish.go b/netclient/functions/mqpublish.go index 5f1fdae6..766c19ed 100644 --- a/netclient/functions/mqpublish.go +++ b/netclient/functions/mqpublish.go @@ -15,7 +15,7 @@ import ( "github.com/cloverstd/tcping/ping" "github.com/gravitl/netmaker/logger" - "github.com/gravitl/netmaker/logic/pro/metrics" + "github.com/gravitl/netmaker/logic/metrics" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/auth" "github.com/gravitl/netmaker/netclient/config" From b5a3ad7dbd032bb035e3e94c54411cb5cc31373b Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 14:33:08 -0400 Subject: [PATCH 18/52] add * to allowed groups by default --- Dockerfile | 7 +++++-- logic/networks.go | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 35f1f052..d45080aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,14 @@ #first stage - builder FROM gravitl/go-builder as builder -ARG version +ARG version +ARG tags WORKDIR /app COPY . . ENV GO111MODULE=auto -RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -X 'main.version=${version}'" -o netmaker main.go +RUN apk add git +RUN GOOS=linux CGO_ENABLED=1 go build ${tags} -ldflags="-s -X 'main.version=${version}'" . +# RUN go build -tags=ee . -o netmaker main.go FROM alpine:3.15.2 # add a c lib diff --git a/logic/networks.go b/logic/networks.go index 2103a8fa..6e36372d 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -88,12 +88,17 @@ func CreateNetwork(network models.Network) (models.Network, error) { } network.AddressRange6 = normalizedRange } + network.SetDefaults() network.SetNodesLastModified() network.SetNetworkLastModified() pro.AddProNetDefaults(&network) + if len(network.ProSettings.AllowedGroups) == 0 { + network.ProSettings.AllowedGroups = []string{pro.DEFAULT_ALLOWED_GROUPS} + } + err := ValidateNetwork(&network, false) if err != nil { //logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) From 5916dffb91bc1fb06579c1af1e42ba22622a3ef4 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 14:44:52 -0400 Subject: [PATCH 19/52] fix update user --- serverctl/serverctl.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index b81b95ea..2be0d88a 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -151,7 +151,9 @@ func setUserDefaults() error { logger.Log(0, "could not update user", updateUser.UserName) } logic.SetUserDefaults(&updateUser) - if _, err = logic.UpdateUser(updateUser, updateUser); err != nil { + copyUser := updateUser + copyUser.Password = "" + if _, err = logic.UpdateUser(copyUser, updateUser); err != nil { logger.Log(0, "could not update user", updateUser.UserName) } } From 57f93c8b433b0ea30d0d04604122c68d7b96764c Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 14:54:20 -0400 Subject: [PATCH 20/52] fix get user data --- controllers/networkusers.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/controllers/networkusers.go b/controllers/networkusers.go index 8ac4e24f..444ea499 100644 --- a/controllers/networkusers.go +++ b/controllers/networkusers.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/pro" @@ -98,7 +99,11 @@ func getNetworkUserData(w http.ResponseWriter, r *http.Request) { if doesNetworkAllow := pro.IsUserAllowed(&networks[i], networkUserName, u.Groups); doesNetworkAllow || netUser.AccessLevel == pro.NET_ADMIN { netNodes, err := logic.GetNetworkNodes(netID) if err != nil { - logger.Log(0, "failed to retrieve nodes on network", netID, "for user", string(netUser.ID)) + if database.IsEmptyRecord(err) && netUser.AccessLevel == pro.NET_ADMIN { + newData.Networks = append(newData.Networks, networks[i]) + } else { + logger.Log(0, "failed to retrieve nodes on network", netID, "for user", string(netUser.ID)) + } } else { if netUser.AccessLevel <= pro.NODE_ACCESS { // handle nodes // if access level is NODE_ACCESS, filter nodes From 255e400be84069e3be8484ac77b316f77654e91f Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 15:08:38 -0400 Subject: [PATCH 21/52] set NetworkUser defaults --- controllers/networkusers.go | 1 + logic/pro/networkuser.go | 2 +- models/promodels/networkuser.go | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/controllers/networkusers.go b/controllers/networkusers.go index 444ea499..d2307e26 100644 --- a/controllers/networkusers.go +++ b/controllers/networkusers.go @@ -95,6 +95,7 @@ func getNetworkUserData(w http.ResponseWriter, r *http.Request) { Nodes: netUser.Nodes, Clients: netUser.Clients, } + newData.User.SetDefaults() // check network level permissions if doesNetworkAllow := pro.IsUserAllowed(&networks[i], networkUserName, u.Groups); doesNetworkAllow || netUser.AccessLevel == pro.NET_ADMIN { netNodes, err := logic.GetNetworkNodes(netID) diff --git a/logic/pro/networkuser.go b/logic/pro/networkuser.go index 1811cc47..0cf8d95b 100644 --- a/logic/pro/networkuser.go +++ b/logic/pro/networkuser.go @@ -49,7 +49,7 @@ func CreateNetworkUser(network *models.Network, user *promodels.NetworkUser) err if err != nil { return err } - + user.SetDefaults() currentUsers.Add(user) data, err := json.Marshal(currentUsers) if err != nil { diff --git a/models/promodels/networkuser.go b/models/promodels/networkuser.go index a6865335..ac34a1c1 100644 --- a/models/promodels/networkuser.go +++ b/models/promodels/networkuser.go @@ -25,3 +25,13 @@ func (N NetworkUserMap) Delete(ID NetworkUserID) { func (N NetworkUserMap) Add(User *NetworkUser) { N[User.ID] = *User } + +// SetDefaults - adds the defaults to network user +func (U *NetworkUser) SetDefaults() { + if U.Clients == nil { + U.Clients = []string{} + } + if U.Nodes == nil { + U.Nodes = []string{} + } +} From 42f0af85b6969c6116d2b3a99c03152d7882c532 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Wed, 14 Sep 2022 15:33:26 -0400 Subject: [PATCH 22/52] set networkuser access level --- logic/auth.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/logic/auth.go b/logic/auth.go index fb99ba7e..ece13e37 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -140,7 +140,9 @@ func CreateUser(user models.User) (models.User, error) { // legacy if StringSliceContains(user.Networks, currentNets[i].NetID) { - newUser.AccessLevel = pro.NET_ADMIN + if !Is_EE { + newUser.AccessLevel = pro.NET_ADMIN + } } userErr := pro.CreateNetworkUser(¤tNets[i], &newUser) if userErr != nil { From 4cc32a5b50073ea1b07f48af43e0ff0a8236c764 Mon Sep 17 00:00:00 2001 From: cameronts Date: Thu, 15 Sep 2022 05:47:48 -0700 Subject: [PATCH 23/52] Fix sections for /server/ and /users/ paths in Swagger docs --- controllers/server.go | 8 +-- controllers/user.go | 20 +++---- swagger.yaml | 127 ++++++++++++++++++++++++++---------------- 3 files changed, 93 insertions(+), 62 deletions(-) diff --git a/controllers/server.go b/controllers/server.go index fb7541cc..5a13d449 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -67,7 +67,7 @@ func securityCheckServer(adminonly bool, next http.Handler) http.HandlerFunc { } } -// swagger:route DELETE /api/server/removenetwork/{network} nodes removeNetwork +// swagger:route DELETE /api/server/removenetwork/{network} server removeNetwork // // Remove a network from the server. // @@ -97,7 +97,7 @@ func removeNetwork(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(fmt.Sprintf("network %s removed from server", network)) } -// swagger:route GET /api/server/getserverinfo nodes getServerInfo +// swagger:route GET /api/server/getserverinfo server getServerInfo // // Get the server configuration. // @@ -118,7 +118,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) { //w.WriteHeader(http.StatusOK) } -// swagger:route GET /api/server/getconfig nodes getConfig +// swagger:route GET /api/server/getconfig server getConfig // // Get the server configuration. // @@ -140,7 +140,7 @@ func getConfig(w http.ResponseWriter, r *http.Request) { //w.WriteHeader(http.StatusOK) } -// swagger:route POST /api/server/register nodes register +// swagger:route POST /api/server/register server register // // Registers a client with the server and return the Certificate Authority and certificate. // diff --git a/controllers/user.go b/controllers/user.go index eddc7cef..572de7b2 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -30,7 +30,7 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods("GET") } -// swagger:route POST /api/users/adm/authenticate nodes authenticateUser +// swagger:route POST /api/users/adm/authenticate user authenticateUser // // Node authenticates using its password and retrieves a JWT for authorization. // @@ -97,7 +97,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { response.Write(successJSONResponse) } -// swagger:route GET /api/users/adm/hasadmin nodes hasAdmin +// swagger:route GET /api/users/adm/hasadmin user hasAdmin // // Checks whether the server has an admin. // @@ -137,7 +137,7 @@ func GetUserInternal(username string) (models.User, error) { return user, err } -// swagger:route GET /api/users/{username} nodes getUser +// swagger:route GET /api/users/{username} user getUser // // Get an individual user. // @@ -165,7 +165,7 @@ func getUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route GET /api/users nodes getUsers +// swagger:route GET /api/users user getUsers // // Get all users. // @@ -192,7 +192,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(users) } -// swagger:route POST /api/users/adm/createadmin nodes createAdmin +// swagger:route POST /api/users/adm/createadmin user createAdmin // // Make a user an admin. // @@ -228,7 +228,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(admin) } -// swagger:route POST /api/users/{username} nodes createUser +// swagger:route POST /api/users/{username} user createUser // // Create a user. // @@ -261,7 +261,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route PUT /api/users/networks/{username} nodes updateUserNetworks +// swagger:route PUT /api/users/networks/{username} user updateUserNetworks // // Updates the networks of the given user. // @@ -305,7 +305,7 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route PUT /api/users/{username} nodes updateUser +// swagger:route PUT /api/users/{username} user updateUser // // Update a user. // @@ -356,7 +356,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route PUT /api/users/{username}/adm nodes updateUserAdm +// swagger:route PUT /api/users/{username}/adm user updateUserAdm // // Updates the given admin user's info (as long as the user is an admin). // @@ -408,7 +408,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route DELETE /api/users/{username} nodes deleteUser +// swagger:route DELETE /api/users/{username} user deleteUser // // Delete a user. // diff --git a/swagger.yaml b/swagger.yaml index e061fc17..2ed9596f 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -1087,6 +1087,13 @@ paths: /api/networks/{networkname}: delete: operationId: deleteNetwork + parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName responses: "200": $ref: '#/responses/stringJSONResponse' @@ -1097,6 +1104,13 @@ paths: - networks get: operationId: getNetwork + parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName responses: "200": $ref: '#/responses/networkBodyResponse' @@ -1114,6 +1128,12 @@ paths: schema: $ref: '#/definitions/Network' x-go-name: Network + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName responses: "200": $ref: '#/responses/networkBodyResponse' @@ -1126,6 +1146,12 @@ paths: get: operationId: getNetworkACL parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName - description: ACL Container in: body name: acl_container @@ -1143,6 +1169,12 @@ paths: put: operationId: updateNetworkACL parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName - description: ACL Container in: body name: acl_container @@ -1160,6 +1192,13 @@ paths: /api/networks/{networkname}/keys: get: operationId: getAccessKeys + parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName responses: "200": $ref: '#/responses/accessKeySliceBodyResponse' @@ -1171,6 +1210,12 @@ paths: post: operationId: createAccessKey parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName - description: Access Key in: body name: access_key @@ -1189,6 +1234,12 @@ paths: delete: operationId: deleteAccessKey parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName - description: Access Key Name in: path name: access_key_name @@ -1206,6 +1257,13 @@ paths: /api/networks/{networkname}/keyupdate: post: operationId: keyUpdate + parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName responses: "200": $ref: '#/responses/networkBodyResponse' @@ -1217,6 +1275,13 @@ paths: /api/networks/{networkname}/nodelimit: put: operationId: updateNetworkNodeLimit + parameters: + - description: Network Name + in: path + name: networkname + required: true + type: string + x-go-name: NetworkName responses: "200": $ref: '#/responses/networkBodyResponse' @@ -1239,13 +1304,6 @@ paths: /api/nodes/{network}: get: operationId: getNetworkNodes - parameters: - - description: Network - in: path - name: network - required: true - type: string - x-go-name: Network responses: "200": $ref: '#/responses/nodeSliceResponse' @@ -1256,13 +1314,6 @@ paths: - nodes post: operationId: createNode - parameters: - - description: Network - in: path - name: network - required: true - type: string - x-go-name: Network responses: "200": $ref: '#/responses/nodeGetResponse' @@ -1537,12 +1588,6 @@ paths: post: operationId: authenticate parameters: - - description: Network - in: path - name: network - required: true - type: string - x-go-name: Network - description: AuthParams in: body name: auth_params @@ -1560,13 +1605,6 @@ paths: /api/nodes/adm/{network}/lastmodified: get: operationId: getLastModified - parameters: - - description: Network - in: path - name: network - required: true - type: string - x-go-name: Network responses: "200": $ref: '#/responses/nodeLastModifiedResponse' @@ -1593,7 +1631,7 @@ paths: - https summary: Get the server configuration. tags: - - nodes + - server /api/server/getserverinfo: get: operationId: getServerInfo @@ -1604,7 +1642,7 @@ paths: - https summary: Get the server configuration. tags: - - nodes + - server /api/server/register: post: operationId: register @@ -1622,17 +1660,10 @@ paths: - https summary: Registers a client with the server and return the Certificate Authority and certificate. tags: - - nodes + - server /api/server/removenetwork/{network}: delete: operationId: removeNetwork - parameters: - - description: Network - in: path - name: network - required: true - type: string - x-go-name: Network responses: "200": $ref: '#/responses/stringJSONResponse' @@ -1640,7 +1671,7 @@ paths: - https summary: Remove a network from the server. tags: - - nodes + - server /api/users: get: operationId: getUsers @@ -1651,7 +1682,7 @@ paths: - https summary: Get all users. tags: - - nodes + - user /api/users/{username}: delete: operationId: deleteUser @@ -1669,7 +1700,7 @@ paths: - https summary: Delete a user. tags: - - nodes + - user get: operationId: getUser parameters: @@ -1686,7 +1717,7 @@ paths: - https summary: Get an individual user. tags: - - nodes + - user post: operationId: createUser parameters: @@ -1709,7 +1740,7 @@ paths: - https summary: Create a user. tags: - - nodes + - user put: operationId: updateUser parameters: @@ -1732,7 +1763,7 @@ paths: - https summary: Update a user. tags: - - nodes + - user /api/users/{username}/adm: put: operationId: updateUserAdm @@ -1750,7 +1781,7 @@ paths: - https summary: Updates the given admin user's info (as long as the user is an admin). tags: - - nodes + - user /api/users/adm/authenticate: post: operationId: authenticateUser @@ -1768,7 +1799,7 @@ paths: - https summary: Node authenticates using its password and retrieves a JWT for authorization. tags: - - nodes + - user /api/users/adm/createadmin: post: operationId: createAdmin @@ -1786,7 +1817,7 @@ paths: - https summary: Make a user an admin. tags: - - nodes + - user /api/users/adm/hasadmin: get: operationId: hasAdmin @@ -1797,7 +1828,7 @@ paths: - https summary: Checks whether the server has an admin. tags: - - nodes + - user /api/users/networks/{username}: put: operationId: updateUserNetworks @@ -1821,7 +1852,7 @@ paths: - https summary: Updates the networks of the given user. tags: - - nodes + - user /meshclient/files/{filename}: get: operationId: fileServer From 3df00df66173cddaa3c1f85547772095ec69d772 Mon Sep 17 00:00:00 2001 From: cameronts Date: Thu, 15 Sep 2022 05:53:05 -0700 Subject: [PATCH 24/52] Rev Swagger docs version --- controllers/docs.go | 2 +- swagger.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/docs.go b/controllers/docs.go index 25464d7d..ed165206 100644 --- a/controllers/docs.go +++ b/controllers/docs.go @@ -11,7 +11,7 @@ // // Schemes: https // BasePath: / -// Version: 0.15.2 +// Version: 0.16.0 // Host: netmaker.io // // Consumes: diff --git a/swagger.yaml b/swagger.yaml index 2ed9596f..af4419da 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -746,7 +746,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.15.2 + version: 0.16.0 paths: /api/dns: get: From b1b497faa4964ead1cd1a3f2423a8767a5d53081 Mon Sep 17 00:00:00 2001 From: 0xdcarns Date: Thu, 15 Sep 2022 10:23:19 -0400 Subject: [PATCH 25/52] PR comments addressed --- auth/google.go | 2 +- auth/nodecallback.go | 2 +- auth/nodesession.go | 7 +++---- auth/oidc.go | 1 - controllers/user.go | 6 +++++- ee/initialize.go | 2 +- ee/util.go | 10 +++++----- mq/publishers.go | 1 - netclient/functions/join.go | 21 +++++++++++---------- netclient/functions/mqpublish.go | 2 +- 10 files changed, 28 insertions(+), 26 deletions(-) diff --git a/auth/google.go b/auth/google.go index 22be2a47..3b614481 100644 --- a/auth/google.go +++ b/auth/google.go @@ -88,7 +88,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { } logger.Log(1, "completed google OAuth sigin in for", content.Email) - http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect) + http.Redirect(w, r, fmt.Sprintf("%s/login?login=%s&user=%s", servercfg.GetFrontendURL(), jwt, content.Email), http.StatusPermanentRedirect) } func getGoogleUserInfo(state string, code string) (*OAuthUser, error) { diff --git a/auth/nodecallback.go b/auth/nodecallback.go index d7bfae9c..4af44ca5 100644 --- a/auth/nodecallback.go +++ b/auth/nodecallback.go @@ -58,7 +58,7 @@ func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) { // retrieve machinekey from state cache reqKeyIf, machineKeyFoundErr := netcache.Get(state) if machineKeyFoundErr != nil { - logger.Log(0, "requested machine state key expired before authorisation completed -", err.Error()) + logger.Log(0, "requested machine state key expired before authorisation completed -", machineKeyFoundErr.Error()) reqKeyIf = &netcache.CValue{ Network: "invalid", Value: state, diff --git a/auth/nodesession.go b/auth/nodesession.go index b848c2b8..0b4cd3cc 100644 --- a/auth/nodesession.go +++ b/auth/nodesession.go @@ -19,7 +19,7 @@ import ( // SessionHandler - called by the HTTP router when user // is calling netclient with --login-server parameter in order to authenticate // via SSO mechanism by OAuth2 protocol flow. -// This triggers a session start and it is managed by the flow implmented here and callback +// This triggers a session start and it is managed by the flow implemented here and callback // When this method finishes - the auth flow has finished either OK or by timeout or any other error occured func SessionHandler(conn *websocket.Conn) { defer conn.Close() @@ -55,6 +55,8 @@ func SessionHandler(conn *websocket.Conn) { // TBD: what should be the timeout here ? timeout := make(chan bool, 1) answer := make(chan string, 1) + defer close(answer) + defer close(timeout) if loginMessage.User != "" { // handle basic auth // verify that server supports basic auth, then authorize the request with given credentials @@ -149,7 +151,4 @@ func SessionHandler(conn *websocket.Conn) { logger.Log(0, "write close:", err.Error()) return } - time.After(time.Second) - close(answer) - close(timeout) } diff --git a/auth/oidc.go b/auth/oidc.go index ad03b1c9..11115728 100644 --- a/auth/oidc.go +++ b/auth/oidc.go @@ -62,7 +62,6 @@ func handleOIDCLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect) return } - logger.Log(3, "using state string:", oauth_state_string) var url = auth_provider.AuthCodeURL(oauth_state_string) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } diff --git a/controllers/user.go b/controllers/user.go index f53ab2f1..1f72b3ff 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -476,7 +476,11 @@ func socketHandler(w http.ResponseWriter, r *http.Request) { // Upgrade our raw HTTP connection to a websocket based one conn, err := upgrader.Upgrade(w, r, nil) if err != nil { - logger.Log(0, "error during connection upgrade for node SSO sign-in:", err.Error()) + logger.Log(0, "error during connection upgrade for node sign-in:", err.Error()) + return + } + if conn == nil { + logger.Log(0, "failed to establish web-socket connection during node sign-in") return } // Start handling the session diff --git a/ee/initialize.go b/ee/initialize.go index 665e7729..9439d6ac 100644 --- a/ee/initialize.go +++ b/ee/initialize.go @@ -13,7 +13,7 @@ import ( // InitEE - Initialize EE Logic func InitEE() { - SetIsEnterprise() + setIsEnterprise() models.SetLogo(retrieveEELogo()) controller.HttpHandlers = append(controller.HttpHandlers, ee_controllers.MetricHandlers) logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() { diff --git a/ee/util.go b/ee/util.go index 90705a0b..7e923aaf 100644 --- a/ee/util.go +++ b/ee/util.go @@ -8,16 +8,16 @@ import ( var isEnterprise bool -// SetIsEnterprise - sets server to use enterprise features -func SetIsEnterprise() { - isEnterprise = true -} - // IsEnterprise - checks if enterprise binary or not func IsEnterprise() bool { return isEnterprise } +// setIsEnterprise - sets server to use enterprise features +func setIsEnterprise() { + isEnterprise = true +} + // base64encode - base64 encode helper function func base64encode(input []byte) string { return base64.StdEncoding.EncodeToString(input) diff --git a/mq/publishers.go b/mq/publishers.go index 9e6cc521..4c45ea7b 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -229,7 +229,6 @@ func collectServerMetrics(networks []models.Network) { func pushMetricsToExporter(metrics models.Metrics) error { logger.Log(2, "----> Pushing metrics to exporter") - SetupMQTT() data, err := json.Marshal(metrics) if err != nil { return errors.New("failed to marshal metrics: " + err.Error()) diff --git a/netclient/functions/join.go b/netclient/functions/join.go index fc53e295..c59553be 100644 --- a/netclient/functions/join.go +++ b/netclient/functions/join.go @@ -13,7 +13,6 @@ import ( "runtime" "strings" "syscall" - "time" "github.com/gorilla/websocket" "github.com/gravitl/netmaker/logger" @@ -56,7 +55,7 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { // Dial the netmaker server controller conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil) if err != nil { - logger.Log(0, fmt.Sprintf("Error connecting to %s : %s", cfg.Server.API, err.Error())) + logger.Log(0, fmt.Sprintf("error connecting to %s : %s", cfg.Server.API, err.Error())) return err } // Don't forget to close when finished @@ -113,14 +112,14 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { // An answer from the server. // Server waits ~5 min - If takes too long timeout will be triggered by the server done := make(chan struct{}) + defer close(done) // Following code will run in a separate go routine // it reads a message from the server which either contains 'AccessToken:' string or not // if not - then it contains an Error to display. // if yes - then AccessToken is to be used to proceed joining the network go func() { - defer close(done) for { - _, msg, err := conn.ReadMessage() + msgType, msg, err := conn.ReadMessage() if err != nil { // Error reading a message from the server if !strings.Contains(err.Error(), "normal") { @@ -128,13 +127,19 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { } return } + + if msgType == websocket.CloseMessage { + logger.Log(1, "received close message from server") + done <- struct{}{} + return + } // Get the access token from the response if strings.Contains(string(msg), "AccessToken: ") { // Access was granted rxToken := strings.TrimPrefix(string(msg), "AccessToken: ") accesstoken, err := config.ParseAccessToken(rxToken) if err != nil { - log.Printf("Failed to parse received access token %s,err=%s\n", accesstoken, err.Error()) + logger.Log(0, fmt.Sprintf("failed to parse received access token %s,err=%s\n", accesstoken, err.Error())) return } @@ -159,7 +164,7 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { logger.Log(1, "finished") return nil case <-interrupt: - log.Println("interrupt") + logger.Log(0, "interrupt received, closing connection") // Cleanly close the connection by sending a close message and then // waiting (with timeout) for the server to close the connection. err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) @@ -167,10 +172,6 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { logger.Log(0, "write close:", err.Error()) return err } - select { - case <-done: - case <-time.After(time.Second): - } return nil } } diff --git a/netclient/functions/mqpublish.go b/netclient/functions/mqpublish.go index 766c19ed..8341a4fb 100644 --- a/netclient/functions/mqpublish.go +++ b/netclient/functions/mqpublish.go @@ -167,7 +167,7 @@ func publishMetrics(nodeCfg *config.ClientConfig) { logger.Log(1, "failed to authenticate when publishing metrics", err.Error()) return } - url := "https://" + nodeCfg.Server.API + "/api/nodes/" + nodeCfg.Network + "/" + nodeCfg.Node.ID + url := fmt.Sprintf("https://%s/api/nodes/%s/%s", nodeCfg.Server.API, nodeCfg.Network, nodeCfg.Node.ID) response, err := API("", http.MethodGet, url, token) if err != nil { logger.Log(1, "failed to read from server during metrics publish", err.Error()) From f5a20ffcca6aa806b35911cd38fe318237bfdd65 Mon Sep 17 00:00:00 2001 From: 0xdcarns Date: Fri, 16 Sep 2022 14:09:04 -0400 Subject: [PATCH 26/52] fixed default node acl logic --- logic/pro/proacls/nodes.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logic/pro/proacls/nodes.go b/logic/pro/proacls/nodes.go index d55035e7..d1e7074d 100644 --- a/logic/pro/proacls/nodes.go +++ b/logic/pro/proacls/nodes.go @@ -23,10 +23,10 @@ func AdjustNodeAcls(node *models.Node, networkNodes []models.Node) error { // 2 cases // both allow - allow // either 1 denies - deny - if node.DoesACLAllow() { - currentACLs.ChangeAccess(acls.AclID(nodeID), acls.AclID(currentNodeID), acls.Allowed) - } else if node.DoesACLDeny() { + if node.DoesACLDeny() || networkNodes[i].DoesACLDeny() { currentACLs.ChangeAccess(acls.AclID(nodeID), acls.AclID(currentNodeID), acls.NotAllowed) + } else if node.DoesACLAllow() || networkNodes[i].DoesACLAllow() { + currentACLs.ChangeAccess(acls.AclID(nodeID), acls.AclID(currentNodeID), acls.Allowed) } } From 56a96e8bc277a829ee86169fa1e687bb5580712b Mon Sep 17 00:00:00 2001 From: agraphie Date: Sun, 18 Sep 2022 16:19:57 +0200 Subject: [PATCH 27/52] Fix copy paste error The PostDown section wasn't set anymore in certain cases. --- netclient/wireguard/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netclient/wireguard/common.go b/netclient/wireguard/common.go index 4175c790..fa494c7c 100644 --- a/netclient/wireguard/common.go +++ b/netclient/wireguard/common.go @@ -359,7 +359,7 @@ func WriteWgConfig(node *models.Node, privateKey string, peers []wgtypes.PeerCon wireguard.Section(section_interface).Key("PostDown").AddShadow(part) } } else { - wireguard.Section(section_interface).Key("PostUp").SetValue((node.PostUp)) + wireguard.Section(section_interface).Key("PostDown").SetValue((node.PostDown)) } } if node.MTU != 0 { From be2fa030c308dc4db5c29d116dd8bbb39af7c637 Mon Sep 17 00:00:00 2001 From: 0xdcarns Date: Mon, 19 Sep 2022 09:46:09 -0400 Subject: [PATCH 28/52] added 0.16.0 upgrade logic --- netclient/functions/clientconfig.go | 1 + netclient/functions/upgrades/upgrades.go | 5 +++-- netclient/functions/upgrades/v0-14-8.go | 22 ---------------------- netclient/functions/upgrades/v0-16-0.go | 21 +++++++++++++++++++++ 4 files changed, 25 insertions(+), 24 deletions(-) delete mode 100644 netclient/functions/upgrades/v0-14-8.go create mode 100644 netclient/functions/upgrades/v0-16-0.go diff --git a/netclient/functions/clientconfig.go b/netclient/functions/clientconfig.go index 3b1fbdf5..67793abd 100644 --- a/netclient/functions/clientconfig.go +++ b/netclient/functions/clientconfig.go @@ -14,6 +14,7 @@ import ( // Usage : set update required to true and and update logic to function func UpdateClientConfig() { defer upgrades.ReleaseUpgrades() + upgrades.InitializeUpgrades() networks, _ := ncutils.GetSystemNetworks() if len(networks) == 0 { diff --git a/netclient/functions/upgrades/upgrades.go b/netclient/functions/upgrades/upgrades.go index 77dab11a..ffdf9cfc 100644 --- a/netclient/functions/upgrades/upgrades.go +++ b/netclient/functions/upgrades/upgrades.go @@ -1,10 +1,11 @@ package upgrades -func init() { +// InitializeUpgrades - initializes written upgrades +func InitializeUpgrades() { addUpgrades([]UpgradeInfo{ upgrade0145, upgrade0146, - upgrade0148, + upgrade0160, }) } diff --git a/netclient/functions/upgrades/v0-14-8.go b/netclient/functions/upgrades/v0-14-8.go deleted file mode 100644 index af87e1b7..00000000 --- a/netclient/functions/upgrades/v0-14-8.go +++ /dev/null @@ -1,22 +0,0 @@ -package upgrades - -import ( - "github.com/gravitl/netmaker/netclient/config" -) - -var upgrade0148 = UpgradeInfo{ - RequiredVersions: []string{ - "v0.14.5", - "v0.14.6", - "v0.14.7", - }, - NewVersion: "v0.14.8", - OP: update0148, -} - -func update0148(cfg *config.ClientConfig) { - // set connect default if not present 14.X -> 14.8 - if cfg.Node.Connected == "" { - cfg.Node.SetDefaultConnected() - } -} diff --git a/netclient/functions/upgrades/v0-16-0.go b/netclient/functions/upgrades/v0-16-0.go new file mode 100644 index 00000000..43dac44c --- /dev/null +++ b/netclient/functions/upgrades/v0-16-0.go @@ -0,0 +1,21 @@ +package upgrades + +import ( + "github.com/gravitl/netmaker/netclient/config" +) + +var upgrade0160 = UpgradeInfo{ + RequiredVersions: []string{ + "v0.15.1", + "v0.15.2", + }, + NewVersion: "v0.16.0", + OP: update0160, +} + +func update0160(cfg *config.ClientConfig) { + // set connect default if not present 15.X -> 16.0 + if cfg.Node.Connected == "" { + cfg.Node.SetDefaultConnected() + } +} From 0ac7669ac3d3d25a85cfe4582d9305ab2a5d7a83 Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Mon, 19 Sep 2022 12:43:17 -0400 Subject: [PATCH 29/52] update static checks for build tags --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b868e077..96844794 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,7 +81,7 @@ jobs: sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev go test -p 1 ./... -v go install honnef.co/go/tools/cmd/staticcheck@latest - ~/go/bin/staticcheck ./... + { ~/go/bin/staticcheck -tags=ee ./... ; ~/go/bin/staticcheck ./... ; } | ~/go/bin/staticcheck -merge env: DATABASE: sqlite CLIENT_MODE: "off" From 5253e80cdb07fc128d064673da66c9efe9765a12 Mon Sep 17 00:00:00 2001 From: 0xdcarns Date: Mon, 19 Sep 2022 12:53:50 -0400 Subject: [PATCH 30/52] added upgrade versions --- netclient/functions/upgrades/v0-16-0.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netclient/functions/upgrades/v0-16-0.go b/netclient/functions/upgrades/v0-16-0.go index 43dac44c..e8c7eb45 100644 --- a/netclient/functions/upgrades/v0-16-0.go +++ b/netclient/functions/upgrades/v0-16-0.go @@ -6,6 +6,8 @@ import ( var upgrade0160 = UpgradeInfo{ RequiredVersions: []string{ + "v0.14.6", + "v0.15.0", "v0.15.1", "v0.15.2", }, From d19cac35fa2e9ae0a19c474b5b81fabdacddabc1 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 12:59:37 -0400 Subject: [PATCH 31/52] removed remove network --- controllers/server.go | 30 ------------------------------ swagger.yaml | 11 ----------- 2 files changed, 41 deletions(-) diff --git a/controllers/server.go b/controllers/server.go index b21b92c0..869e65c4 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -50,36 +50,6 @@ func allowUsers(next http.Handler) http.HandlerFunc { } } -// swagger:route DELETE /api/server/removenetwork/{network} server removeNetwork -// -// Remove a network from the server. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: stringJSONResponse -func removeNetwork(w http.ResponseWriter, r *http.Request) { - // Set header - w.Header().Set("Content-Type", "application/json") - - // get params - var params = mux.Vars(r) - network := params["network"] - err := logic.DeleteNetwork(network) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("failed to delete network [%s]: %v", network, err)) - json.NewEncoder(w).Encode(fmt.Sprintf("could not remove network %s from server", network)) - return - } - logger.Log(1, r.Header.Get("user"), - fmt.Sprintf("deleted network [%s]: %v", network, err)) - json.NewEncoder(w).Encode(fmt.Sprintf("network %s removed from server", network)) -} - // swagger:route GET /api/server/getserverinfo server getServerInfo // // Get the server configuration. diff --git a/swagger.yaml b/swagger.yaml index af4419da..5532c70e 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -1661,17 +1661,6 @@ paths: summary: Registers a client with the server and return the Certificate Authority and certificate. tags: - server - /api/server/removenetwork/{network}: - delete: - operationId: removeNetwork - responses: - "200": - $ref: '#/responses/stringJSONResponse' - schemes: - - https - summary: Remove a network from the server. - tags: - - server /api/users: get: operationId: getUsers From d0a75532e8187ed02966f4229d57e692dd6fc83f Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 13:05:27 -0400 Subject: [PATCH 32/52] fix gomod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3e0da7a8..2adf491a 100644 --- a/go.mod +++ b/go.mod @@ -40,8 +40,8 @@ require ( ) require ( - github.com/gorilla/websocket v1.4.2 github.com/coreos/go-oidc/v3 v3.4.0 + github.com/gorilla/websocket v1.4.2 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 ) From 4157ddb73a0b76e4cd96f0e51ed675309ff0b62e Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 13:27:30 -0400 Subject: [PATCH 33/52] fix staticcheck --- .github/workflows/test.yml | 2 +- ee/license.go | 10 ---------- ee/types.go | 4 ---- models/accessToken.go | 2 ++ 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96844794..6b8123fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,7 +81,7 @@ jobs: sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev go test -p 1 ./... -v go install honnef.co/go/tools/cmd/staticcheck@latest - { ~/go/bin/staticcheck -tags=ee ./... ; ~/go/bin/staticcheck ./... ; } | ~/go/bin/staticcheck -merge + { ~/go/bin/staticcheck -tags=ee ./... ; } env: DATABASE: sqlite CLIENT_MODE: "off" diff --git a/ee/license.go b/ee/license.go index ff4d291c..eb614cb2 100644 --- a/ee/license.go +++ b/ee/license.go @@ -239,13 +239,3 @@ func getCachedResponse() ([]byte, error) { func ClearLicenseCache() error { return database.DeleteRecord(database.CACHE_TABLE_NAME, license_cache_key) } - -func getServerCount() int { - if record, err := database.FetchRecord(database.SERVERCONF_TABLE_NAME, server_id_key); err == nil { - currentServerIDs := serverIDs{} - if err = json.Unmarshal([]byte(record), ¤tServerIDs); err == nil { - return len(currentServerIDs.ServerIDs) - } - } - return 1 -} diff --git a/ee/types.go b/ee/types.go index d802e1af..2e6a7c41 100644 --- a/ee/types.go +++ b/ee/types.go @@ -81,7 +81,3 @@ type ValidateLicenseRequest struct { type licenseResponseCache struct { Body []byte `json:"body" binding:"required"` } - -type serverIDs struct { - ServerIDs []string `json:"server_ids" binding:"required"` -} diff --git a/models/accessToken.go b/models/accessToken.go index 6c3d704d..61cd3a08 100644 --- a/models/accessToken.go +++ b/models/accessToken.go @@ -1,10 +1,12 @@ package models +// AccessToken - token used to access netmaker type AccessToken struct { APIConnString string `json:"apiconnstring"` ClientConfig } +// ClientConfig - the config of the client type ClientConfig struct { Network string `json:"network"` Key string `json:"key"` From e438dd56d417ced904f650902e14ed3278f705de Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 13:32:39 -0400 Subject: [PATCH 34/52] updating versions --- .github/ISSUE_TEMPLATE/bug-report.yml | 1 + README.md | 2 +- compose/docker-compose.reference.yml | 4 ++-- compose/docker-compose.yml | 4 ++-- k8s/client/netclient-daemonset.yaml | 2 +- k8s/client/netclient.yaml | 2 +- k8s/server/netmaker-server.yaml | 2 +- k8s/server/netmaker-ui.yaml | 2 +- netclient/netclient.exe.manifest.xml | 2 +- netclient/versioninfo.json | 10 +++++----- 10 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index acfb1090..4b4ff3ed 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.16.0 - v0.15.2 - v0.15.1 - v0.15.0 diff --git a/README.md b/README.md index 3b409658..defde18a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@

- + diff --git a/compose/docker-compose.reference.yml b/compose/docker-compose.reference.yml index 3091d18f..e7db2ccd 100644 --- a/compose/docker-compose.reference.yml +++ b/compose/docker-compose.reference.yml @@ -3,7 +3,7 @@ version: "3.4" services: netmaker: # The Primary Server for running Netmaker container_name: netmaker - image: gravitl/netmaker:v0.15.2 + image: gravitl/netmaker:v0.16.0 cap_add: - NET_ADMIN - NET_RAW @@ -62,7 +62,7 @@ services: - traefik.http.services.netmaker-api.loadbalancer.server.port=8081 netmaker-ui: # The Netmaker UI Component container_name: netmaker-ui - image: gravitl/netmaker-ui:v0.15.2 + image: gravitl/netmaker-ui:v0.16.0 depends_on: - netmaker links: diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 45c4a8ea..afc32564 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.4" services: netmaker: container_name: netmaker - image: gravitl/netmaker:v0.15.2 + image: gravitl/netmaker:v0.16.0 cap_add: - NET_ADMIN - NET_RAW @@ -52,7 +52,7 @@ services: - traefik.http.services.netmaker-api.loadbalancer.server.port=8081 netmaker-ui: container_name: netmaker-ui - image: gravitl/netmaker-ui:v0.15.2 + image: gravitl/netmaker-ui:v0.16.0 depends_on: - netmaker links: diff --git a/k8s/client/netclient-daemonset.yaml b/k8s/client/netclient-daemonset.yaml index c4c20ce5..52bf056c 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-go:v0.15.2 + image: gravitl/netclient-go:v0.16.0 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/client/netclient.yaml b/k8s/client/netclient.yaml index fa8307ff..f95a0ff1 100644 --- a/k8s/client/netclient.yaml +++ b/k8s/client/netclient.yaml @@ -28,7 +28,7 @@ spec: # - "" containers: - name: netclient - image: gravitl/netclient:v0.15.2 + image: gravitl/netclient:v0.16.0 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/server/netmaker-server.yaml b/k8s/server/netmaker-server.yaml index ed2530b5..61c1d866 100644 --- a/k8s/server/netmaker-server.yaml +++ b/k8s/server/netmaker-server.yaml @@ -83,7 +83,7 @@ spec: value: "Kubernetes" - name: VERBOSITY value: "3" - image: gravitl/netmaker:v0.15.2 + image: gravitl/netmaker:v0.16.0 imagePullPolicy: Always name: netmaker ports: diff --git a/k8s/server/netmaker-ui.yaml b/k8s/server/netmaker-ui.yaml index 326d94a0..240f9a82 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.15.2 + image: gravitl/netmaker-ui:v0.16.0 ports: - containerPort: 443 env: diff --git a/netclient/netclient.exe.manifest.xml b/netclient/netclient.exe.manifest.xml index 285f21be..01fb0d9d 100644 --- a/netclient/netclient.exe.manifest.xml +++ b/netclient/netclient.exe.manifest.xml @@ -1,7 +1,7 @@ Date: Mon, 19 Sep 2022 15:37:00 -0400 Subject: [PATCH 35/52] fixing sso error handling --- auth/nodecallback.go | 10 ++++++---- auth/nodesession.go | 23 +++++++++++++++++++---- logger/logger.go | 2 +- netclient/command/commands.go | 5 ++--- netclient/daemon/freebsd.go | 2 +- netclient/daemon/macos.go | 2 +- netclient/daemon/systemd.go | 6 +++--- netclient/functions/common.go | 3 +-- netclient/functions/join.go | 7 ++++++- netclient/main.go | 4 ++-- 10 files changed, 42 insertions(+), 22 deletions(-) diff --git a/auth/nodecallback.go b/auth/nodecallback.go index 4af44ca5..5345f111 100644 --- a/auth/nodecallback.go +++ b/auth/nodecallback.go @@ -155,8 +155,11 @@ func returnErrTemplate(uname, message, state string, ncache *netcache.CValue) [] // Listens in /oidc/register/:regKey. func RegisterNodeSSO(w http.ResponseWriter, r *http.Request) { - logger.Log(1, "RegisterNodeSSO\n") - + if auth_provider == nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("invalid login attempt")) + return + } vars := mux.Vars(r) // machineKeyStr this is not key but state @@ -165,8 +168,7 @@ func RegisterNodeSSO(w http.ResponseWriter, r *http.Request) { if machineKeyStr == "" { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Wrong params")) - logger.Log(0, "Wrong params ", machineKeyStr) + w.Write([]byte("invalid login attempt")) return } diff --git a/auth/nodesession.go b/auth/nodesession.go index 0b4cd3cc..7d444dd7 100644 --- a/auth/nodesession.go +++ b/auth/nodesession.go @@ -58,12 +58,20 @@ func SessionHandler(conn *websocket.Conn) { defer close(answer) defer close(timeout) + if _, err = logic.GetNetwork(loginMessage.Network); err != nil { + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + logger.Log(0, "error during message writing:", err.Error()) + } + return + } + if loginMessage.User != "" { // handle basic auth // verify that server supports basic auth, then authorize the request with given credentials // check if user is allowed to join via node sso // i.e. user is admin or user has network permissions if !servercfg.IsBasicAuthEnabled() { - err = conn.WriteMessage(messageType, []byte("Basic Auth Disabled")) + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { logger.Log(0, "error during message writing:", err.Error()) } @@ -73,7 +81,7 @@ func SessionHandler(conn *websocket.Conn) { Password: loginMessage.Password, }) if err != nil { - err = conn.WriteMessage(messageType, []byte(fmt.Sprintf("Failed to authenticate, %s.", loginMessage.User))) + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { logger.Log(0, "error during message writing:", err.Error()) } @@ -81,7 +89,7 @@ func SessionHandler(conn *websocket.Conn) { } user, err := isUserIsAllowed(loginMessage.User, loginMessage.Network, false) if err != nil { - err = conn.WriteMessage(messageType, []byte(fmt.Sprintf("%s lacks permission to join.", loginMessage.User))) + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { logger.Log(0, "error during message writing:", err.Error()) } @@ -99,6 +107,13 @@ func SessionHandler(conn *websocket.Conn) { return } } else { // handle SSO / OAuth + if auth_provider == nil { + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + logger.Log(0, "error during message writing:", err.Error()) + } + return + } redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr) err = conn.WriteMessage(messageType, []byte(redirectUrl)) if err != nil { @@ -135,7 +150,7 @@ func SessionHandler(conn *websocket.Conn) { case <-timeout: logger.Log(0, "Authentication server time out for a node on network", loginMessage.Network) // the read from req.answerCh has timed out - err = conn.WriteMessage(messageType, []byte("Authentication server time out")) + err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { logger.Log(0, "Error during message writing:", err.Error()) } diff --git a/logger/logger.go b/logger/logger.go index 5b90b6fa..ad258772 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -134,7 +134,7 @@ func Retrieve(filePath string) string { // FatalLog - exits os after logging func FatalLog(message ...string) { - fmt.Printf("[netmaker] Fatal: %s \n", MakeString(" ", message...)) + fmt.Printf("[%s] Fatal: %s \n", program, MakeString(" ", message...)) os.Exit(2) } diff --git a/netclient/command/commands.go b/netclient/command/commands.go index b8c7d082..7bb92f0a 100644 --- a/netclient/command/commands.go +++ b/netclient/command/commands.go @@ -30,14 +30,13 @@ func Join(cfg *config.ClientConfig, privateKey string) error { logger.Log(1, "Logging into %s via:", cfg.Network, cfg.SsoServer) err = functions.JoinViaSSo(cfg, privateKey) if err != nil { - logger.Log(0, "Join via OIDC failed: ", err.Error()) + logger.Log(0, "Join failed: ", err.Error()) return err } if cfg.AccessKey == "" { - return errors.New("failed to get access key") + return errors.New("login failed") } - logger.Log(1, "Got an access key to ", cfg.Network, " via:", cfg.SsoServer) } logger.Log(1, "Joining network: ", cfg.Network) diff --git a/netclient/daemon/freebsd.go b/netclient/daemon/freebsd.go index 6eafcfb6..27238412 100644 --- a/netclient/daemon/freebsd.go +++ b/netclient/daemon/freebsd.go @@ -28,7 +28,7 @@ func SetupFreebsdDaemon() error { } err = ncutils.Copy(binarypath, EXEC_DIR+"netclient") if err != nil { - log.Println(err) + logger.Log(0, err.Error()) return err } diff --git a/netclient/daemon/macos.go b/netclient/daemon/macos.go index 8301ed3b..d59ccf65 100644 --- a/netclient/daemon/macos.go +++ b/netclient/daemon/macos.go @@ -25,7 +25,7 @@ func SetupMacDaemon() error { } err = ncutils.Copy(binarypath, MAC_EXEC_DIR+"netclient") if err != nil { - log.Println(err) + logger.Log(0, err.Error()) return err } diff --git a/netclient/daemon/systemd.go b/netclient/daemon/systemd.go index aa218734..5916c6c0 100644 --- a/netclient/daemon/systemd.go +++ b/netclient/daemon/systemd.go @@ -38,7 +38,7 @@ func SetupSystemDDaemon() error { } err = ncutils.Copy(binarypath, EXEC_DIR+"netclient") if err != nil { - log.Println(err) + logger.Log(0, err.Error()) return err } @@ -64,7 +64,7 @@ WantedBy=multi-user.target if !ncutils.FileExists("/etc/systemd/system/netclient.service") { err = os.WriteFile("/etc/systemd/system/netclient.service", servicebytes, 0644) if err != nil { - log.Println(err) + logger.Log(0, err.Error()) return err } } @@ -106,7 +106,7 @@ func RemoveSystemDServices() error { var err error if !ncutils.IsWindows() && isOnlyService() { if err != nil { - log.Println(err) + logger.Log(0, err.Error()) } ncutils.RunCmd("systemctl disable netclient.service", false) ncutils.RunCmd("systemctl disable netclient.timer", false) diff --git a/netclient/functions/common.go b/netclient/functions/common.go index a8b23c80..7b78d7ba 100644 --- a/netclient/functions/common.go +++ b/netclient/functions/common.go @@ -301,8 +301,7 @@ func WipeLocal(cfg *config.ClientConfig) error { if cfg.Node.Interface != "" { if ncutils.FileExists(dir + cfg.Node.Interface + ".conf") { if err := os.Remove(dir + cfg.Node.Interface + ".conf"); err != nil { - log.Println("error removing .conf:") - log.Println(err.Error()) + logger.Log(0, err.Error()) fail = true } } diff --git a/netclient/functions/join.go b/netclient/functions/join.go index c59553be..c6684a02 100644 --- a/netclient/functions/join.go +++ b/netclient/functions/join.go @@ -82,6 +82,7 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { } loginMsg.User = global_settings.User loginMsg.Password = string(pass) + fmt.Println("attempting login...") } msgTx, err := json.Marshal(loginMsg) @@ -101,7 +102,6 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { // Wait to receive something from server _, msg, err := conn.ReadMessage() if err != nil { - log.Println("Error in receive:", err) return err } // Print message from the netmaker controller to the user @@ -121,6 +121,11 @@ func JoinViaSSo(cfg *config.ClientConfig, privateKey string) error { for { msgType, msg, err := conn.ReadMessage() if err != nil { + if msgType < 0 { + logger.Log(1, "received close message from server") + done <- struct{}{} + return + } // Error reading a message from the server if !strings.Contains(err.Error(), "normal") { logger.Log(0, "read:", err.Error()) diff --git a/netclient/main.go b/netclient/main.go index de79967b..8987e3b8 100644 --- a/netclient/main.go +++ b/netclient/main.go @@ -4,10 +4,10 @@ package main import ( - "log" "os" "runtime/debug" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/netclient/cli_options" "github.com/gravitl/netmaker/netclient/config" "github.com/gravitl/netmaker/netclient/functions" @@ -47,7 +47,7 @@ func main() { } else { err := app.Run(os.Args) if err != nil { - log.Fatal(err) + logger.FatalLog(err.Error()) } } } From 8d19451244bda8eccaea608db16aa84022aacf76 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 15:38:26 -0400 Subject: [PATCH 36/52] remove log --- auth/nodesession.go | 1 - 1 file changed, 1 deletion(-) diff --git a/auth/nodesession.go b/auth/nodesession.go index 7d444dd7..af6107a3 100644 --- a/auth/nodesession.go +++ b/auth/nodesession.go @@ -23,7 +23,6 @@ import ( // When this method finishes - the auth flow has finished either OK or by timeout or any other error occured func SessionHandler(conn *websocket.Conn) { defer conn.Close() - logger.Log(1, "Running sessionHandler") // If reached here we have a session from user to handle... messageType, message, err := conn.ReadMessage() From a6eac84e51506e1b115058d9bf4fdcdd4e068f34 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 16:14:51 -0400 Subject: [PATCH 37/52] removing log --- netclient/functions/daemon.go | 5 +---- netclient/functions/join.go | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/netclient/functions/daemon.go b/netclient/functions/daemon.go index 993059be..2e68b1c0 100644 --- a/netclient/functions/daemon.go +++ b/netclient/functions/daemon.go @@ -106,10 +106,7 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { // == subscribe to all nodes for each on machine == serverSet[server] = true logger.Log(1, "started daemon for server ", server) - err := local.SetNetmakerDomainRoute(cfg.Server.API) - if err != nil { - logger.Log(0, "error setting route for netmaker: "+err.Error()) - } + local.SetNetmakerDomainRoute(cfg.Server.API) wg.Add(1) go messageQueue(ctx, wg, &cfg) } diff --git a/netclient/functions/join.go b/netclient/functions/join.go index c6684a02..49c88aa8 100644 --- a/netclient/functions/join.go +++ b/netclient/functions/join.go @@ -360,10 +360,7 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error { logger.Log(0, "network:", node.Network, "failed to make backup, node will not auto restore if config is corrupted") } - err = local.SetNetmakerDomainRoute(cfg.Server.API) - if err != nil { - logger.Log(0, "error setting route for netmaker: "+err.Error()) - } + local.SetNetmakerDomainRoute(cfg.Server.API) cfg.Node = node if err := Register(cfg); err != nil { return err From def1487bcf6efa363b3fac702a57ded24aabadd6 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Mon, 19 Sep 2022 16:20:58 -0400 Subject: [PATCH 38/52] fix default acl --- logic/nodes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/logic/nodes.go b/logic/nodes.go index 1b3584c4..ca04d839 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -421,6 +421,10 @@ func SetNodeDefaults(node *models.Node) { node.ExpirationDateTime = time.Now().Unix() + models.TEN_YEARS_IN_SECONDS + if node.DefaultACL == "" && node.IsServer != "yes" { + node.DefaultACL = parentNetwork.DefaultACL + } + if node.ListenPort == 0 { node.ListenPort = parentNetwork.DefaultListenPort } From 8920fcd99ec64d9b53cc2a298c608f5c1d9a173b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:13:04 +0000 Subject: [PATCH 39/52] Bump github.com/go-playground/validator/v10 from 10.11.0 to 10.11.1 Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.0 to 10.11.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.11.0...v10.11.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2adf491a..5cdb6ce4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/eclipse/paho.mqtt.golang v1.4.1 - github.com/go-playground/validator/v10 v10.11.0 + github.com/go-playground/validator/v10 v10.11.1 github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 diff --git a/go.sum b/go.sum index 9abdc29c..00b6f41e 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= -github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -667,7 +667,6 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 87411d091317f90cb2f3a0813fa16427f975fd35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:13:09 +0000 Subject: [PATCH 40/52] Bump github.com/gorilla/websocket from 1.4.2 to 1.5.0 Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.4.2 to 1.5.0. - [Release notes](https://github.com/gorilla/websocket/releases) - [Commits](https://github.com/gorilla/websocket/compare/v1.4.2...v1.5.0) --- updated-dependencies: - dependency-name: github.com/gorilla/websocket dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2adf491a..a409d226 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( require ( github.com/coreos/go-oidc/v3 v3.4.0 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.5.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 ) diff --git a/go.sum b/go.sum index 9abdc29c..b607946f 100644 --- a/go.sum +++ b/go.sum @@ -274,8 +274,9 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -667,7 +668,6 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From e93eaf8e1d8fff7a28a146ddc9234bf1d5aa7b13 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 20 Sep 2022 11:03:44 -0400 Subject: [PATCH 41/52] fixing compare --- netclient/functions/mqhandlers.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netclient/functions/mqhandlers.go b/netclient/functions/mqhandlers.go index 56499c70..95244f83 100644 --- a/netclient/functions/mqhandlers.go +++ b/netclient/functions/mqhandlers.go @@ -216,10 +216,13 @@ func UpdatePeers(client mqtt.Client, msg mqtt.Message) { } //check if internet gateway has changed oldGateway, err := net.ResolveUDPAddr("udp", cfg.Node.InternetGateway) + + // note: may want to remove second part (oldGateway == &net.UDPAddr{}) + // since it's a pointer, will never be true if err != nil || (oldGateway == &net.UDPAddr{}) { oldGateway = nil } - if (internetGateway == nil && oldGateway != nil) || (internetGateway != nil && internetGateway != oldGateway) { + if (internetGateway == nil && oldGateway != nil) || (internetGateway != nil && internetGateway.String() != oldGateway.String()) { cfg.Node.InternetGateway = internetGateway.String() if err := config.ModNodeConfig(&cfg.Node); err != nil { logger.Log(0, "failed to save internet gateway", err.Error()) From 1723f5703590877d6d6c6e8e7b158a7379adeee6 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 20 Sep 2022 11:50:15 -0400 Subject: [PATCH 42/52] fixing flags and config --- logic/gateway.go | 18 +++++++++--------- netclient/cli_options/flags.go | 8 ++++---- netclient/config/config.go | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/logic/gateway.go b/logic/gateway.go index 25945f3c..2837b24c 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -212,12 +212,12 @@ func CreateIngressGateway(netid string, nodeid string) (models.Node, error) { if node.PostUp != "" { if !strings.Contains(node.PostUp, postUpCmd) { - postUpCmd = node.PostUp + " ; " + postUpCmd + postUpCmd = node.PostUp + postUpCmd } } if node.PostDown != "" { if !strings.Contains(node.PostDown, postDownCmd) { - postDownCmd = node.PostDown + " ; " + postDownCmd + postDownCmd = node.PostDown + postDownCmd } } node.SetLastModified() @@ -385,12 +385,12 @@ func firewallIPTablesCommandsCreateIngress(networkInterface string, ipv4, ipv6 b // spacing around ; is important for later parsing of postup/postdown in wireguard/common.go postUp += "ip6tables -A FORWARD -i " + networkInterface + " -j ACCEPT ; " postUp += "ip6tables -A FORWARD -o " + networkInterface + " -j ACCEPT ; " - postUp += "ip6tables -t nat -A POSTROUTING -o " + networkInterface + " -j MASQUERADE" + postUp += "ip6tables -t nat -A POSTROUTING -o " + networkInterface + " -j MASQUERADE ; " // doesn't remove potentially empty tables or chains postDown += "ip6tables -D FORWARD -i " + networkInterface + " -j ACCEPT ; " postDown += "ip6tables -D FORWARD -o " + networkInterface + " -j ACCEPT ; " - postDown += "ip6tables -t nat -D POSTROUTING -o " + networkInterface + " -j MASQUERADE" + postDown += "ip6tables -t nat -D POSTROUTING -o " + networkInterface + " -j MASQUERADE ; " } return postUp, postDown } @@ -402,13 +402,13 @@ func firewallIPTablesCommandsCreateEgress(networkInterface string, gatewayInterf postDown := "" if ipv4 { postUp += "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; " - postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT" + postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT ; " postDown += "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; " postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT ; " if egressNatEnabled == "yes" { - postUp += " ; iptables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; " - postDown += " ; iptables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; " + postUp += "iptables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; " + postDown += "iptables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; " } } if ipv6 { @@ -418,8 +418,8 @@ func firewallIPTablesCommandsCreateEgress(networkInterface string, gatewayInterf postDown += "ip6tables -D FORWARD -o " + networkInterface + " -j ACCEPT ; " if egressNatEnabled == "yes" { - postUp += " ; ip6tables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE" - postDown += " ; ip6tables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE" + postUp += "ip6tables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; " + postDown += "ip6tables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; " } } return postUp, postDown diff --git a/netclient/cli_options/flags.go b/netclient/cli_options/flags.go index 2887306d..89204dd2 100644 --- a/netclient/cli_options/flags.go +++ b/netclient/cli_options/flags.go @@ -134,11 +134,11 @@ func GetFlags(hostname string) []cli.Flag { Usage: "Access Token for signing up machine with Netmaker server during initial 'add'.", }, &cli.StringFlag{ - Name: "login-server", - Aliases: []string{"l"}, - EnvVars: []string{"LOGIN_SERVER"}, + Name: "server", + Aliases: []string{"s"}, + EnvVars: []string{"HOST_SERVER"}, Value: "", - Usage: "Login server URL, use it for the Single Sign-on along with the network parameter", + Usage: "Host server (domain of API) [Example: api.example.com]. Do not include \"http(s)://\" use it for the Single Sign-on along with the network parameter", }, &cli.StringFlag{ Name: "user", diff --git a/netclient/config/config.go b/netclient/config/config.go index 5a8276ff..2132c59d 100644 --- a/netclient/config/config.go +++ b/netclient/config/config.go @@ -240,8 +240,8 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) { if c.String("apiserver") != "" { cfg.Server.API = c.String("apiserver") } - } else if c.String("login-server") != "" { - cfg.SsoServer = c.String("login-server") + } else if c.String("server") != "" { + cfg.SsoServer = c.String("server") cfg.Network = c.String("network") cfg.Node.Network = c.String("network") global_settings.User = c.String("user") From d97c945012dd27dc981b9d97bf0f38d8458c6aa6 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 20 Sep 2022 18:11:58 -0400 Subject: [PATCH 43/52] creating ee compose --- compose/docker-compose.ee.yml | 197 ++++++++++++++++++++++++++++++++++ compose/docker-compose.yml | 65 +---------- 2 files changed, 198 insertions(+), 64 deletions(-) create mode 100644 compose/docker-compose.ee.yml diff --git a/compose/docker-compose.ee.yml b/compose/docker-compose.ee.yml new file mode 100644 index 00000000..0c4aba06 --- /dev/null +++ b/compose/docker-compose.ee.yml @@ -0,0 +1,197 @@ +version: "3.4" + +services: + netmaker: + container_name: netmaker + image: gravitl/netmaker:v0.16.0-ee + cap_add: + - NET_ADMIN + - NET_RAW + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + - net.ipv6.conf.all.disable_ipv6=0 + - net.ipv6.conf.all.forwarding=1 + restart: always + volumes: + - dnsconfig:/root/config/dnsconfig + - sqldata:/root/data + - shared_certs:/etc/netmaker + environment: + SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN" + SERVER_HOST: "SERVER_PUBLIC_IP" + SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443" + COREDNS_ADDR: "SERVER_PUBLIC_IP" + DNS_MODE: "on" + SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN" + API_PORT: "8081" + CLIENT_MODE: "on" + MASTER_KEY: "REPLACE_MASTER_KEY" + CORS_ALLOWED_ORIGIN: "*" + DISPLAY_KEYS: "on" + DATABASE: "sqlite" + NODE_ID: "netmaker-server-1" + MQ_HOST: "mq" + MQ_PORT: "443" + MQ_SERVER_PORT: "1883" + HOST_NETWORK: "off" + VERBOSITY: "1" + MANAGE_IPTABLES: "on" + PORT_FORWARD_SERVICES: "dns" + METRICS_EXPORTER: "on" + LICENSE_KEY: "YOUR_LICENSE_KEY" + NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID" + ports: + - "51821-51830:51821-51830/udp" + expose: + - "8081" + labels: + - traefik.enable=true + - traefik.http.routers.netmaker-api.entrypoints=websecure + - traefik.http.routers.netmaker-api.rule=Host(`api.NETMAKER_BASE_DOMAIN`) + - traefik.http.routers.netmaker-api.service=netmaker-api + - traefik.http.services.netmaker-api.loadbalancer.server.port=8081 + netmaker-ui: + container_name: netmaker-ui + image: gravitl/netmaker-ui:v0.16.0 + depends_on: + - netmaker + links: + - "netmaker:api" + restart: always + environment: + BACKEND_URL: "https://api.NETMAKER_BASE_DOMAIN" + expose: + - "80" + labels: + - traefik.enable=true + - traefik.http.middlewares.nmui-security.headers.accessControlAllowOriginList=*.NETMAKER_BASE_DOMAIN + - traefik.http.middlewares.nmui-security.headers.stsSeconds=31536000 + - traefik.http.middlewares.nmui-security.headers.browserXssFilter=true + - traefik.http.middlewares.nmui-security.headers.customFrameOptionsValue=SAMEORIGIN + - traefik.http.middlewares.nmui-security.headers.customResponseHeaders.X-Robots-Tag=none + - traefik.http.middlewares.nmui-security.headers.customResponseHeaders.Server= # Remove the server name + - traefik.http.routers.netmaker-ui.entrypoints=websecure + - traefik.http.routers.netmaker-ui.middlewares=nmui-security@docker + - traefik.http.routers.netmaker-ui.rule=Host(`dashboard.NETMAKER_BASE_DOMAIN`) + - traefik.http.routers.netmaker-ui.service=netmaker-ui + - traefik.http.services.netmaker-ui.loadbalancer.server.port=80 + coredns: + container_name: coredns + image: coredns/coredns + command: -conf /root/dnsconfig/Corefile + depends_on: + - netmaker + restart: always + volumes: + - dnsconfig:/root/dnsconfig + traefik: + image: traefik:v2.6 + container_name: traefik + command: + - "--certificatesresolvers.http.acme.email=YOUR_EMAIL" + - "--certificatesresolvers.http.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.http.acme.tlschallenge=true" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http.tls=true" + - "--entrypoints.websecure.http.tls.certResolver=http" + - "--log.level=INFO" + - "--providers.docker=true" + - "--providers.docker.exposedByDefault=false" + - "--serverstransport.insecureskipverify=true" + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik_certs:/letsencrypt + ports: + - "443:443" + mq: + container_name: mq + image: eclipse-mosquitto:2.0.11-openssl + depends_on: + - netmaker + restart: unless-stopped + volumes: + - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf + - /root/mosquitto.passwords:/etc/mosquitto.passwords + - mosquitto_data:/mosquitto/data + - mosquitto_logs:/mosquitto/log + - shared_certs:/mosquitto/certs + expose: + - "8883" + labels: + - traefik.enable=true + - traefik.tcp.routers.mqtts.rule=HostSNI(`broker.NETMAKER_BASE_DOMAIN`) + - traefik.tcp.routers.mqtts.tls.passthrough=true + - traefik.tcp.services.mqtts-svc.loadbalancer.server.port=8883 + - traefik.tcp.routers.mqtts.service=mqtts-svc + - traefik.tcp.routers.mqtts.entrypoints=websecure + prometheus: + container_name: prometheus + image: gravitl/netmaker-prometheus:latest + environment: + NETMAKER_METRICS_TARGET: "netmaker-exporter.NETMAKER_BASE_DOMAIN" + labels: + - traefik.enable=true + - traefik.http.routers.prometheus.entrypoints=websecure + - traefik.http.routers.prometheus.rule=Host(`prometheus.NETMAKER_BASE_DOMAIN`) + - traefik.http.services.prometheus.loadbalancer.server.port=9090 + - traefik.http.routers.prometheus.service=prometheus + restart: always + volumes: + - prometheus_data:/prometheus + depends_on: + - netmaker + ports: + - 9090:9090 + grafana: + container_name: grafana + image: gravitl/netmaker-grafana:latest + labels: + - traefik.enable=true + - traefik.http.routers.grafana.entrypoints=websecure + - traefik.http.routers.grafana.rule=Host(`grafana.NETMAKER_BASE_DOMAIN`) + - traefik.http.services.grafana.loadbalancer.server.port=3000 + - traefik.http.routers.grafana.service=grafana + environment: + PROMETHEUS_HOST: "prometheus.NETMAKER_BASE_DOMAIN" + NETMAKER_METRICS_TARGET: "netmaker-exporter.NETMAKER_BASE_DOMAIN" + ports: + - 3000:3000 + restart: always + links: + - prometheus + depends_on: + - prometheus + - netmaker + netmaker-exporter: + container_name: netmaker-exporter + image: gravitl/netmaker-exporter:latest + labels: + - traefik.enable=true + - traefik.http.routers.netmaker-exporter.entrypoints=websecure + - traefik.http.routers.netmaker-exporter.rule=Host(`netmaker-exporter.NETMAKER_BASE_DOMAIN`) + - traefik.http.services.netmaker-exporter.loadbalancer.server.port=8085 + - traefik.http.routers.netmaker-exporter.service=netmaker-exporter + restart: always + depends_on: + - netmaker + environment: + MQ_HOST: "mq" + MQ_PORT: "443" + MQ_SERVER_PORT: "1884" + PROMETHEUS: "on" + VERBOSITY: "1" + API_PORT: "8085" + PROMETHEUS_HOST: https://prometheus.NETMAKER_BASE_DOMAIN + expose: + - "8085" +volumes: + traefik_certs: {} + shared_certs: {} + sqldata: {} + dnsconfig: {} + mosquitto_data: {} + mosquitto_logs: {} + prometheus_data: {} diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index afc32564..ca0564b2 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -39,7 +39,6 @@ services: VERBOSITY: "1" MANAGE_IPTABLES: "on" PORT_FORWARD_SERVICES: "dns" - METRICS_EXPORTER: "on" ports: - "51821-51830:51821-51830/udp" expose: @@ -112,7 +111,6 @@ services: restart: unless-stopped volumes: - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf - - /root/mosquitto.passwords:/etc/mosquitto.passwords - mosquitto_data:/mosquitto/data - mosquitto_logs:/mosquitto/log - shared_certs:/mosquitto/certs @@ -125,71 +123,10 @@ services: - traefik.tcp.services.mqtts-svc.loadbalancer.server.port=8883 - traefik.tcp.routers.mqtts.service=mqtts-svc - traefik.tcp.routers.mqtts.entrypoints=websecure - prometheus: - container_name: prometheus - image: gravitl/netmaker-prometheus:latest - environment: - NETMAKER_METRICS_TARGET: "netmaker-exporter.NETMAKER_BASE_DOMAIN" - labels: - - traefik.enable=true - - traefik.http.routers.prometheus.entrypoints=websecure - - traefik.http.routers.prometheus.rule=Host(`prometheus.NETMAKER_BASE_DOMAIN`) - - traefik.http.services.prometheus.loadbalancer.server.port=9090 - - traefik.http.routers.prometheus.service=prometheus - restart: always - volumes: - - prometheus_data:/prometheus - depends_on: - - netmaker - ports: - - 9090:9090 - grafana: - container_name: grafana - image: gravitl/netmaker-grafana:latest - labels: - - traefik.enable=true - - traefik.http.routers.grafana.entrypoints=websecure - - traefik.http.routers.grafana.rule=Host(`grafana.NETMAKER_BASE_DOMAIN`) - - traefik.http.services.grafana.loadbalancer.server.port=3000 - - traefik.http.routers.grafana.service=grafana - environment: - PROMETHEUS_HOST: "prometheus.NETMAKER_BASE_DOMAIN" - NETMAKER_METRICS_TARGET: "netmaker-exporter.NETMAKER_BASE_DOMAIN" - ports: - - 3000:3000 - restart: always - links: - - prometheus - depends_on: - - prometheus - - netmaker - netmaker-exporter: - container_name: netmaker-exporter - image: gravitl/netmaker-exporter:latest - labels: - - traefik.enable=true - - traefik.http.routers.netmaker-exporter.entrypoints=websecure - - traefik.http.routers.netmaker-exporter.rule=Host(`netmaker-exporter.NETMAKER_BASE_DOMAIN`) - - traefik.http.services.netmaker-exporter.loadbalancer.server.port=8085 - - traefik.http.routers.netmaker-exporter.service=netmaker-exporter - restart: always - depends_on: - - netmaker - environment: - MQ_HOST: "mq" - MQ_PORT: "443" - MQ_SERVER_PORT: "1884" - PROMETHEUS: "on" - VERBOSITY: "1" - API_PORT: "8085" - PROMETHEUS_HOST: https://prometheus.NETMAKER_BASE_DOMAIN - expose: - - "8085" volumes: traefik_certs: {} shared_certs: {} sqldata: {} dnsconfig: {} mosquitto_data: {} - mosquitto_logs: {} - prometheus_data: {} + mosquitto_logs: {} \ No newline at end of file From f0eb383153b331e8c7bd9a96384ac9cbad98edc5 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 20 Sep 2022 18:29:06 -0400 Subject: [PATCH 44/52] fixing mosquitto --- docker/mosquitto-ee.conf | 16 ++++++++++++++++ docker/mosquitto.conf | 6 +----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 docker/mosquitto-ee.conf diff --git a/docker/mosquitto-ee.conf b/docker/mosquitto-ee.conf new file mode 100644 index 00000000..6ee92ddc --- /dev/null +++ b/docker/mosquitto-ee.conf @@ -0,0 +1,16 @@ +per_listener_settings true + +listener 8883 +allow_anonymous false +require_certificate true +use_identity_as_username true +cafile /mosquitto/certs/root.pem +certfile /mosquitto/certs/server.pem +keyfile /mosquitto/certs/server.key + +listener 1883 +allow_anonymous true + +listener 1884 +allow_anonymous false +password_file /etc/mosquitto.passwords diff --git a/docker/mosquitto.conf b/docker/mosquitto.conf index 6ee92ddc..c6abb68b 100644 --- a/docker/mosquitto.conf +++ b/docker/mosquitto.conf @@ -9,8 +9,4 @@ certfile /mosquitto/certs/server.pem keyfile /mosquitto/certs/server.key listener 1883 -allow_anonymous true - -listener 1884 -allow_anonymous false -password_file /etc/mosquitto.passwords +allow_anonymous true \ No newline at end of file From 4df9bc098b1d3308b1ce79558d0a7c4bb1fed7df Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Wed, 21 Sep 2022 07:32:32 -0400 Subject: [PATCH 45/52] add timeout to metrics pinger --- logic/metrics/metrics.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logic/metrics/metrics.go b/logic/metrics/metrics.go index c660b325..efa3b58b 100644 --- a/logic/metrics/metrics.go +++ b/logic/metrics/metrics.go @@ -1,6 +1,8 @@ package metrics import ( + "time" + "github.com/go-ping/ping" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -48,6 +50,7 @@ func Collect(iface string, peerMap models.PeerMap) (*models.Metrics, error) { newMetric.Latency = 999 } else { pinger.Count = 1 + pinger.Timeout = time.Second * 2 err = pinger.Run() if err != nil { logger.Log(0, "failed ping for metrics on peer address", address, err.Error()) From 160cc6ba36ce8fd2e7c7af04e45903470c2ebd96 Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Wed, 21 Sep 2022 10:27:43 -0400 Subject: [PATCH 46/52] fix path for netclient darwin headless --- .github/workflows/buildandrelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buildandrelease.yml b/.github/workflows/buildandrelease.yml index 520b87ec..9b9b4fc2 100644 --- a/.github/workflows/buildandrelease.yml +++ b/.github/workflows/buildandrelease.yml @@ -402,7 +402,7 @@ jobs: cd netclient env GOOS=darwin GOARCH=amd64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient . env CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-arm64/netclient main.go - env GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient-darwin-headless . + env GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-headless/netclient . - name: Upload darwin-amd64 to Release uses: svenstaro/upload-release-action@v2 with: From da7234e2822a186ff94d868ad3f7c9de6a086288 Mon Sep 17 00:00:00 2001 From: Agorgianitis Loukas Date: Thu, 22 Sep 2022 14:48:45 +0300 Subject: [PATCH 47/52] Add support for armv7l container images --- .github/workflows/docker-builder.yml | 2 +- .github/workflows/publish-docker.yml | 21 +++++++++++++++--- .../workflows/publish-netclient-docker.yml | 22 ++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker-builder.yml b/.github/workflows/docker-builder.yml index d23a1230..91c38863 100644 --- a/.github/workflows/docker-builder.yml +++ b/.github/workflows/docker-builder.yml @@ -23,6 +23,6 @@ jobs: with: context: . push: true - platforms: linux/amd64, linux/arm64 + platforms: linux/amd64, linux/arm64, linux/armv7l file: ./docker/Dockerfile-go-builder tags: gravitl/go-builder:latest diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 712c909b..790aaeac 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -55,7 +55,7 @@ jobs: sleep 10 kill %1 - - name: Build arm and export to Docker + name: Build arm64 and export to Docker uses: docker/build-push-action@v2 with: context: . @@ -64,7 +64,22 @@ jobs: tags: ${{ env.TAG }} build-args: version=${{ env.TAG }} - - name: Test arm + name: Test arm64 + run: | + docker run --rm ${{ env.TAG }}& + sleep 10 + kill %1 + - + name: Build armv7l and export to Docker + uses: docker/build-push-action@v2 + with: + context: . + load: true + platforms: linux/armv7l + tags: ${{ env.TAG }} + build-args: version=${{ env.TAG }} + - + name: Test armv7l run: | docker run --rm ${{ env.TAG }}& sleep 10 @@ -74,7 +89,7 @@ jobs: uses: docker/build-push-action@v2 with: context: . - platforms: linux/amd64, linux/arm64 + platforms: linux/amd64, linux/arm64, linux/armv7l push: true tags: ${{ github.repository }}:${{ env.TAG }}, ${{ github.repository }}:latest build-args: version=${{ env.TAG }} diff --git a/.github/workflows/publish-netclient-docker.yml b/.github/workflows/publish-netclient-docker.yml index 46de1df7..c1111701 100644 --- a/.github/workflows/publish-netclient-docker.yml +++ b/.github/workflows/publish-netclient-docker.yml @@ -56,7 +56,7 @@ jobs: sleep 10 kill %1 - - name: Build arm and export to Docker + name: Build arm64 and export to Docker uses: docker/build-push-action@v2 with: context: . @@ -66,7 +66,23 @@ jobs: tags: ${{ env.TAG }} build-args: version=${{ env.TAG }} - - name: Test arm + name: Test arm64 + run: | + docker run --rm ${{ env.TAG }}& + sleep 10 + kill %1 + - + name: Build armv7l and export to Docker + uses: docker/build-push-action@v2 + with: + context: . + load: true + platforms: linux/armv7l + file: ./docker/Dockerfile-netclient-multiarch + tags: ${{ env.TAG }} + build-args: version=${{ env.TAG }} + - + name: Test armv7l run: | docker run --rm ${{ env.TAG }}& sleep 10 @@ -76,7 +92,7 @@ jobs: uses: docker/build-push-action@v2 with: context: . - platforms: linux/amd64, linux/arm64 + platforms: linux/amd64, linux/arm64, linux/armv7l file: ./docker/Dockerfile-netclient-multiarch push: true tags: gravitl/netclient:${{ env.TAG }}, gravitl/netclient:latest From d54fb0823ef359858832a6ff36f4d487864d4398 Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Thu, 22 Sep 2022 12:59:59 -0400 Subject: [PATCH 48/52] ee check --- models/structs.go | 1 + netclient/functions/mqpublish.go | 2 +- servercfg/serverconf.go | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/models/structs.go b/models/structs.go index 6b80e4f9..a082d231 100644 --- a/models/structs.go +++ b/models/structs.go @@ -218,6 +218,7 @@ type ServerConfig struct { Version string `yaml:"version"` MQPort string `yaml:"mqport"` Server string `yaml:"server"` + Is_EE bool `yaml:"isee"` } // User.NameInCharset - returns if name is in charset below or not diff --git a/netclient/functions/mqpublish.go b/netclient/functions/mqpublish.go index 8341a4fb..6bffcf86 100644 --- a/netclient/functions/mqpublish.go +++ b/netclient/functions/mqpublish.go @@ -114,7 +114,7 @@ func checkin(currentRun int) { } Hello(&nodeCfg) checkCertExpiry(&nodeCfg) - if currentRun >= 5 { + if currentRun >= 5 && nodeCfg.Server.Is_EE { logger.Log(0, "collecting metrics for node", nodeCfg.Node.Name) publishMetrics(&nodeCfg) } diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 262327a5..2ad86a97 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -101,6 +101,7 @@ func GetServerInfo() models.ServerConfig { } cfg.Version = GetVersion() cfg.Server = GetServer() + cfg.Is_EE = GetServerConfig().IsEE == "yes" return cfg } From ee64e9e282069b43139983ec0d54fc6dd2f2945d Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Thu, 22 Sep 2022 15:01:59 -0400 Subject: [PATCH 49/52] move IS_EE from logic to servercfg --- ee/initialize.go | 3 ++- logic/serverconf.go | 2 -- servercfg/serverconf.go | 5 +++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ee/initialize.go b/ee/initialize.go index 9439d6ac..a83ad354 100644 --- a/ee/initialize.go +++ b/ee/initialize.go @@ -9,6 +9,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" ) // InitEE - Initialize EE Logic @@ -34,7 +35,7 @@ func setControllerLimits() { logic.Users_Limit = Limits.Users logic.Clients_Limit = Limits.Clients logic.Free_Tier = Limits.FreeTier - logic.Is_EE = true + servercfg.Is_EE = true } func retrieveEELogo() string { diff --git a/logic/serverconf.go b/logic/serverconf.go index fbd5faf5..284e2d45 100644 --- a/logic/serverconf.go +++ b/logic/serverconf.go @@ -17,8 +17,6 @@ var ( Clients_Limit = 1000000000 // Free_Tier - specifies if free tier Free_Tier = false - // Is_EE - specifies if enterprise - Is_EE = false ) // constant for database key for storing server ids diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 2ad86a97..600b474a 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -15,6 +15,7 @@ import ( var ( Version = "dev" + Is_EE = false ) // SetHost - sets the host ip @@ -84,6 +85,10 @@ func GetServerConfig() config.ServerConfig { cfg.PortForwardServices = services cfg.Server = GetServer() cfg.Verbosity = GetVerbosity() + cfg.IsEE = "no" + if Is_EE { + cfg.IsEE = "yes" + } return cfg } From 175bb944716b421f132dc5bec6734458abd1e06c Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Thu, 22 Sep 2022 15:22:21 -0400 Subject: [PATCH 50/52] additional checks for disconnected nodes --- logic/peers.go | 4 ++ netclient/functions/daemon.go | 12 +++--- netclient/functions/mqpublish.go | 66 ++++++++++++++++---------------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/logic/peers.go b/logic/peers.go index 45c7d4b8..edfaff92 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -61,6 +61,10 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) { if node.NetworkSettings.IsPointToSite == "yes" && node.IsHub == "no" && peer.IsHub == "no" { continue } + if node.Connected != "yes" { + //skip unconnected nodes + continue + } // if the node is not a server, set the endpoint var setEndpoint = !(node.IsServer == "yes") diff --git a/netclient/functions/daemon.go b/netclient/functions/daemon.go index 2e68b1c0..528ce2f8 100644 --- a/netclient/functions/daemon.go +++ b/netclient/functions/daemon.go @@ -94,11 +94,13 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { cfg := config.ClientConfig{} cfg.Network = network cfg.ReadConfig() - if err := wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, ncutils.GetNetclientPathSpecific()+cfg.Node.Interface+".conf"); err != nil { - logger.Log(0, "failed to start ", cfg.Node.Interface, "wg interface", err.Error()) - } - if cfg.PublicIPService != "" { - global_settings.PublicIPServices[network] = cfg.PublicIPService + if cfg.Node.Connected == "yes" { + if err := wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, ncutils.GetNetclientPathSpecific()+cfg.Node.Interface+".conf"); err != nil { + logger.Log(0, "failed to start ", cfg.Node.Interface, "wg interface", err.Error()) + } + if cfg.PublicIPService != "" { + global_settings.PublicIPServices[network] = cfg.PublicIPService + } } server := cfg.Server.Server diff --git a/netclient/functions/mqpublish.go b/netclient/functions/mqpublish.go index 8341a4fb..932d49c1 100644 --- a/netclient/functions/mqpublish.go +++ b/netclient/functions/mqpublish.go @@ -69,41 +69,43 @@ func checkin(currentRun int) { // defaults to iptables for now, may need another default for non-Linux OSes nodeCfg.Node.FirewallInUse = models.FIREWALL_IPTABLES } - if nodeCfg.Node.IsStatic != "yes" { - extIP, err := ncutils.GetPublicIP(nodeCfg.Server.API) - if err != nil { - logger.Log(1, "error encountered checking public ip addresses: ", err.Error()) - } - if nodeCfg.Node.Endpoint != extIP && extIP != "" { - logger.Log(1, "network:", nodeCfg.Node.Network, "endpoint has changed from ", nodeCfg.Node.Endpoint, " to ", extIP) - nodeCfg.Node.Endpoint = extIP - if err := PublishNodeUpdate(&nodeCfg); err != nil { - logger.Log(0, "network:", nodeCfg.Node.Network, "could not publish endpoint change") + if nodeCfg.Node.Connected == "yes" { + if nodeCfg.Node.IsStatic != "yes" { + extIP, err := ncutils.GetPublicIP(nodeCfg.Server.API) + if err != nil { + logger.Log(1, "error encountered checking public ip addresses: ", err.Error()) } - } - intIP, err := getPrivateAddr() - if err != nil { - logger.Log(1, "network:", nodeCfg.Node.Network, "error encountered checking private ip addresses: ", err.Error()) - } - if nodeCfg.Node.LocalAddress != intIP && intIP != "" { - logger.Log(1, "network:", nodeCfg.Node.Network, "local Address has changed from ", nodeCfg.Node.LocalAddress, " to ", intIP) - nodeCfg.Node.LocalAddress = intIP - if err := PublishNodeUpdate(&nodeCfg); err != nil { - logger.Log(0, "Network: ", nodeCfg.Node.Network, " could not publish local address change") + if nodeCfg.Node.Endpoint != extIP && extIP != "" { + logger.Log(1, "network:", nodeCfg.Node.Network, "endpoint has changed from ", nodeCfg.Node.Endpoint, " to ", extIP) + nodeCfg.Node.Endpoint = extIP + if err := PublishNodeUpdate(&nodeCfg); err != nil { + logger.Log(0, "network:", nodeCfg.Node.Network, "could not publish endpoint change") + } } - } - _ = UpdateLocalListenPort(&nodeCfg) + intIP, err := getPrivateAddr() + if err != nil { + logger.Log(1, "network:", nodeCfg.Node.Network, "error encountered checking private ip addresses: ", err.Error()) + } + if nodeCfg.Node.LocalAddress != intIP && intIP != "" { + logger.Log(1, "network:", nodeCfg.Node.Network, "local Address has changed from ", nodeCfg.Node.LocalAddress, " to ", intIP) + nodeCfg.Node.LocalAddress = intIP + if err := PublishNodeUpdate(&nodeCfg); err != nil { + logger.Log(0, "Network: ", nodeCfg.Node.Network, " could not publish local address change") + } + } + _ = UpdateLocalListenPort(&nodeCfg) - } else if nodeCfg.Node.IsLocal == "yes" && nodeCfg.Node.LocalRange != "" { - localIP, err := ncutils.GetLocalIP(nodeCfg.Node.LocalRange) - if err != nil { - logger.Log(1, "network:", nodeCfg.Node.Network, "error encountered checking local ip addresses: ", err.Error()) - } - if nodeCfg.Node.Endpoint != localIP && localIP != "" { - logger.Log(1, "network:", nodeCfg.Node.Network, "endpoint has changed from "+nodeCfg.Node.Endpoint+" to ", localIP) - nodeCfg.Node.Endpoint = localIP - if err := PublishNodeUpdate(&nodeCfg); err != nil { - logger.Log(0, "network:", nodeCfg.Node.Network, "could not publish localip change") + } else if nodeCfg.Node.IsLocal == "yes" && nodeCfg.Node.LocalRange != "" { + localIP, err := ncutils.GetLocalIP(nodeCfg.Node.LocalRange) + if err != nil { + logger.Log(1, "network:", nodeCfg.Node.Network, "error encountered checking local ip addresses: ", err.Error()) + } + if nodeCfg.Node.Endpoint != localIP && localIP != "" { + logger.Log(1, "network:", nodeCfg.Node.Network, "endpoint has changed from "+nodeCfg.Node.Endpoint+" to ", localIP) + nodeCfg.Node.Endpoint = localIP + if err := PublishNodeUpdate(&nodeCfg); err != nil { + logger.Log(0, "network:", nodeCfg.Node.Network, "could not publish localip change") + } } } } From 61c0c675cd7611511c0f52f4294b04fdd677d5bc Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Thu, 22 Sep 2022 15:30:09 -0400 Subject: [PATCH 51/52] add ee build to test workflow --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b8123fd..f3c75861 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ jobs: - name: Build run: | env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build main.go + env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags=ee main.go cd netclient env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build main.go From 854c20f7727bb5f170504f5164102ada8fc08861 Mon Sep 17 00:00:00 2001 From: "Matthew R. Kasun" Date: Thu, 22 Sep 2022 16:17:06 -0400 Subject: [PATCH 52/52] shutdown netmaker on reciept of sigterm --- controllers/controller.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/controller.go b/controllers/controller.go index 015ffdb9..1350e37d 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "sync" + "syscall" "time" "github.com/gorilla/handlers" @@ -59,7 +60,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) { // Relay os.Interrupt to our channel (os.Interrupt = CTRL+C) // Ignore other incoming signals - ctx, stop := signal.NotifyContext(context.TODO(), os.Interrupt) + ctx, stop := signal.NotifyContext(context.TODO(), syscall.SIGTERM, os.Interrupt) defer stop() // Block main routine until a signal is received