Merge pull request #2435 from gravitl/release-v0.20.3

This commit is contained in:
Alex Feiszli 2023-07-03 14:07:07 -04:00 committed by GitHub
commit f9405f7ed2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 1071 additions and 919 deletions

View file

@ -31,6 +31,7 @@ body:
label: Version
description: What version are you running?
options:
- v0.20.3
- v0.20.2
- v0.20.1
- v0.20.0

View file

@ -71,7 +71,9 @@ jobs:
if [ "$result" == "pass" ]
then
server=$arg
echo server >> /tmp/server
echo $server >> /tmp/server
break
fi
done

View file

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

View file

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

View file

@ -16,7 +16,7 @@
<p align="center">
<a href="https://github.com/gravitl/netmaker/releases">
<img src="https://img.shields.io/badge/Version-0.20.2-informational?style=flat-square" />
<img src="https://img.shields.io/badge/Version-0.20.3-informational?style=flat-square" />
</a>
<a href="https://hub.docker.com/r/gravitl/netmaker/tags">
<img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

View file

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

View file

@ -12,7 +12,7 @@ services:
- sqldata:/root/data
environment:
# config-dependant vars
- STUN_LIST=stun.${NM_DOMAIN}:${STUN_PORT},stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
- STUN_LIST=stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
# The domain/host IP indicating the mq broker address
- BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}
# The base domain of netmaker
@ -26,8 +26,6 @@ services:
- TURN_SERVER_HOST=turn.${NM_DOMAIN}
# domain of the turn api server
- TURN_SERVER_API_HOST=https://turnapi.${NM_DOMAIN}
ports:
- "3478:3478/udp"
netmaker-ui:
container_name: netmaker-ui

View file

@ -83,6 +83,11 @@ type ServerConfig struct {
TurnUserName string `yaml:"turn_username"`
TurnPassword string `yaml:"turn_password"`
UseTurn bool `yaml:"use_turn"`
UsersLimit int `yaml:"user_limit"`
ClientsLimit int `yaml:"client_limit"`
NetworksLimit int `yaml:"network_limit"`
HostsLimit int `yaml:"host_limit"`
DeployedByOperator bool `yaml:"deployed_by_operator"`
}
// ProxyMode - default proxy mode for server

View file

@ -51,8 +51,7 @@ func TestGetNodeDNS(t *testing.T) {
createNet()
createHost()
t.Run("NoNodes", func(t *testing.T) {
dns, err := logic.GetNodeDNS("skynet")
assert.EqualError(t, err, "could not find any records")
dns, _ := logic.GetNodeDNS("skynet")
assert.Equal(t, []models.DNSEntry(nil), dns)
})
t.Run("NodeExists", func(t *testing.T) {

View file

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

View file

@ -10,7 +10,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"
@ -102,7 +101,7 @@ func getAllExtClients(w http.ResponseWriter, r *http.Request) {
clients := []models.ExtClient{}
var err error
if len(networksSlice) > 0 && networksSlice[0] == logic.ALL_NETWORK_ACCESS {
clients, err = functions.GetAllExtClients()
clients, err = logic.GetAllExtClients()
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, "failed to get all extclients: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

View file

@ -1,7 +1,6 @@
package controller
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -49,38 +48,8 @@ func getHosts(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
//isMasterAdmin := r.Header.Get("ismaster") == "yes"
//user, err := logic.GetUser(r.Header.Get("user"))
//if err != nil && !isMasterAdmin {
// logger.Log(0, r.Header.Get("user"), "failed to fetch user: ", err.Error())
// logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
// return
//}
// return JSON/API formatted hosts
//ret := []models.ApiHost{}
apiHosts := logic.GetAllHostsAPI(currentHosts[:])
logger.Log(2, r.Header.Get("user"), "fetched all hosts")
//for _, host := range apiHosts {
// nodes := host.Nodes
// // work on the copy
// host.Nodes = []string{}
// for _, nid := range nodes {
// node, err := logic.GetNodeByID(nid)
// if err != nil {
// logger.Log(0, r.Header.Get("user"), "failed to fetch node: ", err.Error())
// // TODO find the reason for the DB error, skip this node for now
// continue
// }
// if !isMasterAdmin && !logic.UserHasNetworksAccess([]string{node.Network}, user) {
// continue
// }
// host.Nodes = append(host.Nodes, nid)
// }
// // add to the response only if has perms to some nodes / networks
// if len(host.Nodes) > 0 {
// ret = append(ret, host)
// }
//}
logic.SortApiHosts(apiHosts[:])
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(apiHosts)
@ -111,7 +80,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
hPU, err := logic.GetPeerUpdateForHost(context.Background(), "", host, nil, nil)
allNodes, err := logic.GetAllNodes()
if err != nil {
logger.Log(0, "could not pull peers for host", hostID)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
hPU, err := logic.GetPeerUpdateForHost("", host, allNodes, nil, nil)
if err != nil {
logger.Log(0, "could not pull peers for host", hostID)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

View file

@ -6,7 +6,6 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
)
// limit consts
@ -23,20 +22,13 @@ func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc {
Code: http.StatusForbidden, Message: "free tier limits exceeded on networks",
}
if logic.Free_Tier && servercfg.Is_EE { // check that free tier limits not exceeded
if logic.Free_Tier { // check that free tier limits not exceeded
if limit_choice == networks_l {
currentNetworks, err := logic.GetNetworks()
if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit {
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
} else if limit_choice == node_l {
nodes, err := logic.GetAllNodes()
if (err != nil && !database.IsEmptyRecord(err)) || len(nodes) >= logic.Node_Limit {
errorResponse.Message = "free tier limits exceeded on nodes"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
} else if limit_choice == users_l {
users, err := logic.GetUsers()
if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit {

View file

@ -1,7 +1,6 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -388,7 +387,14 @@ func getNode(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil, nil)
allNodes, err := logic.GetAllNodes()
if err != nil {
logger.Log(0, r.Header.Get("user"),
fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
hostPeerUpdate, err := logic.GetPeerUpdateForHost(node.Network, host, allNodes, nil, nil)
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, r.Header.Get("user"),
fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
@ -583,9 +589,13 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
if len(removedClients) > 0 {
host, err := logic.GetHost(node.HostID.String())
if err == nil {
allNodes, err := logic.GetAllNodes()
if err != nil {
return
}
go mq.PublishSingleHostPeerUpdate(
context.Background(),
host,
allNodes,
nil,
removedClients[:],
)
@ -730,10 +740,6 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
return
}
}
if err := logic.DeleteNode(&node, fromNode); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
return
}
if node.IsRelayed {
// cleanup node from relayednodes on relay node
relayNode, err := logic.GetNodeByID(node.RelayedBy)
@ -750,6 +756,15 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
}
}
if node.IsRelay {
// unset all the relayed nodes
logic.SetRelayedNodes(false, node.ID.String(), node.RelayedNodes)
}
if err := logic.DeleteNode(&node, fromNode); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
return
}
logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
if !fromNode { // notify node change

View file

@ -217,6 +217,7 @@ func TestNodeACLs(t *testing.T) {
}
func deleteAllNodes() {
logic.ClearNodeCache()
database.DeleteAllRecords(database.NODES_TABLE_NAME)
}

View file

@ -22,6 +22,38 @@ func serverHandlers(r *mux.Router) {
r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
}
func getUsage(w http.ResponseWriter, r *http.Request) {
type usage struct {
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Users int `json:"users"`
}
var serverUsage usage
hosts, err := logic.GetAllHosts()
if err == nil {
serverUsage.Hosts = len(hosts)
}
clients, err := logic.GetAllExtClients()
if err == nil {
serverUsage.Clients = len(clients)
}
users, err := logic.GetUsers()
if err == nil {
serverUsage.Users = len(users)
}
networks, err := logic.GetNetworks()
if err == nil {
serverUsage.Networks = len(networks)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.SuccessResponse{
Code: http.StatusOK,
Response: serverUsage,
})
}
// swagger:route GET /api/server/status server getStatus
@ -41,6 +73,12 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
type status struct {
DB bool `json:"db_connected"`
Broker bool `json:"broker_connected"`
Usage struct {
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Users int `json:"users"`
} `json:"usage"`
}
currentServerStatus := status{

View file

@ -19,6 +19,9 @@ var (
upgrader = websocket.Upgrader{}
)
// verifyJWT makes logic.VerifyJWT fakeable/mockable in tests
var verifyJWT = logic.VerifyJWT
func userHandlers(r *mux.Router) {
r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet)
@ -152,7 +155,7 @@ func getUser(w http.ResponseWriter, r *http.Request) {
var params = mux.Vars(r)
usernameFetched := params["username"]
user, err := logic.GetUser(usernameFetched)
user, err := logic.GetReturnUser(usernameFetched)
if err != nil {
logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error())
@ -230,7 +233,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
}
logger.Log(1, admin.UserName, "was made a new admin")
json.NewEncoder(w).Encode(admin)
json.NewEncoder(w).Encode(logic.ToReturnUser(admin))
}
// swagger:route POST /api/users/{username} user createUser
@ -264,7 +267,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
return
}
logger.Log(1, user.UserName, "was created")
json.NewEncoder(w).Encode(user)
json.NewEncoder(w).Encode(logic.ToReturnUser(user))
}
// swagger:route PUT /api/users/networks/{username} user updateUserNetworks
@ -314,12 +317,13 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
}
logger.Log(1, username, "status was updated")
// re-read and return the new user struct
if userChange, err = logic.GetUser(username); err != nil {
returnUser, err := logic.GetReturnUser(username)
if err != nil {
logger.Log(0, username, "failed to fetch user: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
json.NewEncoder(w).Encode(userChange)
json.NewEncoder(w).Encode(returnUser)
}
// swagger:route PUT /api/users/{username} user updateUser
@ -337,7 +341,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
// start here
jwtUser, _, isadmin, err := logic.VerifyJWT(r.Header.Get("Authorization"))
jwtUser, _, isadmin, err := verifyJWT(r.Header.Get("Authorization"))
if err != nil {
logger.Log(0, "verifyJWT error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@ -385,7 +389,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
return
}
logger.Log(1, username, "was updated")
json.NewEncoder(w).Encode(user)
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
// swagger:route PUT /api/users/{username}/adm user updateUserAdm
@ -409,7 +413,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if auth.IsOauthUser(user) != nil {
if auth.IsOauthUser(user) == nil {
err := fmt.Errorf("cannot update user info for oauth user %s", username)
logger.Log(0, err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
@ -436,7 +440,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
return
}
logger.Log(1, username, "was updated (admin)")
json.NewEncoder(w).Encode(user)
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
// swagger:route DELETE /api/users/{username} user deleteUser

View file

@ -1,6 +1,12 @@
package controller
import (
"bytes"
"github.com/go-jose/go-jose/v3/json"
"github.com/gorilla/mux"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
@ -19,6 +25,123 @@ func deleteAllUsers(t *testing.T) {
}
}
func TestGetUserNoHashedPassword(t *testing.T) {
// prepare existing user base
user := models.User{UserName: "freddie", Password: "password"}
haveOnlyOneUser(t, user)
// prepare request
rec, req := prepareUserRequest(t, models.User{}, user.UserName)
// test response
getUser(rec, req)
assertUserNameButNoPassword(t, rec.Body, user.UserName)
}
func TestCreateAdminNoHashedPassword(t *testing.T) {
// prepare existing user base
deleteAllUsers(t)
// prepare request
user := models.User{UserName: "jonathan", Password: "password"}
rec, req := prepareUserRequest(t, user, "")
// test response
createAdmin(rec, req)
assertUserNameButNoPassword(t, rec.Body, user.UserName)
}
func TestCreateUserNoHashedPassword(t *testing.T) {
// prepare existing user base
deleteAllUsers(t)
// prepare request
user := models.User{UserName: "jonathan", Password: "password"}
rec, req := prepareUserRequest(t, user, "")
// test response
createUser(rec, req)
assertUserNameButNoPassword(t, rec.Body, user.UserName)
}
func TestUpdateUserNetworksNoHashedPassword(t *testing.T) {
// prepare existing user base
user1 := models.User{UserName: "joestar", Password: "jonathan"}
haveOnlyOneUser(t, user1)
// prepare request
user2 := models.User{UserName: "joestar", Password: "joseph"}
rec, req := prepareUserRequest(t, user2, user1.UserName)
// test response
updateUserNetworks(rec, req)
assertUserNameButNoPassword(t, rec.Body, user1.UserName)
}
func TestUpdateUserNoHashedPassword(t *testing.T) {
// prepare existing user base
user1 := models.User{UserName: "dio", Password: "brando"}
haveOnlyOneUser(t, user1)
// prepare request
user2 := models.User{UserName: "giorno", Password: "giovanna"}
rec, req := prepareUserRequest(t, user2, user1.UserName)
// mock the jwt verification
oldVerify := verifyJWT
verifyJWT = func(bearerToken string) (username string, networks []string, isadmin bool, err error) {
return user1.UserName, user1.Networks, user1.IsAdmin, nil
}
defer func() { verifyJWT = oldVerify }()
// test response
updateUser(rec, req)
assertUserNameButNoPassword(t, rec.Body, user2.UserName)
}
func TestUpdateUserAdmNoHashedPassword(t *testing.T) {
// prepare existing user base
user1 := models.User{UserName: "dio", Password: "brando", IsAdmin: true}
haveOnlyOneUser(t, user1)
// prepare request
user2 := models.User{UserName: "giorno", Password: "giovanna"}
rec, req := prepareUserRequest(t, user2, user1.UserName)
// test response
updateUserAdm(rec, req)
assertUserNameButNoPassword(t, rec.Body, user2.UserName)
}
func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam string) (*httptest.ResponseRecorder, *http.Request) {
bits, err := json.Marshal(userForBody)
assert.Nil(t, err)
body := bytes.NewReader(bits)
rec := httptest.NewRecorder()
req := httptest.NewRequest("ANY", "https://example.com", body) // only the body matters here
req = mux.SetURLVars(req, map[string]string{"username": userNameForParam})
return rec, req
}
func haveOnlyOneUser(t *testing.T, user models.User) {
deleteAllUsers(t)
var err error
if user.IsAdmin {
err = logic.CreateAdmin(&user)
} else {
err = logic.CreateUser(&user)
}
assert.Nil(t, err)
}
func assertUserNameButNoPassword(t *testing.T, r io.Reader, userName string) {
var resp models.User
err := json.NewDecoder(r).Decode(&resp)
assert.Nil(t, err)
assert.Equal(t, userName, resp.UserName)
assert.Empty(t, resp.Password)
}
func TestHasAdmin(t *testing.T) {
// delete all current users
users, _ := logic.GetUsers()

View file

@ -26,12 +26,6 @@ https://api.{$NM_DOMAIN} {
reverse_proxy http://netmaker:8081
}
# STUN
https://stun.{$NM_DOMAIN} {
tls /root/certs/fullchain.pem /root/certs/privkey.pem
reverse_proxy netmaker:3478
}
# TURN
https://turn.{$NM_DOMAIN} {
tls /root/certs/fullchain.pem /root/certs/privkey.pem

View file

@ -44,12 +44,6 @@ https://api.{$NM_DOMAIN} {
reverse_proxy http://netmaker:8081
}
# STUN
https://stun.{$NM_DOMAIN} {
tls /root/certs/fullchain.pem /root/certs/privkey.pem
reverse_proxy netmaker:3478
}
# TURN
https://turn.{$NM_DOMAIN} {
tls /root/certs/fullchain.pem /root/certs/privkey.pem

View file

@ -87,6 +87,19 @@ func deleteRelay(w http.ResponseWriter, r *http.Request) {
logger.Log(1, "relayed node update ", relayedNode.ID.String(), "on network", relayedNode.Network, ": ", err.Error())
}
h, err := logic.GetHost(relayedNode.HostID.String())
if err == nil {
if h.OS == models.OS_Types.IoT {
nodes, err := logic.GetAllNodes()
if err != nil {
return
}
node.IsRelay = true // for iot update to recognise that it has to delete relay peer
if err = mq.PublishSingleHostPeerUpdate(h, nodes, &node, nil); err != nil {
logger.Log(1, "failed to publish peer update to host", h.ID.String(), ": ", err.Error())
}
}
}
}
mq.PublishPeerUpdate()
}()

View file

@ -16,6 +16,7 @@ import (
// InitEE - Initialize EE Logic
func InitEE() {
setIsEnterprise()
servercfg.Is_EE = true
models.SetLogo(retrieveEELogo())
controller.HttpHandlers = append(
controller.HttpHandlers,
@ -27,13 +28,8 @@ func InitEE() {
logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
// == License Handling ==
ValidateLicense()
if Limits.FreeTier {
logger.Log(0, "proceeding with Free Tier license")
logic.SetFreeTierForTelemetry(true)
} else {
logger.Log(0, "proceeding with Paid Tier license")
logic.SetFreeTierForTelemetry(false)
}
logger.Log(0, "proceeding with Paid Tier license")
logic.SetFreeTierForTelemetry(false)
// == End License Handling ==
AddLicenseHooks()
resetFailover()
@ -46,17 +42,6 @@ func InitEE() {
logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient
}
func setControllerLimits() {
logic.Node_Limit = Limits.Nodes
logic.Users_Limit = Limits.Users
logic.Clients_Limit = Limits.Clients
logic.Free_Tier = Limits.FreeTier
servercfg.Is_EE = true
if logic.Free_Tier {
logic.Networks_Limit = 3
}
}
func resetFailover() {
nets, err := logic.GetNetworks()
if err == nil {

View file

@ -9,12 +9,13 @@ import (
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"time"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/netclient/ncutils"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/crypto/nacl/box"
@ -31,8 +32,14 @@ type apiServerConf struct {
// AddLicenseHooks - adds the validation and cache clear hooks
func AddLicenseHooks() {
logic.AddHook(ValidateLicense)
logic.AddHook(ClearLicenseCache)
logic.HookManagerCh <- models.HookDetails{
Hook: ValidateLicense,
Interval: time.Hour,
}
logic.HookManagerCh <- models.HookDetails{
Hook: ClearLicenseCache,
Interval: time.Hour,
}
}
// ValidateLicense - the initial license check for netmaker server
@ -58,8 +65,8 @@ func ValidateLicense() error {
}
licenseSecret := LicenseSecret{
UserID: netmakerAccountID,
Limits: getCurrentServerLimit(),
AssociatedID: netmakerAccountID,
Limits: getCurrentServerLimit(),
}
secretData, err := json.Marshal(&licenseSecret)
@ -92,17 +99,6 @@ func ValidateLicense() error {
logger.FatalLog0(errValidation.Error())
}
Limits.Networks = math.MaxInt
Limits.FreeTier = license.FreeTier == "yes"
Limits.Clients = license.LimitClients
Limits.Nodes = license.LimitNodes
Limits.Servers = license.LimitServers
Limits.Users = license.LimitUsers
if Limits.FreeTier {
Limits.Networks = 3
}
setControllerLimits()
logger.Log(0, "License validation succeeded!")
return nil
}
@ -167,6 +163,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
}
msg := ValidateLicenseRequest{
LicenseKey: servercfg.GetLicenseKey(),
NmServerPubKey: base64encode(publicKeyBytes),
EncryptedPart: base64encode(encryptedData),
}
@ -180,9 +177,6 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
if err != nil {
return nil, err
}
reqParams := req.URL.Query()
reqParams.Add("licensevalue", servercfg.GetLicenseKey())
req.URL.RawQuery = reqParams.Encode()
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
client := &http.Client{}

View file

@ -3,7 +3,7 @@ package ee
import "fmt"
const (
api_endpoint = "https://api.controller.netmaker.io/api/v1/license/validate"
api_endpoint = "https://api.accounts.netmaker.io/api/v1/license/validate"
license_cache_key = "license_response_cache"
license_validation_err_msg = "invalid license"
server_id_key = "nm-server-id"
@ -11,38 +11,17 @@ const (
var errValidation = fmt.Errorf(license_validation_err_msg)
// Limits - limits to be referenced throughout server
var Limits = GlobalLimits{
Servers: 0,
Users: 0,
Nodes: 0,
Clients: 0,
Networks: 0,
FreeTier: false,
}
// GlobalLimits - struct for holding global limits on this netmaker server in memory
type GlobalLimits struct {
Servers int
Users int
Nodes int
Clients int
FreeTier bool
Networks int
}
// LicenseKey - the license key struct representation with associated data
type LicenseKey struct {
LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key
Expiration int64 `json:"expiration"`
LimitServers int `json:"limit_servers"`
LimitUsers int `json:"limit_users"`
LimitNodes int `json:"limit_nodes"`
LimitClients int `json:"limit_clients"`
Metadata string `json:"metadata"`
SubscriptionID string `json:"subscription_id"` // for a paid subscription (non-free-tier license)
FreeTier string `json:"free_tier"` // yes if free tier
IsActive string `json:"is_active"` // yes if active
LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key
Expiration int64 `json:"expiration"`
LimitServers int `json:"limit_servers"`
LimitUsers int `json:"limit_users"`
LimitHosts int `json:"limit_hosts"`
LimitNetworks int `json:"limit_networks"`
LimitClients int `json:"limit_clients"`
Metadata string `json:"metadata"`
IsActive bool `json:"is_active"` // yes if active
}
// ValidatedLicense - the validated license struct
@ -53,28 +32,31 @@ type ValidatedLicense struct {
// LicenseSecret - the encrypted struct for sending user-id
type LicenseSecret struct {
UserID string `json:"user_id" binding:"required"` // UUID for user foreign key to User table
Limits LicenseLimits `json:"limits" binding:"required"`
AssociatedID string `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
Limits LicenseLimits `json:"limits" binding:"required"`
}
// LicenseLimits - struct license limits
type LicenseLimits struct {
Servers int `json:"servers" binding:"required"`
Users int `json:"users" binding:"required"`
Nodes int `json:"nodes" binding:"required"`
Clients int `json:"clients" binding:"required"`
Servers int `json:"servers"`
Users int `json:"users"`
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
}
// LicenseLimits.SetDefaults - sets the default values for limits
func (l *LicenseLimits) SetDefaults() {
l.Clients = 0
l.Servers = 1
l.Nodes = 0
l.Hosts = 0
l.Users = 1
l.Networks = 0
}
// ValidateLicenseRequest - used for request to validate license endpoint
type ValidateLicenseRequest struct {
LicenseKey string `json:"license_key" binding:"required"`
NmServerPubKey string `json:"nm_server_pub_key" binding:"required"` // Netmaker server public key used to send data back to Netmaker for the Netmaker server to decrypt (eg output from validating license)
EncryptedPart string `json:"secret" binding:"required"`
}

View file

@ -30,12 +30,11 @@ func base64decode(input string) []byte {
return bytes
}
func getCurrentServerLimit() (limits LicenseLimits) {
limits.SetDefaults()
nodes, err := logic.GetAllNodes()
hosts, err := logic.GetAllHosts()
if err == nil {
limits.Nodes = len(nodes)
limits.Hosts = len(hosts)
}
clients, err := logic.GetAllExtClients()
if err == nil {
@ -45,5 +44,9 @@ func getCurrentServerLimit() (limits LicenseLimits) {
if err == nil {
limits.Users = len(users)
}
networks, err := logic.GetNetworks()
if err == nil {
limits.Networks = len(networks)
}
return
}

10
go.mod
View file

@ -15,11 +15,11 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4
github.com/txn2/txeh v1.4.0
golang.org/x/crypto v0.9.0
golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.8.0
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/crypto v0.10.0
golang.org/x/net v0.11.0 // indirect
golang.org/x/oauth2 v0.9.0
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
google.golang.org/protobuf v1.28.1 // indirect

20
go.sum
View file

@ -121,8 +121,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -133,10 +133,10 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -152,8 +152,8 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -161,8 +161,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

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

View file

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

View file

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

View file

@ -2,24 +2,57 @@ package acls
import (
"encoding/json"
"sync"
"github.com/gravitl/netmaker/database"
"golang.org/x/exp/slog"
)
var (
aclCacheMutex = &sync.RWMutex{}
aclCacheMap = make(map[ContainerID]ACLContainer)
aclMutex = &sync.RWMutex{}
)
func fetchAclContainerFromCache(containerID ContainerID) (aclCont ACLContainer, ok bool) {
aclCacheMutex.RLock()
aclCont, ok = aclCacheMap[containerID]
aclCacheMutex.RUnlock()
return
}
func storeAclContainerInCache(containerID ContainerID, aclContainer ACLContainer) {
aclCacheMutex.Lock()
aclCacheMap[containerID] = aclContainer
aclCacheMutex.Unlock()
}
func DeleteAclFromCache(containerID ContainerID) {
aclCacheMutex.Lock()
delete(aclCacheMap, containerID)
aclCacheMutex.Unlock()
}
// == type functions ==
// ACL.Allow - allows access by ID in memory
func (acl ACL) Allow(ID AclID) {
aclMutex.Lock()
defer aclMutex.Unlock()
acl[ID] = Allowed
}
// ACL.DisallowNode - disallows access by ID in memory
func (acl ACL) Disallow(ID AclID) {
aclMutex.Lock()
defer aclMutex.Unlock()
acl[ID] = NotAllowed
}
// ACL.Remove - removes a node from a ACL in memory
func (acl ACL) Remove(ID AclID) {
aclMutex.Lock()
defer aclMutex.Unlock()
delete(acl, ID)
}
@ -29,29 +62,47 @@ func (acl ACL) Save(containerID ContainerID, ID AclID) (ACL, error) {
}
// ACL.IsAllowed - sees if ID is allowed in referring ACL
func (acl ACL) IsAllowed(ID AclID) bool {
return acl[ID] == Allowed
}
// ACLContainer.IsAllowed - returns if the current ACL container contains allowed ACLs between two IDs
func (aclContainer ACLContainer) IsAllowed(ID1, ID2 AclID) bool {
return aclContainer[ID1].IsAllowed(ID2) && aclContainer[ID2].IsAllowed(ID1)
func (acl ACL) IsAllowed(ID AclID) (allowed bool) {
aclMutex.RLock()
allowed = acl[ID] == Allowed
aclMutex.RUnlock()
return
}
// ACLContainer.UpdateACL - saves the state of a ACL in the ACLContainer in memory
func (aclContainer ACLContainer) UpdateACL(ID AclID, acl ACL) ACLContainer {
aclMutex.Lock()
defer aclMutex.Unlock()
aclContainer[ID] = acl
return aclContainer
}
// ACLContainer.RemoveACL - removes the state of a ACL in the ACLContainer in memory
func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
aclMutex.Lock()
defer aclMutex.Unlock()
delete(aclContainer, ID)
return aclContainer
}
// ACLContainer.ChangeAccess - changes the relationship between two nodes in memory
func (networkACL ACLContainer) ChangeAccess(ID1, ID2 AclID, value byte) {
if _, ok := networkACL[ID1]; !ok {
slog.Error("ACL missing for ", "id", ID1)
return
}
if _, ok := networkACL[ID2]; !ok {
slog.Error("ACL missing for ", "id", ID2)
return
}
if _, ok := networkACL[ID1][ID2]; !ok {
slog.Error("ACL missing for ", "id1", ID1, "id2", ID2)
return
}
if _, ok := networkACL[ID2][ID1]; !ok {
slog.Error("ACL missing for ", "id2", ID2, "id1", ID1)
return
}
networkACL[ID1][ID2] = value
networkACL[ID2][ID1] = value
}
@ -75,6 +126,11 @@ func (aclContainer ACLContainer) Get(containerID ContainerID) (ACLContainer, err
// fetchACLContainer - fetches all current rules in given ACL container
func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
aclMutex.RLock()
defer aclMutex.RUnlock()
if aclContainer, ok := fetchAclContainerFromCache(containerID); ok {
return aclContainer, nil
}
aclJson, err := fetchACLContainerJson(ContainerID(containerID))
if err != nil {
return nil, err
@ -83,6 +139,7 @@ func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
if err := json.Unmarshal([]byte(aclJson), &currentNetworkACL); err != nil {
return nil, err
}
storeAclContainerInCache(containerID, currentNetworkACL)
return currentNetworkACL, nil
}
@ -109,10 +166,18 @@ func upsertACL(containerID ContainerID, ID AclID, acl ACL) (ACL, error) {
// upsertACLContainer - Inserts or updates a network ACL given the json string of the ACL and the container ID
// if nil, create it
func upsertACLContainer(containerID ContainerID, aclContainer ACLContainer) (ACLContainer, error) {
aclMutex.Lock()
defer aclMutex.Unlock()
if aclContainer == nil {
aclContainer = make(ACLContainer)
}
return aclContainer, database.Insert(string(containerID), string(convertNetworkACLtoACLJson(aclContainer)), database.NODE_ACLS_TABLE_NAME)
err := database.Insert(string(containerID), string(convertNetworkACLtoACLJson(aclContainer)), database.NODE_ACLS_TABLE_NAME)
if err != nil {
return aclContainer, err
}
storeAclContainerInCache(containerID, aclContainer)
return aclContainer, nil
}
func convertNetworkACLtoACLJson(networkACL ACLContainer) ACLJson {

View file

@ -83,5 +83,10 @@ func RemoveNodeACL(networkID NetworkID, nodeID NodeID) (acls.ACLContainer, error
// DeleteACLContainer - removes an ACLContainer state from db
func DeleteACLContainer(network NetworkID) error {
return database.DeleteRecord(database.NODE_ACLS_TABLE_NAME, string(network))
err := database.DeleteRecord(database.NODE_ACLS_TABLE_NAME, string(network))
if err != nil {
return err
}
acls.DeleteAclFromCache(acls.ContainerID(network))
return nil
}

View file

@ -42,20 +42,6 @@ func HasAdmin() (bool, error) {
return false, err
}
// GetReturnUser - gets a user
func GetReturnUser(username string) (models.ReturnUser, error) {
var user models.ReturnUser
record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
if err != nil {
return user, err
}
if err = json.Unmarshal([]byte(record), &user); err != nil {
return models.ReturnUser{}, err
}
return user, err
}
// GetUsers - gets users
func GetUsers() ([]models.ReturnUser, error) {

View file

@ -69,16 +69,12 @@ func GetNodeDNS(network string) ([]models.DNSEntry, error) {
var dns []models.DNSEntry
collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
nodes, err := GetNetworkNodes(network)
if err != nil {
return dns, err
}
for _, value := range collection {
var node models.Node
if err = json.Unmarshal([]byte(value), &node); err != nil {
continue
}
for _, node := range nodes {
if node.Network != network {
continue
}

View file

@ -3,58 +3,56 @@ package logic
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// GetExtPeersList - gets the ext peers lists
func GetExtPeersList(node *models.Node) ([]models.ExtPeersResponse, error) {
var (
extClientCacheMutex = &sync.RWMutex{}
extClientCacheMap = make(map[string]models.ExtClient)
)
var peers []models.ExtPeersResponse
records, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME)
if err != nil {
return peers, err
func getAllExtClientsFromCache() (extClients []models.ExtClient) {
extClientCacheMutex.RLock()
for _, extclient := range extClientCacheMap {
extClients = append(extClients, extclient)
}
extClientCacheMutex.RUnlock()
return
}
for _, value := range records {
var peer models.ExtPeersResponse
var extClient models.ExtClient
err = json.Unmarshal([]byte(value), &peer)
if err != nil {
logger.Log(2, "failed to unmarshal peer when getting ext peer list")
continue
}
err = json.Unmarshal([]byte(value), &extClient)
if err != nil {
logger.Log(2, "failed to unmarshal ext client")
continue
}
func deleteExtClientFromCache(key string) {
extClientCacheMutex.Lock()
delete(extClientCacheMap, key)
extClientCacheMutex.Unlock()
}
if extClient.Enabled && extClient.Network == node.Network && extClient.IngressGatewayID == node.ID.String() {
peers = append(peers, peer)
}
}
return peers, err
func getExtClientFromCache(key string) (extclient models.ExtClient, ok bool) {
extClientCacheMutex.RLock()
extclient, ok = extClientCacheMap[key]
extClientCacheMutex.RUnlock()
return
}
func storeExtClientInCache(key string, extclient models.ExtClient) {
extClientCacheMutex.Lock()
extClientCacheMap[key] = extclient
extClientCacheMutex.Unlock()
}
// ExtClient.GetEgressRangesOnNetwork - returns the egress ranges on network of ext client
func GetEgressRangesOnNetwork(client *models.ExtClient) ([]string, error) {
var result []string
nodesData, err := database.FetchRecords(database.NODES_TABLE_NAME)
networkNodes, err := GetNetworkNodes(client.Network)
if err != nil {
return []string{}, err
}
for _, nodeData := range nodesData {
var currentNode models.Node
if err = json.Unmarshal([]byte(nodeData), &currentNode); err != nil {
continue
}
for _, currentNode := range networkNodes {
if currentNode.Network != client.Network {
continue
}
@ -75,13 +73,25 @@ func DeleteExtClient(network string, clientid string) error {
return err
}
err = database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, key)
return err
if err != nil {
return err
}
deleteExtClientFromCache(key)
return nil
}
// GetNetworkExtClients - gets the ext clients of given network
func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
var extclients []models.ExtClient
allextclients := getAllExtClientsFromCache()
if len(allextclients) != 0 {
for _, extclient := range allextclients {
if extclient.Network == network {
extclients = append(extclients, extclient)
}
}
return extclients, nil
}
records, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME)
if err != nil {
return extclients, err
@ -92,6 +102,10 @@ func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
if err != nil {
continue
}
key, err := GetRecordKey(extclient.ClientID, network)
if err == nil {
storeExtClientInCache(key, extclient)
}
if extclient.Network == network {
extclients = append(extclients, extclient)
}
@ -106,12 +120,15 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
if err != nil {
return extclient, err
}
if extclient, ok := getExtClientFromCache(key); ok {
return extclient, nil
}
data, err := database.FetchRecord(database.EXT_CLIENT_TABLE_NAME, key)
if err != nil {
return extclient, err
}
err = json.Unmarshal([]byte(data), &extclient)
storeExtClientInCache(key, extclient)
return extclient, err
}
@ -190,6 +207,7 @@ func SaveExtClient(extclient *models.ExtClient) error {
if err = database.Insert(key, string(data), database.EXT_CLIENT_TABLE_NAME); err != nil {
return err
}
storeExtClientInCache(key, *extclient)
return SetNetworkNodesLastModified(extclient.Network)
}

View file

@ -1,7 +1,6 @@
package logic
import (
"encoding/json"
"errors"
"fmt"
"time"
@ -53,11 +52,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway
node.SetLastModified()
nodeData, err := json.Marshal(&node)
if err != nil {
return node, err
}
if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil {
if err = UpsertNode(&node); err != nil {
return models.Node{}, err
}
return node, nil
@ -84,12 +79,7 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
node.EgressGatewayRanges = []string{}
node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
node.SetLastModified()
data, err := json.Marshal(&node)
if err != nil {
return models.Node{}, err
}
if err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
if err = UpsertNode(&node); err != nil {
return models.Node{}, err
}
return node, nil
@ -115,9 +105,6 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
if host.FirewallInUse == models.FIREWALL_NONE {
return models.Node{}, errors.New("firewall is not supported for ingress gateways")
}
if host.NatType != models.NAT_Types.Public {
return models.Node{}, errors.New("ingress cannot be created on nodes behind NAT")
}
network, err := GetParentNetwork(netid)
if err != nil {
@ -131,11 +118,7 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
if ingress.Failover && servercfg.Is_EE {
node.Failover = true
}
data, err := json.Marshal(&node)
if err != nil {
return models.Node{}, err
}
err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
err = UpsertNode(&node)
if err != nil {
return models.Node{}, err
}
@ -176,12 +159,7 @@ func DeleteIngressGateway(networkName string, nodeid string) (models.Node, bool,
node.EgressGatewayRequest.NodeID, node.EgressGatewayRequest.NetID, err))
}
}
data, err := json.Marshal(&node)
if err != nil {
return models.Node{}, false, removedClients, err
}
err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
err = UpsertNode(&node)
if err != nil {
return models.Node{}, wasFailover, removedClients, err
}

View file

@ -10,6 +10,7 @@ import (
"net/http"
"sort"
"strconv"
"sync"
"github.com/devilcove/httpclient"
"github.com/google/uuid"
@ -20,6 +21,11 @@ import (
"golang.org/x/crypto/bcrypt"
)
var (
hostCacheMutex = &sync.RWMutex{}
hostsCacheMap = make(map[string]models.Host)
)
var (
// ErrHostExists error indicating that host exists when trying to create new host
ErrHostExists error = errors.New("host already exists")
@ -27,6 +33,46 @@ var (
ErrInvalidHostID error = errors.New("invalid host id")
)
func getHostsFromCache() (hosts []models.Host) {
hostCacheMutex.RLock()
for _, host := range hostsCacheMap {
hosts = append(hosts, host)
}
hostCacheMutex.RUnlock()
return
}
func getHostsMapFromCache() (hostsMap map[string]models.Host) {
hostCacheMutex.RLock()
hostsMap = hostsCacheMap
hostCacheMutex.RUnlock()
return
}
func getHostFromCache(hostID string) (host models.Host, ok bool) {
hostCacheMutex.RLock()
host, ok = hostsCacheMap[hostID]
hostCacheMutex.RUnlock()
return
}
func storeHostInCache(h models.Host) {
hostCacheMutex.Lock()
hostsCacheMap[h.ID.String()] = h
hostCacheMutex.Unlock()
}
func deleteHostFromCache(hostID string) {
hostCacheMutex.Lock()
delete(hostsCacheMap, hostID)
hostCacheMutex.Unlock()
}
func loadHostsIntoCache(hMap map[string]models.Host) {
hostCacheMutex.Lock()
hostsCacheMap = hMap
hostCacheMutex.Unlock()
}
const (
maxPort = 1<<16 - 1
minPort = 1025
@ -34,17 +80,28 @@ const (
// GetAllHosts - returns all hosts in flat list or error
func GetAllHosts() ([]models.Host, error) {
currHostMap, err := GetHostsMap()
if err != nil {
currHosts := getHostsFromCache()
if len(currHosts) != 0 {
return currHosts, nil
}
records, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
if err != nil && !database.IsEmptyRecord(err) {
return nil, err
}
var currentHosts = []models.Host{}
for k := range currHostMap {
var h = *currHostMap[k]
currentHosts = append(currentHosts, h)
currHostsMap := make(map[string]models.Host)
defer loadHostsIntoCache(currHostsMap)
for k := range records {
var h models.Host
err = json.Unmarshal([]byte(records[k]), &h)
if err != nil {
return nil, err
}
currHosts = append(currHosts, h)
currHostsMap[h.ID.String()] = h
}
return currentHosts, nil
return currHosts, nil
}
// GetAllHostsAPI - get's all the hosts in an API usable format
@ -58,19 +115,24 @@ func GetAllHostsAPI(hosts []models.Host) []models.ApiHost {
}
// GetHostsMap - gets all the current hosts on machine in a map
func GetHostsMap() (map[string]*models.Host, error) {
func GetHostsMap() (map[string]models.Host, error) {
hostsMap := getHostsMapFromCache()
if len(hostsMap) != 0 {
return hostsMap, nil
}
records, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
if err != nil && !database.IsEmptyRecord(err) {
return nil, err
}
currHostMap := make(map[string]*models.Host)
currHostMap := make(map[string]models.Host)
defer loadHostsIntoCache(currHostMap)
for k := range records {
var h models.Host
err = json.Unmarshal([]byte(records[k]), &h)
if err != nil {
return nil, err
}
currHostMap[h.ID.String()] = &h
currHostMap[h.ID.String()] = h
}
return currHostMap, nil
@ -78,6 +140,10 @@ func GetHostsMap() (map[string]*models.Host, error) {
// GetHost - gets a host from db given id
func GetHost(hostid string) (*models.Host, error) {
if host, ok := getHostFromCache(hostid); ok {
return &host, nil
}
record, err := database.FetchRecord(database.HOSTS_TABLE_NAME, hostid)
if err != nil {
return nil, err
@ -87,13 +153,20 @@ func GetHost(hostid string) (*models.Host, error) {
if err = json.Unmarshal([]byte(record), &h); err != nil {
return nil, err
}
storeHostInCache(h)
return &h, nil
}
// CreateHost - creates a host if not exist
func CreateHost(h *models.Host) error {
_, err := GetHost(h.ID.String())
hosts, err := GetAllHosts()
if err != nil && !database.IsEmptyRecord(err) {
return err
}
if len(hosts) >= Hosts_Limit {
return errors.New("free tier limits exceeded on hosts")
}
_, err = GetHost(h.ID.String())
if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
return ErrHostExists
}
@ -111,7 +184,6 @@ func CreateHost(h *models.Host) error {
}
h.HostPass = string(hash)
h.AutoUpdate = servercfg.AutoUpdateEnabled()
h.EndpointDetection = servercfg.EndpointDetectionEnabled()
// if another server has already updated proxyenabled, leave it alone
if !h.ProxyEnabledSet {
log.Println("checking default proxy", servercfg.GetServerConfig().DefaultProxyMode)
@ -215,8 +287,12 @@ func UpsertHost(h *models.Host) error {
if err != nil {
return err
}
return database.Insert(h.ID.String(), string(data), database.HOSTS_TABLE_NAME)
err = database.Insert(h.ID.String(), string(data), database.HOSTS_TABLE_NAME)
if err != nil {
return err
}
storeHostInCache(*h)
return nil
}
// RemoveHost - removes a given host from server
@ -227,8 +303,12 @@ func RemoveHost(h *models.Host) error {
if servercfg.IsUsingTurn() {
DeRegisterHostWithTurn(h.ID.String())
}
return database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String())
err := database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String())
if err != nil {
return err
}
deleteHostFromCache(h.ID.String())
return nil
}
// RemoveHostByID - removes a given host by id from server
@ -236,7 +316,13 @@ func RemoveHostByID(hostID string) error {
if servercfg.IsUsingTurn() {
DeRegisterHostWithTurn(hostID)
}
return database.DeleteRecord(database.HOSTS_TABLE_NAME, hostID)
err := database.DeleteRecord(database.HOSTS_TABLE_NAME, hostID)
if err != nil {
return err
}
deleteHostFromCache(hostID)
return nil
}
// UpdateHostNetwork - adds/deletes host from a network

View file

@ -115,24 +115,8 @@ func CreateNetwork(network models.Network) (models.Network, error) {
// GetNetworkNonServerNodeCount - get number of network non server nodes
func GetNetworkNonServerNodeCount(networkName string) (int, error) {
collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
count := 0
if err != nil && !database.IsEmptyRecord(err) {
return count, err
}
for _, value := range collection {
var node models.Node
if err = json.Unmarshal([]byte(value), &node); err != nil {
return count, err
} else {
if node.Network == networkName {
count++
}
}
}
return count, nil
nodes, err := GetNetworkNodes(networkName)
return len(nodes), err
}
// GetParentNetwork - get parent network
@ -210,18 +194,12 @@ func UniqueAddress(networkName string, reverse bool) (net.IP, error) {
func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
isunique := true
collection, err := database.FetchRecords(tableName)
if err != nil {
return isunique
}
for _, value := range collection { // filter
if tableName == database.NODES_TABLE_NAME {
var node models.Node
if err = json.Unmarshal([]byte(value), &node); err != nil {
continue
}
if tableName == database.NODES_TABLE_NAME {
nodes, err := GetNetworkNodes(network)
if err != nil {
return isunique
}
for _, node := range nodes {
if isIpv6 {
if node.Address6.IP.String() == ip && node.Network == network {
return false
@ -231,11 +209,15 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
return false
}
}
} else if tableName == database.EXT_CLIENT_TABLE_NAME {
var extClient models.ExtClient
if err = json.Unmarshal([]byte(value), &extClient); err != nil {
continue
}
}
} else if tableName == database.EXT_CLIENT_TABLE_NAME {
extClients, err := GetNetworkExtClients(network)
if err != nil {
return isunique
}
for _, extClient := range extClients { // filter
if isIpv6 {
if (extClient.Address6 == ip) && extClient.Network == network {
return false
@ -247,7 +229,6 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
}
}
}
}
return isunique
@ -298,149 +279,6 @@ func UniqueAddress6(networkName string, reverse bool) (net.IP, error) {
return add, errors.New("ERROR: No unique IPv6 addresses available. Check network subnet")
}
// UpdateNetworkLocalAddresses - updates network localaddresses
func UpdateNetworkLocalAddresses(networkName string) error {
collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
if err != nil {
return err
}
for _, value := range collection {
var node models.Node
err := json.Unmarshal([]byte(value), &node)
if err != nil {
fmt.Println("error in node address assignment!")
return err
}
if node.Network == networkName {
var ipaddr net.IP
var iperr error
ipaddr, iperr = UniqueAddress(networkName, false)
if iperr != nil {
fmt.Println("error in node address assignment!")
return iperr
}
node.Address.IP = ipaddr
newNodeData, err := json.Marshal(&node)
if err != nil {
logger.Log(1, "error in node address assignment!")
return err
}
database.Insert(node.ID.String(), string(newNodeData), database.NODES_TABLE_NAME)
}
}
return nil
}
// RemoveNetworkNodeIPv6Addresses - removes network node IPv6 addresses
func RemoveNetworkNodeIPv6Addresses(networkName string) error {
collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
if err != nil {
return err
}
for _, value := range collections {
var node models.Node
err := json.Unmarshal([]byte(value), &node)
if err != nil {
fmt.Println("error in node address assignment!")
return err
}
if node.Network == networkName {
node.Address6.IP = nil
data, err := json.Marshal(&node)
if err != nil {
return err
}
database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
}
}
return nil
}
// UpdateNetworkNodeAddresses - updates network node addresses
func UpdateNetworkNodeAddresses(networkName string) error {
collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
if err != nil {
return err
}
for _, value := range collections {
var node models.Node
err := json.Unmarshal([]byte(value), &node)
if err != nil {
logger.Log(1, "error in node ipv4 address assignment!")
return err
}
if node.Network == networkName {
var ipaddr net.IP
var iperr error
ipaddr, iperr = UniqueAddress(networkName, false)
if iperr != nil {
logger.Log(1, "error in node ipv4 address assignment!")
return iperr
}
node.Address.IP = ipaddr
data, err := json.Marshal(&node)
if err != nil {
return err
}
database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
}
}
return nil
}
// UpdateNetworkNodeAddresses6 - updates network node addresses
func UpdateNetworkNodeAddresses6(networkName string) error {
collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
if err != nil {
return err
}
for _, value := range collections {
var node models.Node
err := json.Unmarshal([]byte(value), &node)
if err != nil {
logger.Log(1, "error in node ipv6 address assignment!")
return err
}
if node.Network == networkName {
var ipaddr net.IP
var iperr error
ipaddr, iperr = UniqueAddress6(networkName, false)
if iperr != nil {
logger.Log(1, "error in node ipv6 address assignment!")
return iperr
}
node.Address6.IP = ipaddr
data, err := json.Marshal(&node)
if err != nil {
return err
}
database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
}
}
return nil
}
// IsNetworkNameUnique - checks to see if any other networks have the same name (id)
func IsNetworkNameUnique(network *models.Network) (bool, error) {

View file

@ -6,6 +6,7 @@ import (
"fmt"
"net"
"sort"
"sync"
"time"
validator "github.com/go-playground/validator/v10"
@ -17,11 +18,53 @@ import (
"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"
"github.com/gravitl/netmaker/validation"
)
var (
nodeCacheMutex = &sync.RWMutex{}
nodesCacheMap = make(map[string]models.Node)
)
func getNodeFromCache(nodeID string) (node models.Node, ok bool) {
nodeCacheMutex.RLock()
node, ok = nodesCacheMap[nodeID]
nodeCacheMutex.RUnlock()
return
}
func getNodesFromCache() (nodes []models.Node) {
nodeCacheMutex.RLock()
for _, node := range nodesCacheMap {
nodes = append(nodes, node)
}
nodeCacheMutex.RUnlock()
return
}
func deleteNodeFromCache(nodeID string) {
nodeCacheMutex.Lock()
delete(nodesCacheMap, nodeID)
nodeCacheMutex.Unlock()
}
func storeNodeInCache(node models.Node) {
nodeCacheMutex.Lock()
nodesCacheMap[node.ID.String()] = node
nodeCacheMutex.Unlock()
}
func loadNodesIntoCache(nMap map[string]models.Node) {
nodeCacheMutex.Lock()
nodesCacheMap = nMap
nodeCacheMutex.Unlock()
}
func ClearNodeCache() {
nodeCacheMutex.Lock()
nodesCacheMap = make(map[string]models.Node)
nodeCacheMutex.Unlock()
}
const (
// RELAY_NODE_ERR - error to return if relay node is unfound
RELAY_NODE_ERR = "could not find relay for node"
@ -72,7 +115,12 @@ func UpdateNodeCheckin(node *models.Node) error {
if err != nil {
return err
}
return database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
if err != nil {
return err
}
storeNodeInCache(*node)
return nil
}
// UpsertNode - updates node in the DB
@ -82,7 +130,12 @@ func UpsertNode(newNode *models.Node) error {
if err != nil {
return err
}
return database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
err = database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
if err != nil {
return err
}
storeNodeInCache(*newNode)
return nil
}
// UpdateNode - takes a node and updates another node with it's values
@ -114,7 +167,12 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
if data, err := json.Marshal(newNode); err != nil {
return err
} else {
return database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
err = database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
if err != nil {
return err
}
storeNodeInCache(*newNode)
return nil
}
}
@ -172,6 +230,7 @@ func deleteNodeByID(node *models.Node) error {
return err
}
}
deleteNodeFromCache(node.ID.String())
if servercfg.IsDNSMode() {
SetDNS()
}
@ -237,7 +296,12 @@ func IsFailoverPresent(network string) bool {
// GetAllNodes - returns all nodes in the DB
func GetAllNodes() ([]models.Node, error) {
var nodes []models.Node
nodes = getNodesFromCache()
if len(nodes) != 0 {
return nodes, nil
}
nodesMap := make(map[string]models.Node)
defer loadNodesIntoCache(nodesMap)
collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
if err != nil {
if database.IsEmptyRecord(err) {
@ -255,6 +319,7 @@ func GetAllNodes() ([]models.Node, error) {
}
// add node to our array
nodes = append(nodes, node)
nodesMap[node.ID.String()] = node
}
return nodes, nil
@ -309,46 +374,10 @@ func GetRecordKey(id string, network string) (string, error) {
return id + "###" + network, nil
}
// GetNodesByAddress - gets a node by mac address
func GetNodesByAddress(network string, addresses []string) ([]models.Node, error) {
var nodes []models.Node
allnodes, err := GetAllNodes()
if err != nil {
return []models.Node{}, err
}
for _, node := range allnodes {
if node.Network == network && ncutils.StringSliceContains(addresses, node.Address.String()) {
nodes = append(nodes, node)
}
}
return nodes, nil
}
// GetDeletedNodeByMacAddress - get a deleted node
func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node, error) {
var node models.Node
key, err := GetRecordKey(macaddress, network)
if err != nil {
return node, err
}
record, err := database.FetchRecord(database.DELETED_NODES_TABLE_NAME, key)
if err != nil {
return models.Node{}, err
}
if err = json.Unmarshal([]byte(record), &node); err != nil {
return models.Node{}, err
}
SetNodeDefaults(&node)
return node, nil
}
func GetNodeByID(uuid string) (models.Node, error) {
if node, ok := getNodeFromCache(uuid); ok {
return node, nil
}
var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid)
if err != nil {
return models.Node{}, err
@ -357,6 +386,7 @@ func GetNodeByID(uuid string) (models.Node, error) {
if err = json.Unmarshal([]byte(record), &node); err != nil {
return models.Node{}, err
}
storeNodeInCache(node)
return node, nil
}
@ -506,7 +536,7 @@ func createNode(node *models.Node) error {
if err != nil {
return err
}
storeNodeInCache(*node)
_, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), defaultACLVal)
if err != nil {
logger.Log(1, "failed to create node ACL for node,", node.ID.String(), "err:", err.Error())

View file

@ -1,9 +1,7 @@
package logic
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
@ -13,18 +11,12 @@ import (
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/exp/slices"
"golang.org/x/exp/slog"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var (
// PeerUpdateCtx context to send to host peer updates
PeerUpdateCtx context.Context
// PeerUpdateStop - the cancel for PeerUpdateCtx
PeerUpdateStop context.CancelFunc
)
// GetProxyUpdateForHost - gets the proxy update for host
func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.ProxyManagerPayload, error) {
func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error) {
proxyPayload := models.ProxyManagerPayload{
Action: models.ProxyUpdate,
}
@ -84,24 +76,12 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
return proxyPayload, nil
}
// ResetPeerUpdateContext - kills any current peer updates and resets the context
func ResetPeerUpdateContext() {
if PeerUpdateCtx != nil && PeerUpdateStop != nil {
PeerUpdateStop() // tell any current peer updates to stop
}
PeerUpdateCtx, PeerUpdateStop = context.WithCancel(context.Background())
}
// GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node, deletedClients []models.ExtClient) (models.HostPeerUpdate, error) {
func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.Node, deletedNode *models.Node, deletedClients []models.ExtClient) (models.HostPeerUpdate, error) {
if host == nil {
return models.HostPeerUpdate{}, errors.New("host is nil")
}
allNodes, err := GetAllNodes()
if err != nil {
return models.HostPeerUpdate{}, err
}
// track which nodes are deleted
// after peer calculation, if peer not in list, add delete config of peer
hostPeerUpdate := models.HostPeerUpdate{
@ -121,9 +101,8 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
}
// endpoint detection always comes from the server
hostPeerUpdate.Host.EndpointDetection = servercfg.EndpointDetectionEnabled()
logger.Log(1, "peer update for host", host.ID.String())
hostPeerUpdate.EndpointDetection = servercfg.EndpointDetectionEnabled()
slog.Debug("peer update for host", "hostId", host.ID.String())
peerIndexMap := make(map[string]int)
for _, nodeID := range host.Nodes {
nodeID := nodeID
@ -134,79 +113,25 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
continue
}
currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
var nodePeerMap map[string]models.PeerRouteInfo
if node.IsIngressGateway || node.IsEgressGateway {
nodePeerMap = make(map[string]models.PeerRouteInfo)
}
for _, peer := range currentPeers {
select {
case <-ctx.Done():
logger.Log(2, "cancelled peer update for host", host.Name, host.ID.String())
return models.HostPeerUpdate{}, fmt.Errorf("peer update cancelled")
default:
peer := peer
if peer.ID.String() == node.ID.String() {
logger.Log(2, "peer update, skipping self")
//skip yourself
continue
}
peerHost, err := GetHost(peer.HostID.String())
if host.OS == models.OS_Types.IoT {
hostPeerUpdate.NodeAddrs = append(hostPeerUpdate.NodeAddrs, node.PrimaryAddressIPNet())
if node.IsRelayed {
relayNode, err := GetNodeByID(node.RelayedBy)
if err != nil {
logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
return models.HostPeerUpdate{}, err
}
peerConfig := wgtypes.PeerConfig{
PublicKey: peerHost.PublicKey,
PersistentKeepaliveInterval: &peer.PersistentKeepalive,
ReplaceAllowedIPs: true,
}
if node.IsIngressGateway || node.IsEgressGateway {
if peer.IsIngressGateway {
_, extPeerIDAndAddrs, err := getExtPeers(&peer)
if err == nil {
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
PeerKey: extPeerIdAndAddr.ID,
Allow: true,
ID: extPeerIdAndAddr.ID,
}
}
}
}
if node.IsIngressGateway && peer.IsEgressGateway {
hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
peer.EgressGatewayRanges...)
}
nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(peer.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(peer.PrimaryAddress()),
},
PeerKey: peerHost.PublicKey.String(),
Allow: true,
ID: peer.ID.String(),
}
}
if (node.IsRelayed && node.RelayedBy != peer.ID.String()) || (peer.IsRelayed && peer.RelayedBy != node.ID.String()) {
// if node is relayed and peer is not the relay, set remove to true
if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; ok {
continue
}
peerConfig.Remove = true
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
continue
}
relayHost, err := GetHost(relayNode.HostID.String())
if err != nil {
continue
}
relayPeer := wgtypes.PeerConfig{
PublicKey: relayHost.PublicKey,
PersistentKeepaliveInterval: &relayNode.PersistentKeepalive,
ReplaceAllowedIPs: true,
AllowedIPs: GetAllowedIPs(&node, &relayNode, nil),
}
uselocal := false
if host.EndpointIP.String() == peerHost.EndpointIP.String() {
if host.EndpointIP.String() == relayHost.EndpointIP.String() {
// peer is on same network
// set to localaddress
uselocal = true
@ -214,76 +139,180 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
// use public endpint
uselocal = false
}
if node.LocalAddress.String() == peer.LocalAddress.String() {
if node.LocalAddress.String() == relayNode.LocalAddress.String() {
uselocal = false
}
}
peerConfig.Endpoint = &net.UDPAddr{
IP: peerHost.EndpointIP,
Port: getPeerWgListenPort(peerHost),
relayPeer.Endpoint = &net.UDPAddr{
IP: relayHost.EndpointIP,
Port: getPeerWgListenPort(relayHost),
}
if uselocal {
peerConfig.Endpoint.IP = peer.LocalAddress.IP
peerConfig.Endpoint.Port = peerHost.ListenPort
}
allowedips := GetAllowedIPs(&node, &peer, nil)
if peer.Action != models.NODE_DELETE &&
!peer.PendingDelete &&
peer.Connected &&
nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
relayPeer.Endpoint.IP = relayNode.LocalAddress.IP
relayPeer.Endpoint.Port = relayHost.ListenPort
}
peerProxyPort := GetProxyListenPort(peerHost)
var nodePeer wgtypes.PeerConfig
if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
ProxyListenPort: peerProxyPort,
}
hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
Interfaces: peerHost.Interfaces,
ProxyListenPort: peerProxyPort,
}
nodePeer = peerConfig
} else {
peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
peerAllowedIPs = append(peerAllowedIPs, peerConfig.AllowedIPs...)
hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].Remove = false
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
ProxyListenPort: GetProxyListenPort(peerHost),
}
hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
Interfaces: peerHost.Interfaces,
ProxyListenPort: peerProxyPort,
}
nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer)
} else if deletedNode != nil && deletedNode.IsRelay {
relayHost, err := GetHost(deletedNode.HostID.String())
if err != nil {
continue
}
relayPeer := wgtypes.PeerConfig{
PublicKey: relayHost.PublicKey,
Remove: true,
}
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer)
}
continue
}
if node.Network == network { // add to peers map for metrics
hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
ProxyListenPort: peerHost.ProxyListenPort,
currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
var nodePeerMap map[string]models.PeerRouteInfo
if node.IsIngressGateway || node.IsEgressGateway {
nodePeerMap = make(map[string]models.PeerRouteInfo)
}
for _, peer := range currentPeers {
peer := peer
if peer.ID.String() == node.ID.String() {
logger.Log(2, "peer update, skipping self")
//skip yourself
continue
}
peerHost, err := GetHost(peer.HostID.String())
if err != nil {
logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
return models.HostPeerUpdate{}, err
}
peerConfig := wgtypes.PeerConfig{
PublicKey: peerHost.PublicKey,
PersistentKeepaliveInterval: &peer.PersistentKeepalive,
ReplaceAllowedIPs: true,
}
if node.IsIngressGateway || node.IsEgressGateway {
if peer.IsIngressGateway {
_, extPeerIDAndAddrs, err := getExtPeers(&peer)
if err == nil {
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
PeerKey: extPeerIdAndAddr.ID,
Allow: true,
ID: extPeerIdAndAddr.ID,
}
}
}
hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
}
if node.IsIngressGateway && peer.IsEgressGateway {
hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
peer.EgressGatewayRanges...)
}
nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(peer.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(peer.PrimaryAddress()),
},
PeerKey: peerHost.PublicKey.String(),
Allow: true,
ID: peer.ID.String(),
}
}
if (node.IsRelayed && node.RelayedBy != peer.ID.String()) || (peer.IsRelayed && peer.RelayedBy != node.ID.String()) {
// if node is relayed and peer is not the relay, set remove to true
if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; ok {
continue
}
peerConfig.Remove = true
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
continue
}
uselocal := false
if host.EndpointIP.String() == peerHost.EndpointIP.String() {
// peer is on same network
// set to localaddress
uselocal = true
if node.LocalAddress.IP == nil {
// use public endpint
uselocal = false
}
if node.LocalAddress.String() == peer.LocalAddress.String() {
uselocal = false
}
}
peerConfig.Endpoint = &net.UDPAddr{
IP: peerHost.EndpointIP,
Port: getPeerWgListenPort(peerHost),
}
if uselocal {
peerConfig.Endpoint.IP = peer.LocalAddress.IP
peerConfig.Endpoint.Port = peerHost.ListenPort
}
allowedips := GetAllowedIPs(&node, &peer, nil)
if peer.Action != models.NODE_DELETE &&
!peer.PendingDelete &&
peer.Connected &&
nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
}
peerProxyPort := GetProxyListenPort(peerHost)
var nodePeer wgtypes.PeerConfig
if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
ProxyListenPort: peerProxyPort,
}
hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
Interfaces: peerHost.Interfaces,
ProxyListenPort: peerProxyPort,
}
nodePeer = peerConfig
} else {
peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
peerAllowedIPs = append(peerAllowedIPs, peerConfig.AllowedIPs...)
hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].Remove = false
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
ProxyListenPort: GetProxyListenPort(peerHost),
}
hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
Interfaces: peerHost.Interfaces,
ProxyListenPort: peerProxyPort,
}
nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
}
if node.Network == network { // add to peers map for metrics
hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
ProxyListenPort: peerHost.ProxyListenPort,
}
hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
}
//}
}
var extPeers []wgtypes.PeerConfig
var extPeerIDAndAddrs []models.IDandAddr

View file

@ -1,12 +1,10 @@
package logic
import (
"encoding/json"
"errors"
"fmt"
"net"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
)
@ -33,25 +31,11 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error)
node.IsRelay = true
node.RelayedNodes = relay.RelayedNodes
node.SetLastModified()
nodeData, err := json.Marshal(&node)
err = UpsertNode(&node)
if err != nil {
return returnnodes, node, err
}
if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil {
return returnnodes, models.Node{}, err
}
returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes)
for _, relayedNode := range returnnodes {
data, err := json.Marshal(&relayedNode)
if err != nil {
logger.Log(0, "marshalling relayed node", err.Error())
continue
}
if err := database.Insert(relayedNode.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
logger.Log(0, "inserting relayed node", err.Error())
continue
}
}
return returnnodes, node, nil
}
@ -71,12 +55,7 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.N
node.RelayedBy = ""
}
node.SetLastModified()
data, err := json.Marshal(&node)
if err != nil {
logger.Log(0, "setRelayedNodes.Marshal", err.Error())
continue
}
if err := database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
if err := UpsertNode(&node); err != nil {
logger.Log(0, "setRelayedNodes.Insert", err.Error())
continue
}
@ -145,11 +124,7 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
node.IsRelay = false
node.RelayedNodes = []string{}
node.SetLastModified()
data, err := json.Marshal(&node)
if err != nil {
return returnnodes, models.Node{}, err
}
if err = database.Insert(nodeid, string(data), database.NODES_TABLE_NAME); err != nil {
if err = UpsertNode(&node); err != nil {
return returnnodes, models.Node{}, err
}
return returnnodes, node, nil

View file

@ -4,17 +4,18 @@ import (
"encoding/json"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/servercfg"
)
var (
// Node_Limit - dummy var for community
Node_Limit = 1000000000
// Networks_Limit - dummy var for community
Networks_Limit = 1000000000
// Users_Limit - dummy var for community
Users_Limit = 1000000000
// Clients_Limit - dummy var for community
Clients_Limit = 1000000000
// Hosts_Limit - dummy var for community
Hosts_Limit = 1000000000
// Free_Tier - specifies if free tier
Free_Tier = false
)
@ -85,3 +86,11 @@ func StoreJWTSecret(privateKey string) error {
}
return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
}
func SetFreeTierLimits() {
Free_Tier = true
Users_Limit = servercfg.GetUserLimit()
Clients_Limit = servercfg.GetClientLimit()
Networks_Limit = servercfg.GetNetworkLimit()
Hosts_Limit = servercfg.GetHostLimit()
}

View file

@ -60,6 +60,7 @@ func sendTelemetry() error {
Event: "daily checkin",
Properties: posthog.NewProperties().
Set("nodes", d.Nodes).
Set("hosts", d.Hosts).
Set("servers", d.Servers).
Set("non-server nodes", d.Count.NonServer).
Set("extclients", d.ExtClients).
@ -84,6 +85,7 @@ func fetchTelemetryData() (telemetryData, error) {
data.ExtClients = getDBLength(database.EXT_CLIENT_TABLE_NAME)
data.Users = getDBLength(database.USERS_TABLE_NAME)
data.Networks = getDBLength(database.NETWORKS_TABLE_NAME)
data.Hosts = getDBLength(database.HOSTS_TABLE_NAME)
data.Version = servercfg.GetVersion()
data.Servers = getServerCount()
nodes, err := GetAllNodes()
@ -167,6 +169,7 @@ func getDBLength(dbname string) int {
// telemetryData - What data to send to posthog
type telemetryData struct {
Nodes int
Hosts int
ExtClients int
Users int
Count clientCount

View file

@ -1,10 +1,13 @@
package logic
import (
"context"
"fmt"
"sync"
"time"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
)
// == Constants ==
@ -12,6 +15,9 @@ import (
// How long to wait before sending telemetry to server (24 hours)
const timer_hours_between_runs = 24
// HookManagerCh - channel to add any new hooks
var HookManagerCh = make(chan models.HookDetails, 2)
// == Public ==
// TimerCheckpoint - Checks if 24 hours has passed since telemetry was last sent. If so, sends telemetry data to posthog
@ -40,6 +46,36 @@ func AddHook(ifaceToAdd interface{}) {
timeHooks = append(timeHooks, ifaceToAdd)
}
// StartHookManager - listens on `HookManagerCh` to run any hook
func StartHookManager(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
logger.Log(0, "## Stopping Hook Manager")
return
case newhook := <-HookManagerCh:
wg.Add(1)
go addHookWithInterval(ctx, wg, newhook.Hook, newhook.Interval)
}
}
}
func addHookWithInterval(ctx context.Context, wg *sync.WaitGroup, hook func() error, interval time.Duration) {
defer wg.Done()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
hook()
}
}
}
// == private ==
// timeHooks - functions to run once a day, functions must take no parameters

View file

@ -26,6 +26,30 @@ func GetUser(username string) (*models.User, error) {
return &user, err
}
// GetReturnUser - gets a user
func GetReturnUser(username string) (models.ReturnUser, error) {
var user models.ReturnUser
record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
if err != nil {
return user, err
}
if err = json.Unmarshal([]byte(record), &user); err != nil {
return models.ReturnUser{}, err
}
return user, err
}
// ToReturnUser - gets a user as a return user
func ToReturnUser(user models.User) models.ReturnUser {
return models.ReturnUser{
UserName: user.UserName,
Networks: user.Networks,
IsAdmin: user.IsAdmin,
Groups: user.Groups,
}
}
// GetGroupUsers - gets users in a group
func GetGroupUsers(group string) ([]models.ReturnUser, error) {
var returnUsers []models.ReturnUser

10
main.go
View file

@ -26,11 +26,10 @@ import (
"github.com/gravitl/netmaker/netclient/ncutils"
"github.com/gravitl/netmaker/servercfg"
"github.com/gravitl/netmaker/serverctl"
stunserver "github.com/gravitl/netmaker/stun-server"
"golang.org/x/exp/slog"
)
var version = "v0.20.2"
var version = "v0.20.3"
// Start DB Connection and start API Request Handler
func main() {
@ -42,6 +41,9 @@ func main() {
initialize() // initial db and acls
setGarbageCollection()
setVerbosity()
if servercfg.DeployedByOperator() && !servercfg.Is_EE {
logic.SetFreeTierLimits()
}
defer database.CloseDB()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
defer stop()
@ -89,7 +91,6 @@ func initialize() { // Client Mode Prereq Check
if err != nil {
logger.Log(1, "Timer error occurred: ", err.Error())
}
logic.EnterpriseCheck()
var authProvider = auth.InitializeAuthProvider()
@ -147,9 +148,8 @@ func startControllers(wg *sync.WaitGroup, ctx context.Context) {
logger.Log(0, "No Server Mode selected, so nothing is being served! Set Rest mode (REST_BACKEND) or MessageQueue (MESSAGEQUEUE_BACKEND) to 'true'.")
}
// starts the stun server
wg.Add(1)
go stunserver.Start(wg, ctx)
go logic.StartHookManager(ctx, wg)
}
// Should we be using a context vice a waitgroup????????????

View file

@ -34,6 +34,7 @@ type ApiHost struct {
RelayedBy string `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
IsRelay bool `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
RelayedHosts []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
NatType string `json:"nat_type" yaml:"nat_type"`
}
// Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
@ -67,6 +68,7 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
a.Verbosity = h.Verbosity
a.Version = h.Version
a.IsDefault = h.IsDefault
a.NatType = h.NatType
return &a
}

View file

@ -48,7 +48,6 @@ type Host struct {
IPForwarding bool `json:"ipforwarding" yaml:"ipforwarding"`
DaemonInstalled bool `json:"daemoninstalled" yaml:"daemoninstalled"`
AutoUpdate bool `json:"autoupdate" yaml:"autoupdate"`
EndpointDetection bool `json:"endpointdetection" yaml:"endpointdetection"`
HostPass string `json:"hostpass" yaml:"hostpass"`
Name string `json:"name" yaml:"name"`
OS string `json:"os" yaml:"os"`

View file

@ -8,18 +8,20 @@ import (
// HostPeerUpdate - struct for host peer updates
type HostPeerUpdate struct {
Host Host `json:"host" bson:"host" yaml:"host"`
Server string `json:"server" bson:"server" yaml:"server"`
ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
Peers []wgtypes.PeerConfig
HostPeerIDs HostPeerMap `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"`
ProxyUpdate ProxyManagerPayload `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
EgressInfo map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
IngressInfo IngressInfo `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"`
HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
Host Host `json:"host" bson:"host" yaml:"host"`
NodeAddrs []net.IPNet `json:"nodes_addrs" yaml:"nodes_addrs"`
Server string `json:"server" bson:"server" yaml:"server"`
ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
Peers []wgtypes.PeerConfig
HostPeerIDs HostPeerMap `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"`
ProxyUpdate ProxyManagerPayload `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
EgressInfo map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
IngressInfo IngressInfo `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"`
EndpointDetection bool `json:"endpointdetection" yaml:"endpointdetection"`
HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
}
// IngressInfo - struct for ingress info

View file

@ -181,6 +181,14 @@ func isLess(ipA string, ipB string) bool {
return bytes.Compare(ipNetA, ipNetB) < 0
}
// Node.PrimaryAddress - return ipv4 address if present, else return ipv6
func (node *Node) PrimaryAddressIPNet() net.IPNet {
if node.Address.IP != nil {
return node.Address
}
return node.Address6
}
// Node.PrimaryAddress - return ipv4 address if present, else return ipv6
func (node *Node) PrimaryAddress() string {
if node.Address.IP != nil {

View file

@ -2,6 +2,7 @@ package models
import (
"strings"
"time"
jwt "github.com/golang-jwt/jwt/v4"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@ -274,3 +275,18 @@ type StunServer struct {
Domain string `json:"domain" yaml:"domain"`
Port int `json:"port" yaml:"port"`
}
// HookDetails - struct to hold hook info
type HookDetails struct {
Hook func() error
Interval time.Duration
}
// LicenseLimits - struct license limits
type LicenseLimits struct {
Servers int `json:"servers"`
Users int `json:"users"`
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
}

View file

@ -6,11 +6,14 @@ import (
"fmt"
"io"
"net/http"
"strings"
"sync"
"github.com/gravitl/netmaker/servercfg"
)
const already_exists = "ALREADY_EXISTS"
type (
emqxUser struct {
UserID string `json:"user_id"`
@ -99,7 +102,9 @@ func CreateEmqxUser(username, password string, admin bool) error {
if err != nil {
return err
}
return fmt.Errorf("error creating EMQX user %v", string(msg))
if !strings.Contains(string(msg), already_exists) {
return fmt.Errorf("error creating EMQX user %v", string(msg))
}
}
return nil
}

View file

@ -1,7 +1,6 @@
package mq
import (
"context"
"encoding/json"
"fmt"
"math"
@ -107,7 +106,11 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
return
}
}
if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil, nil); err != nil {
nodes, err := logic.GetAllNodes()
if err != nil {
return
}
if err = PublishSingleHostPeerUpdate(currentHost, nodes, nil, nil); err != nil {
slog.Error("failed peers publish after join acknowledged", "name", hostUpdate.Host.Name, "id", currentHost.ID, "error", err)
return
}
@ -235,12 +238,16 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
slog.Info("updating peers after node detected connectivity issues", "id", currentNode.ID, "network", currentNode.Network)
host, err := logic.GetHost(currentNode.HostID.String())
if err == nil {
if err = PublishSingleHostPeerUpdate(context.Background(), host, nil, nil); err != nil {
nodes, err := logic.GetAllNodes()
if err != nil {
return
}
if err = PublishSingleHostPeerUpdate(host, nodes, nil, nil); err != nil {
slog.Warn("failed to publish update after failover peer change for node", "id", currentNode.ID, "network", currentNode.Network, "error", err)
}
}
}
slog.Info("updated node metrics", "id", id)
slog.Debug("updated node metrics", "id", id)
}
}
@ -439,12 +446,18 @@ func handleHostCheckin(h, currentHost *models.Host) bool {
!h.EndpointIP.Equal(currentHost.EndpointIP) ||
(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
h.DefaultInterface != currentHost.DefaultInterface ||
h.EndpointDetection != servercfg.EndpointDetectionEnabled()
(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) || (h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort)
if ifaceDelta { // only save if something changes
currentHost.EndpointIP = h.EndpointIP
currentHost.Interfaces = h.Interfaces
currentHost.DefaultInterface = h.DefaultInterface
currentHost.NatType = h.NatType
if h.ListenPort != 0 {
currentHost.ListenPort = h.ListenPort
}
if h.WgPublicListenPort != 0 {
currentHost.WgPublicListenPort = h.WgPublicListenPort
}
if err := logic.UpsertHost(currentHost); err != nil {
slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
return false

View file

@ -80,7 +80,7 @@ func SetupMQTT() {
logger.Log(0, "node metrics subscription failed")
}
opts.SetOrderMatters(true)
opts.SetOrderMatters(false)
opts.SetResumeSubs(true)
})
mqclient = mqtt.NewClient(opts)

View file

@ -1,7 +1,6 @@
package mq
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -24,10 +23,13 @@ func PublishPeerUpdate() error {
logger.Log(1, "err getting all hosts", err.Error())
return err
}
logic.ResetPeerUpdateContext()
allNodes, err := logic.GetAllNodes()
if err != nil {
return err
}
for _, host := range hosts {
host := host
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, nil); err != nil {
if err = PublishSingleHostPeerUpdate(&host, allNodes, nil, nil); err != nil {
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
}
}
@ -46,10 +48,13 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
logger.Log(1, "err getting all hosts", err.Error())
return err
}
logic.ResetPeerUpdateContext()
allNodes, err := logic.GetAllNodes()
if err != nil {
return err
}
for _, host := range hosts {
host := host
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode, nil); err != nil {
if err = PublishSingleHostPeerUpdate(&host, allNodes, delNode, nil); err != nil {
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
}
}
@ -68,10 +73,13 @@ func PublishDeletedClientPeerUpdate(delClient *models.ExtClient) error {
logger.Log(1, "err getting all hosts", err.Error())
return err
}
logic.ResetPeerUpdateContext()
nodes, err := logic.GetAllNodes()
if err != nil {
return err
}
for _, host := range hosts {
host := host
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, []models.ExtClient{*delClient}); err != nil {
if err = PublishSingleHostPeerUpdate(&host, nodes, nil, []models.ExtClient{*delClient}); err != nil {
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
}
}
@ -79,27 +87,29 @@ func PublishDeletedClientPeerUpdate(delClient *models.ExtClient) error {
}
// PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node, deletedClients []models.ExtClient) error {
func PublishSingleHostPeerUpdate(host *models.Host, allNodes []models.Node, deletedNode *models.Node, deletedClients []models.ExtClient) error {
peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode, deletedClients)
peerUpdate, err := logic.GetPeerUpdateForHost("", host, allNodes, deletedNode, deletedClients)
if err != nil {
return err
}
if len(peerUpdate.Peers) == 0 { // no peers to send
return nil
}
proxyUpdate, err := logic.GetProxyUpdateForHost(ctx, host)
if err != nil {
return err
}
proxyUpdate.Server = servercfg.GetServer()
if host.ProxyEnabled {
proxyUpdate.Action = models.ProxyUpdate
} else {
proxyUpdate.Action = models.NoProxy
}
if host.OS != models.OS_Types.IoT {
proxyUpdate, err := logic.GetProxyUpdateForHost(host)
if err != nil {
return err
}
proxyUpdate.Server = servercfg.GetServer()
if host.ProxyEnabled {
proxyUpdate.Action = models.ProxyUpdate
} else {
proxyUpdate.Action = models.NoProxy
}
peerUpdate.ProxyUpdate = proxyUpdate
peerUpdate.ProxyUpdate = proxyUpdate
}
data, err := json.Marshal(&peerUpdate)
if err != nil {
@ -436,7 +446,10 @@ func sendPeers() {
if err != nil && len(hosts) > 0 {
logger.Log(1, "error retrieving networks for keepalive", err.Error())
}
nodes, err := logic.GetAllNodes()
if err != nil {
return
}
var force bool
peer_force_send++
if peer_force_send == 5 {
@ -451,11 +464,10 @@ func sendPeers() {
//collectServerMetrics(networks[:])
}
if force {
logic.ResetPeerUpdateContext()
for _, host := range hosts {
host := host
logger.Log(2, "sending scheduled peer update (5 min)")
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, nil); err != nil {
if err = PublishSingleHostPeerUpdate(&host, nodes, nil, nil); err != nil {
logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
}
}

View file

@ -1,22 +1,26 @@
# Netmaker v0.20.2
## whats new
-
## whats fixed
- enrollment keys for non-admins
- client version displayed correctly in UI
- upd hole punching improvments
- SSL fallback to letsencrypt
- permission handling for non-admin users
# Netmaker v0.20.3
## Whats New
- Moved to new licensing server for self-hosted
- STUN removed from netmaker server to improve memory performance
- Added DB caching to drastically reduce read/writes from disk
## What's Fixed
- Major memory leak resolved due to STUN
- Issues with netclient ports on daemon restart
- Windows GUI unable to find netclient backend
- Major scalability fixes - Can now scale to hundreds of hosts with low resources
- Resolved ACL panic
- Reverted blocking creation of Ingress with NAT
## known issues
- Migration causes a listen port of 0 for some upgraded hosts
- Docker clients can not re-join after deletion
- Innacurate Ext Client Metrics
- Issue with Mac + IPv6 addressing
- Nodes on same local network may not always connect
- List populates egress ranges twice
- If you do NOT set STUN_LIST on server, it could lead to strange behavior on client
- netclient-gui (windows) will display an erroneous error dialog when joining a network (can be ignored)
- netclient-gui will continously display error dialog if netmaker server is offline
- Incorrect metrics against ext clients
- Host ListenPorts set to 0 after migration from 0.17.1 -> 0.20.3
- Mac IPv6 addresses/route issues
- Docker client can not re-join after complete deletion
- netclient-gui network tab blank after disconnect

View file

@ -15,7 +15,7 @@ if [ -z "$NM_DOMAIN" ] || [ -z "$NM_EMAIL" ]; then
fi
# TODO make sure this doesnt break, parse `certbot certificates` if yes
CERT_DIR="$SCRIPT_DIR/letsencrypt/live/stun.$NM_DOMAIN"
CERT_DIR="$SCRIPT_DIR/letsencrypt/live/api.$NM_DOMAIN"
echo "Setting up SSL certificates..."
@ -31,7 +31,6 @@ CERTBOT_PARAMS=$(cat <<EOF
certonly --standalone \
--non-interactive --agree-tos \
-m $NM_EMAIL \
-d stun.$NM_DOMAIN \
-d api.$NM_DOMAIN \
-d broker.$NM_DOMAIN \
-d dashboard.$NM_DOMAIN \

View file

@ -543,7 +543,6 @@ set_install_vars() {
echo " dashboard.$NETMAKER_BASE_DOMAIN"
echo " api.$NETMAKER_BASE_DOMAIN"
echo " broker.$NETMAKER_BASE_DOMAIN"
echo " stun.$NETMAKER_BASE_DOMAIN"
echo " turn.$NETMAKER_BASE_DOMAIN"
echo " turnapi.$NETMAKER_BASE_DOMAIN"

View file

@ -1,6 +1,6 @@
#!/bin/bash
LATEST="v0.20.2"
LATEST="v0.20.3"
INSTALL_PATH="/root"
trap restore_old_netmaker_instructions
@ -259,7 +259,6 @@ collect_server_settings() {
esac
done
STUN_DOMAIN="stun.$SERVER_NAME"
TURN_DOMAIN="turn.$SERVER_NAME"
TURNAPI_DOMAIN="turnapi.$SERVER_NAME"
echo "-----------------------------------------------------"

View file

@ -10,6 +10,7 @@ import (
"time"
"github.com/gravitl/netmaker/config"
"github.com/gravitl/netmaker/models"
)
@ -741,6 +742,58 @@ func IsProxyEnabled() bool {
return enabled
}
// GetNetworkLimit - fetches free tier limits on users
func GetUserLimit() int {
var userslimit int
if os.Getenv("USERS_LIMIT") != "" {
userslimit, _ = strconv.Atoi(os.Getenv("USERS_LIMIT"))
} else {
userslimit = config.Config.Server.UsersLimit
}
return userslimit
}
// GetNetworkLimit - fetches free tier limits on networks
func GetNetworkLimit() int {
var networkslimit int
if os.Getenv("NETWORKS_LIMIT") != "" {
networkslimit, _ = strconv.Atoi(os.Getenv("NETWORKS_LIMIT"))
} else {
networkslimit = config.Config.Server.NetworksLimit
}
return networkslimit
}
// GetClientLimit - fetches free tier limits on ext. clients
func GetClientLimit() int {
var clientsLimit int
if os.Getenv("CLIENTS_LIMIT") != "" {
clientsLimit, _ = strconv.Atoi(os.Getenv("CLIENTS_LIMIT"))
} else {
clientsLimit = config.Config.Server.ClientsLimit
}
return clientsLimit
}
// GetHostLimit - fetches free tier limits on hosts
func GetHostLimit() int {
var hostsLimit int
if os.Getenv("HOSTS_LIMIT") != "" {
hostsLimit, _ = strconv.Atoi(os.Getenv("HOSTS_LIMIT"))
} else {
hostsLimit = config.Config.Server.HostsLimit
}
return hostsLimit
}
// DeployedByOperator - returns true if the instance is deployed by netmaker operator
func DeployedByOperator() bool {
if os.Getenv("DEPLOYED_BY_OPERATOR") != "" {
return os.Getenv("DEPLOYED_BY_OPERATOR") == "true"
}
return config.Config.Server.DeployedByOperator
}
// GetDefaultProxyMode - default proxy mode for a server
func GetDefaultProxyMode() config.ProxyMode {
var (

View file

@ -1,154 +0,0 @@
package stunserver
import (
"context"
"fmt"
"net"
"strings"
"sync"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/servercfg"
"github.com/pkg/errors"
"gortc.io/stun"
)
// Server is RFC 5389 basic server implementation.
//
// Current implementation is UDP only and not utilizes FINGERPRINT mechanism,
// nor ALTERNATE-SERVER, nor credentials mechanisms. It does not support
// backwards compatibility with RFC 3489.
type Server struct {
Addr string
}
var (
software = stun.NewSoftware("netmaker-stun")
errNotSTUNMessage = errors.New("not stun message")
)
func basicProcess(addr net.Addr, b []byte, req, res *stun.Message) error {
if !stun.IsMessage(b) {
return errNotSTUNMessage
}
if _, err := req.Write(b); err != nil {
return errors.Wrap(err, "failed to read message")
}
var (
ip net.IP
port int
)
switch a := addr.(type) {
case *net.UDPAddr:
ip = a.IP
port = a.Port
default:
panic(fmt.Sprintf("unknown addr: %v", addr))
}
return res.Build(req,
stun.BindingSuccess,
software,
&stun.XORMappedAddress{
IP: ip,
Port: port,
},
stun.Fingerprint,
)
}
func (s *Server) serveConn(c net.PacketConn, res, req *stun.Message, ctx context.Context) error {
if c == nil {
return nil
}
go func(ctx context.Context) {
<-ctx.Done()
if c != nil {
// kill connection on server shutdown
c.Close()
}
}(ctx)
buf := make([]byte, 1024)
n, addr, err := c.ReadFrom(buf) // this be blocky af
if err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
logger.Log(1, "STUN read error:", err.Error())
}
return nil
}
if _, err = req.Write(buf[:n]); err != nil {
logger.Log(1, "STUN write error:", err.Error())
return err
}
if err = basicProcess(addr, buf[:n], req, res); err != nil {
if err == errNotSTUNMessage {
return nil
}
logger.Log(1, "STUN process error:", err.Error())
return nil
}
_, err = c.WriteTo(res.Raw, addr)
if err != nil {
logger.Log(1, "STUN response write error", err.Error())
}
return err
}
// Serve reads packets from connections and responds to BINDING requests.
func (s *Server) serve(c net.PacketConn, ctx context.Context) error {
var (
res = new(stun.Message)
req = new(stun.Message)
)
for {
select {
case <-ctx.Done():
logger.Log(0, "shut down STUN server")
return nil
default:
if err := s.serveConn(c, res, req, ctx); err != nil {
logger.Log(1, "serve: %v", err.Error())
continue
}
res.Reset()
req.Reset()
}
}
}
// listenUDPAndServe listens on laddr and process incoming packets.
func listenUDPAndServe(ctx context.Context, serverNet, laddr string) error {
c, err := net.ListenPacket(serverNet, laddr)
if err != nil {
return err
}
s := &Server{
Addr: laddr,
}
return s.serve(c, ctx)
}
func normalize(address string) string {
if len(address) == 0 {
address = "0.0.0.0"
}
if !strings.Contains(address, ":") {
address = fmt.Sprintf("%s:%d", address, stun.DefaultPort)
}
return address
}
// Start - starts the stun server
func Start(wg *sync.WaitGroup, ctx context.Context) {
defer wg.Done()
normalized := normalize(fmt.Sprintf("0.0.0.0:%d", servercfg.GetStunPort()))
logger.Log(0, "netmaker-stun listening on", normalized, "via udp")
if err := listenUDPAndServe(ctx, "udp", normalized); err != nil {
if strings.Contains(err.Error(), "closed network connection") {
logger.Log(0, "shutdown STUN server")
} else {
logger.Log(0, "server: ", err.Error())
}
}
}

View file

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