Browse Source

resolve merge conflicts

abhishek9686 5 months ago
parent
commit
c9446461de

+ 1 - 0
controllers/enrollmentkeys.go

@@ -160,6 +160,7 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 		enrollmentKeyBody.Unlimited,
 		enrollmentKeyBody.Unlimited,
 		relayId,
 		relayId,
 		false,
 		false,
+		enrollmentKeyBody.AutoEgress,
 	)
 	)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
 		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())

+ 5 - 1
controllers/network.go

@@ -436,7 +436,7 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 // @Security    oauth
 // @Security    oauth
 // @Param       networkname path string true "Network name"
 // @Param       networkname path string true "Network name"
 // @Produce     json
 // @Produce     json
-// @Success     200 {object} acls.SuccessResponse
+// @Success     200 {object} models.SuccessResponse
 // @Failure     500 {object} models.ErrorResponse
 // @Failure     500 {object} models.ErrorResponse
 func getNetworkEgressRoutes(w http.ResponseWriter, r *http.Request) {
 func getNetworkEgressRoutes(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
@@ -626,6 +626,10 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 			logic.CreateFailOver(*newNode)
 			logic.CreateFailOver(*newNode)
 			// make host remote access gateway
 			// make host remote access gateway
 			logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
 			logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
+			logic.CreateRelay(models.RelayRequest{
+				NodeID: newNode.ID.String(),
+				NetID:  network.NetID,
+			})
 		}
 		}
 		// send peer updates
 		// send peer updates
 		if err = mq.PublishPeerUpdate(false); err != nil {
 		if err = mq.PublishPeerUpdate(false); err != nil {

+ 119 - 6
controllers/user.go

@@ -6,7 +6,9 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"reflect"
 	"reflect"
+	"time"
 
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/websocket"
 	"github.com/gorilla/websocket"
 	"github.com/gravitl/netmaker/auth"
 	"github.com/gravitl/netmaker/auth"
@@ -37,7 +39,123 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(ListRoles))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(ListRoles))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/users/access_token", logic.SecurityCheck(true, http.HandlerFunc(createUserAccessToken))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/users/access_token", logic.SecurityCheck(true, http.HandlerFunc(getUserAccessTokens))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/users/access_token", logic.SecurityCheck(true, http.HandlerFunc(deleteUserAccessTokens))).Methods(http.MethodDelete)
+}
 
 
+// @Summary     Authenticate a user to retrieve an authorization token
+// @Router      /api/v1/users/{username}/access_token [post]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.UserAuthParams true "Authentication parameters"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func createUserAccessToken(w http.ResponseWriter, r *http.Request) {
+
+	// Auth request consists of Mac Address and Password (from node that is authorizing
+	// in case of Master, auth is ignored and mac is set to "mastermac"
+	var req models.UserAccessToken
+
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		logger.Log(0, "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if req.Name == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("name is required"), "badrequest"))
+		return
+	}
+	if req.UserName == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username is required"), "badrequest"))
+		return
+	}
+
+	user, err := logic.GetUser(req.UserName)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
+		return
+	}
+	if logic.IsOauthUser(user) == nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user is registered via SSO"), "badrequest"))
+		return
+	}
+	req.ID = uuid.New().String()
+	req.CreatedBy = r.Header.Get("user")
+	req.CreatedAt = time.Now()
+	jwt, err := logic.CreateUserAccessJwtToken(user.UserName, user.PlatformRoleID, req.ExpiresAt, req.ID)
+	if jwt == "" {
+		// very unlikely that err is !nil and no jwt returned, but handle it anyways.
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error creating access token "+err.Error()), "internal"),
+		)
+		return
+	}
+	err = req.Create()
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error creating access token "+err.Error()), "internal"),
+		)
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, models.SuccessfulUserLoginResponse{
+		AuthToken: jwt,
+		UserName:  req.UserName,
+	}, "api access token has generated for user "+req.UserName)
+}
+
+// @Summary     Authenticate a user to retrieve an authorization token
+// @Router      /api/v1/users/{username}/access_token [post]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.UserAuthParams true "Authentication parameters"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func getUserAccessTokens(w http.ResponseWriter, r *http.Request) {
+	username := r.URL.Query().Get("username")
+	if username == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username is required"), "badrequest"))
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, (&models.UserAccessToken{UserName: username}).ListByUser(), "fetched api access tokens for user "+username)
+}
+
+// @Summary     Authenticate a user to retrieve an authorization token
+// @Router      /api/v1/users/{username}/access_token [post]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.UserAuthParams true "Authentication parameters"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func deleteUserAccessTokens(w http.ResponseWriter, r *http.Request) {
+	id := r.URL.Query().Get("id")
+	if id == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("id is required"), "badrequest"))
+		return
+	}
+
+	err := (&models.UserAccessToken{ID: id}).Delete()
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error deleting access token "+err.Error()), "internal"),
+		)
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, nil, "revoked access token")
 }
 }
 
 
 // @Summary     Authenticate a user to retrieve an authorization token
 // @Summary     Authenticate a user to retrieve an authorization token
@@ -671,17 +789,12 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	success, err := logic.DeleteUser(username)
+	err = logic.DeleteUser(username)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, username,
 		logger.Log(0, username,
 			"failed to delete user: ", err.Error())
 			"failed to delete user: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
-	} else if !success {
-		err := errors.New("delete unsuccessful")
-		logger.Log(0, username, err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
 	}
 	}
 	// check and delete extclient with this ownerID
 	// check and delete extclient with this ownerID
 	go func() {
 	go func() {

+ 37 - 91
database/database.go

@@ -1,18 +1,12 @@
 package database
 package database
 
 
 import (
 import (
-	"crypto/rand"
-	"encoding/json"
 	"errors"
 	"errors"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
-	"golang.org/x/crypto/nacl/box"
 )
 )
 
 
 const (
 const (
@@ -25,6 +19,8 @@ const (
 	DELETED_NODES_TABLE_NAME = "deletednodes"
 	DELETED_NODES_TABLE_NAME = "deletednodes"
 	// USERS_TABLE_NAME - users table
 	// USERS_TABLE_NAME - users table
 	USERS_TABLE_NAME = "users"
 	USERS_TABLE_NAME = "users"
+	// ACCESS_TOKENS_TABLE_NAME - access tokens table
+	ACCESS_TOKENS_TABLE_NAME = "user_access_tokens"
 	// USER_PERMISSIONS_TABLE_NAME - user permissions table
 	// USER_PERMISSIONS_TABLE_NAME - user permissions table
 	USER_PERMISSIONS_TABLE_NAME = "user_permissions"
 	USER_PERMISSIONS_TABLE_NAME = "user_permissions"
 	// CERTS_TABLE_NAME - certificates table
 	// CERTS_TABLE_NAME - certificates table
@@ -104,6 +100,36 @@ const (
 
 
 var dbMutex sync.RWMutex
 var dbMutex sync.RWMutex
 
 
+var Tables = []string{
+	NETWORKS_TABLE_NAME,
+	NODES_TABLE_NAME,
+	CERTS_TABLE_NAME,
+	DELETED_NODES_TABLE_NAME,
+	USERS_TABLE_NAME,
+	DNS_TABLE_NAME,
+	EXT_CLIENT_TABLE_NAME,
+	PEERS_TABLE_NAME,
+	SERVERCONF_TABLE_NAME,
+	SERVER_UUID_TABLE_NAME,
+	GENERATED_TABLE_NAME,
+	NODE_ACLS_TABLE_NAME,
+	SSO_STATE_CACHE,
+	METRICS_TABLE_NAME,
+	NETWORK_USER_TABLE_NAME,
+	USER_GROUPS_TABLE_NAME,
+	CACHE_TABLE_NAME,
+	HOSTS_TABLE_NAME,
+	ENROLLMENT_KEYS_TABLE_NAME,
+	HOST_ACTIONS_TABLE_NAME,
+	PENDING_USERS_TABLE_NAME,
+	USER_PERMISSIONS_TABLE_NAME,
+	USER_INVITES_TABLE_NAME,
+	TAG_TABLE_NAME,
+	ACLS_TABLE_NAME,
+	PEER_ACK_TABLE,
+	// ACCESS_TOKENS_TABLE_NAME,
+}
+
 func getCurrentDB() map[string]interface{} {
 func getCurrentDB() map[string]interface{} {
 	switch servercfg.GetDB() {
 	switch servercfg.GetDB() {
 	case "rqlite":
 	case "rqlite":
@@ -133,72 +159,30 @@ func InitializeDatabase() error {
 		time.Sleep(2 * time.Second)
 		time.Sleep(2 * time.Second)
 	}
 	}
 	createTables()
 	createTables()
-	return initializeUUID()
+	return nil
 }
 }
 
 
 func createTables() {
 func createTables() {
-	CreateTable(NETWORKS_TABLE_NAME)
-	CreateTable(NODES_TABLE_NAME)
-	CreateTable(CERTS_TABLE_NAME)
-	CreateTable(DELETED_NODES_TABLE_NAME)
-	CreateTable(USERS_TABLE_NAME)
-	CreateTable(DNS_TABLE_NAME)
-	CreateTable(EXT_CLIENT_TABLE_NAME)
-	CreateTable(PEERS_TABLE_NAME)
-	CreateTable(SERVERCONF_TABLE_NAME)
-	CreateTable(SERVER_UUID_TABLE_NAME)
-	CreateTable(GENERATED_TABLE_NAME)
-	CreateTable(NODE_ACLS_TABLE_NAME)
-	CreateTable(SSO_STATE_CACHE)
-	CreateTable(METRICS_TABLE_NAME)
-	CreateTable(NETWORK_USER_TABLE_NAME)
-	CreateTable(USER_GROUPS_TABLE_NAME)
-	CreateTable(CACHE_TABLE_NAME)
-	CreateTable(HOSTS_TABLE_NAME)
-	CreateTable(ENROLLMENT_KEYS_TABLE_NAME)
-	CreateTable(HOST_ACTIONS_TABLE_NAME)
-	CreateTable(PENDING_USERS_TABLE_NAME)
-	CreateTable(USER_PERMISSIONS_TABLE_NAME)
-	CreateTable(USER_INVITES_TABLE_NAME)
-	CreateTable(TAG_TABLE_NAME)
-	CreateTable(ACLS_TABLE_NAME)
-	CreateTable(PEER_ACK_TABLE)
-	CreateTable(SERVER_SETTINGS)
+	for _, table := range Tables {
+		_ = CreateTable(table)
+	}
 }
 }
 
 
 func CreateTable(tableName string) error {
 func CreateTable(tableName string) error {
 	return getCurrentDB()[CREATE_TABLE].(func(string) error)(tableName)
 	return getCurrentDB()[CREATE_TABLE].(func(string) error)(tableName)
 }
 }
 
 
-// IsJSONString - checks if valid json
-func IsJSONString(value string) bool {
-	var jsonInt interface{}
-	var nodeInt models.Node
-	return json.Unmarshal([]byte(value), &jsonInt) == nil || json.Unmarshal([]byte(value), &nodeInt) == nil
-}
-
 // Insert - inserts object into db
 // Insert - inserts object into db
 func Insert(key string, value string, tableName string) error {
 func Insert(key string, value string, tableName string) error {
 	dbMutex.Lock()
 	dbMutex.Lock()
 	defer dbMutex.Unlock()
 	defer dbMutex.Unlock()
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		return getCurrentDB()[INSERT].(func(string, string, string) error)(key, value, tableName)
 		return getCurrentDB()[INSERT].(func(string, string, string) error)(key, value, tableName)
 	} else {
 	} else {
 		return errors.New("invalid insert " + key + " : " + value)
 		return errors.New("invalid insert " + key + " : " + value)
 	}
 	}
 }
 }
 
 
-// InsertPeer - inserts peer into db
-func InsertPeer(key string, value string) error {
-	dbMutex.Lock()
-	defer dbMutex.Unlock()
-	if key != "" && value != "" && IsJSONString(value) {
-		return getCurrentDB()[INSERT_PEER].(func(string, string) error)(key, value)
-	} else {
-		return errors.New("invalid peer insert " + key + " : " + value)
-	}
-}
-
 // DeleteRecord - deletes a record from db
 // DeleteRecord - deletes a record from db
 func DeleteRecord(tableName string, key string) error {
 func DeleteRecord(tableName string, key string) error {
 	dbMutex.Lock()
 	dbMutex.Lock()
@@ -240,44 +224,6 @@ func FetchRecords(tableName string) (map[string]string, error) {
 	return getCurrentDB()[FETCH_ALL].(func(string) (map[string]string, error))(tableName)
 	return getCurrentDB()[FETCH_ALL].(func(string) (map[string]string, error))(tableName)
 }
 }
 
 
-// initializeUUID - create a UUID record for server if none exists
-func initializeUUID() error {
-	records, err := FetchRecords(SERVER_UUID_TABLE_NAME)
-	if err != nil {
-		if !IsEmptyRecord(err) {
-			return err
-		}
-	} else if len(records) > 0 {
-		return nil
-	}
-	// setup encryption keys
-	var trafficPubKey, trafficPrivKey, errT = box.GenerateKey(rand.Reader) // generate traffic keys
-	if errT != nil {
-		return errT
-	}
-	tPriv, err := ncutils.ConvertKeyToBytes(trafficPrivKey)
-	if err != nil {
-		return err
-	}
-
-	tPub, err := ncutils.ConvertKeyToBytes(trafficPubKey)
-	if err != nil {
-		return err
-	}
-
-	telemetry := models.Telemetry{
-		UUID:           uuid.NewString(),
-		TrafficKeyPriv: tPriv,
-		TrafficKeyPub:  tPub,
-	}
-	telJSON, err := json.Marshal(&telemetry)
-	if err != nil {
-		return err
-	}
-
-	return Insert(SERVER_UUID_RECORD_KEY, string(telJSON), SERVER_UUID_TABLE_NAME)
-}
-
 // CloseDB - closes a database gracefully
 // CloseDB - closes a database gracefully
 func CloseDB() {
 func CloseDB() {
 	getCurrentDB()[CLOSE_DB].(func())()
 	getCurrentDB()[CLOSE_DB].(func())()

+ 2 - 2
database/postgres.go

@@ -59,7 +59,7 @@ func pgCreateTable(tableName string) error {
 }
 }
 
 
 func pgInsert(key string, value string, tableName string) error {
 func pgInsert(key string, value string, tableName string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		insertSQL := "INSERT INTO " + tableName + " (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $3;"
 		insertSQL := "INSERT INTO " + tableName + " (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $3;"
 		statement, err := PGDB.Prepare(insertSQL)
 		statement, err := PGDB.Prepare(insertSQL)
 		if err != nil {
 		if err != nil {
@@ -77,7 +77,7 @@ func pgInsert(key string, value string, tableName string) error {
 }
 }
 
 
 func pgInsertPeer(key string, value string) error {
 func pgInsertPeer(key string, value string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		err := pgInsert(key, value, PEERS_TABLE_NAME)
 		err := pgInsert(key, value, PEERS_TABLE_NAME)
 		if err != nil {
 		if err != nil {
 			return err
 			return err

+ 2 - 2
database/rqlite.go

@@ -43,7 +43,7 @@ func rqliteCreateTable(tableName string) error {
 }
 }
 
 
 func rqliteInsert(key string, value string, tableName string) error {
 func rqliteInsert(key string, value string, tableName string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		_, err := RQliteDatabase.WriteOne("INSERT OR REPLACE INTO " + tableName + " (key, value) VALUES ('" + key + "', '" + value + "')")
 		_, err := RQliteDatabase.WriteOne("INSERT OR REPLACE INTO " + tableName + " (key, value) VALUES ('" + key + "', '" + value + "')")
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -54,7 +54,7 @@ func rqliteInsert(key string, value string, tableName string) error {
 }
 }
 
 
 func rqliteInsertPeer(key string, value string) error {
 func rqliteInsertPeer(key string, value string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		_, err := RQliteDatabase.WriteOne("INSERT OR REPLACE INTO " + PEERS_TABLE_NAME + " (key, value) VALUES ('" + key + "', '" + value + "')")
 		_, err := RQliteDatabase.WriteOne("INSERT OR REPLACE INTO " + PEERS_TABLE_NAME + " (key, value) VALUES ('" + key + "', '" + value + "')")
 		if err != nil {
 		if err != nil {
 			return err
 			return err

+ 2 - 2
database/sqlite.go

@@ -61,7 +61,7 @@ func sqliteCreateTable(tableName string) error {
 }
 }
 
 
 func sqliteInsert(key string, value string, tableName string) error {
 func sqliteInsert(key string, value string, tableName string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		insertSQL := "INSERT OR REPLACE INTO " + tableName + " (key, value) VALUES (?, ?)"
 		insertSQL := "INSERT OR REPLACE INTO " + tableName + " (key, value) VALUES (?, ?)"
 		statement, err := SqliteDB.Prepare(insertSQL)
 		statement, err := SqliteDB.Prepare(insertSQL)
 		if err != nil {
 		if err != nil {
@@ -78,7 +78,7 @@ func sqliteInsert(key string, value string, tableName string) error {
 }
 }
 
 
 func sqliteInsertPeer(key string, value string) error {
 func sqliteInsertPeer(key string, value string) error {
-	if key != "" && value != "" && IsJSONString(value) {
+	if key != "" && value != "" {
 		err := sqliteInsert(key, value, PEERS_TABLE_NAME)
 		err := sqliteInsert(key, value, PEERS_TABLE_NAME)
 		if err != nil {
 		if err != nil {
 			return err
 			return err

+ 0 - 59
database/statics.go

@@ -1,59 +0,0 @@
-package database
-
-import (
-	"encoding/json"
-	"strings"
-)
-
-// SetPeers - sets peers for a network
-func SetPeers(newPeers map[string]string, networkName string) bool {
-	areEqual := PeersAreEqual(newPeers, networkName)
-	if !areEqual {
-		jsonData, err := json.Marshal(newPeers)
-		if err != nil {
-			return false
-		}
-		InsertPeer(networkName, string(jsonData))
-		return true
-	}
-	return !areEqual
-}
-
-// GetPeers - gets peers for a given network
-func GetPeers(networkName string) (map[string]string, error) {
-	record, err := FetchRecord(PEERS_TABLE_NAME, networkName)
-	if err != nil && !IsEmptyRecord(err) {
-		return nil, err
-	}
-	currentDataMap := make(map[string]string)
-	if IsEmptyRecord(err) {
-		return currentDataMap, nil
-	}
-	err = json.Unmarshal([]byte(record), &currentDataMap)
-	return currentDataMap, err
-}
-
-// PeersAreEqual - checks if peers are the same
-func PeersAreEqual(toCompare map[string]string, networkName string) bool {
-	currentDataMap, err := GetPeers(networkName)
-	if err != nil {
-		return false
-	}
-	if len(currentDataMap) != len(toCompare) {
-		return false
-	}
-	for k := range currentDataMap {
-		if toCompare[k] != currentDataMap[k] {
-			return false
-		}
-	}
-	return true
-}
-
-// IsEmptyRecord - checks for if it's an empty record error or not
-func IsEmptyRecord(err error) bool {
-	if err == nil {
-		return false
-	}
-	return strings.Contains(err.Error(), NO_RECORD) || strings.Contains(err.Error(), NO_RECORDS)
-}

+ 11 - 0
database/utils.go

@@ -0,0 +1,11 @@
+package database
+
+import "strings"
+
+// IsEmptyRecord - checks for if it's an empty record error or not
+func IsEmptyRecord(err error) bool {
+	if err == nil {
+		return false
+	}
+	return strings.Contains(err.Error(), NO_RECORD) || strings.Contains(err.Error(), NO_RECORDS)
+}

+ 41 - 0
db/connector.go

@@ -0,0 +1,41 @@
+package db
+
+import (
+	"errors"
+	"os"
+
+	"github.com/gravitl/netmaker/config"
+	"gorm.io/gorm"
+)
+
+var ErrUnsupportedDB = errors.New("unsupported db type")
+
+// connector helps connect to a database,
+// along with any initializations required.
+type connector interface {
+	connect() (*gorm.DB, error)
+}
+
+// GetDB - gets the database type
+func GetDB() string {
+	database := "sqlite"
+	if os.Getenv("DATABASE") != "" {
+		database = os.Getenv("DATABASE")
+	} else if config.Config.Server.Database != "" {
+		database = config.Config.Server.Database
+	}
+	return database
+}
+
+// newConnector detects the database being
+// used and returns the corresponding connector.
+func newConnector() (connector, error) {
+	switch GetDB() {
+	case "sqlite":
+		return &sqliteConnector{}, nil
+	case "postgres":
+		return &postgresConnector{}, nil
+	default:
+		return nil, ErrUnsupportedDB
+	}
+}

+ 96 - 0
db/db.go

@@ -0,0 +1,96 @@
+package db
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+type ctxKey string
+
+const dbCtxKey ctxKey = "db"
+
+var db *gorm.DB
+
+var ErrDBNotFound = errors.New("no db instance in context")
+
+// InitializeDB initializes a connection to the
+// database (if not already done) and ensures it
+// has the latest schema.
+func InitializeDB(models ...interface{}) error {
+	if db != nil {
+		return nil
+	}
+
+	connector, err := newConnector()
+	if err != nil {
+		return err
+	}
+
+	// DB / LIFE ADVICE: try 5 times before giving up.
+	for i := 0; i < 5; i++ {
+		db, err = connector.connect()
+		if err == nil {
+			break
+		}
+
+		// wait 2s if you have the time.
+		time.Sleep(2 * time.Second)
+	}
+	if err != nil {
+		return err
+	}
+
+	return db.AutoMigrate(models...)
+}
+
+// WithContext returns a new context with the db
+// connection instance.
+//
+// Ensure InitializeDB has been called before using
+// this function.
+//
+// To extract the db connection use the FromContext
+// function.
+func WithContext(ctx context.Context) context.Context {
+	return context.WithValue(ctx, dbCtxKey, db)
+}
+
+// Middleware to auto-inject the db connection instance
+// in a request's context.
+//
+// Ensure InitializeDB has been called before using this
+// middleware.
+func Middleware(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		next.ServeHTTP(w, r.WithContext(WithContext(r.Context())))
+	})
+}
+
+// FromContext extracts the db connection instance from
+// the given context.
+//
+// The function panics, if a connection does not exist.
+func FromContext(ctx context.Context) *gorm.DB {
+
+	return db
+}
+
+// BeginTx returns a context with a new transaction.
+// If the context already has a db connection instance,
+// it uses that instance. Otherwise, it uses the
+// connection initialized in the package.
+//
+// Ensure InitializeDB has been called before using
+// this function.
+func BeginTx(ctx context.Context) context.Context {
+	dbInCtx, ok := ctx.Value(dbCtxKey).(*gorm.DB)
+	if !ok {
+		return context.WithValue(ctx, dbCtxKey, db.Begin())
+	}
+
+	return context.WithValue(ctx, dbCtxKey, dbInCtx.Begin())
+}

+ 117 - 0
db/postgres.go

@@ -0,0 +1,117 @@
+package db
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+
+	"github.com/gravitl/netmaker/config"
+	"gorm.io/driver/postgres"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+)
+
+// postgresConnector for initializing and
+// connecting to a postgres database.
+type postgresConnector struct{}
+
+// postgresConnector.connect connects and
+// initializes a connection to postgres.
+func (pg *postgresConnector) connect() (*gorm.DB, error) {
+	pgConf := GetSQLConf()
+	dsn := fmt.Sprintf(
+		"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s connect_timeout=5",
+		pgConf.Host,
+		pgConf.Port,
+		pgConf.Username,
+		pgConf.Password,
+		pgConf.DB,
+		pgConf.SSLMode,
+	)
+
+	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
+		Logger: logger.Default.LogMode(logger.Silent),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// ensure netmaker_v1 schema exists.
+	err = db.Exec("CREATE SCHEMA IF NOT EXISTS netmaker_v1").Error
+	if err != nil {
+		return nil, err
+	}
+
+	// set the netmaker_v1 schema as the default schema.
+	err = db.Exec("SET search_path TO netmaker_v1").Error
+	if err != nil {
+		return nil, err
+	}
+
+	return db, nil
+}
+func GetSQLConf() config.SQLConfig {
+	var cfg config.SQLConfig
+	cfg.Host = GetSQLHost()
+	cfg.Port = GetSQLPort()
+	cfg.Username = GetSQLUser()
+	cfg.Password = GetSQLPass()
+	cfg.DB = GetSQLDB()
+	cfg.SSLMode = GetSQLSSLMode()
+	return cfg
+}
+func GetSQLHost() string {
+	host := "localhost"
+	if os.Getenv("SQL_HOST") != "" {
+		host = os.Getenv("SQL_HOST")
+	} else if config.Config.SQL.Host != "" {
+		host = config.Config.SQL.Host
+	}
+	return host
+}
+func GetSQLPort() int32 {
+	port := int32(5432)
+	envport, err := strconv.Atoi(os.Getenv("SQL_PORT"))
+	if err == nil && envport != 0 {
+		port = int32(envport)
+	} else if config.Config.SQL.Port != 0 {
+		port = config.Config.SQL.Port
+	}
+	return port
+}
+func GetSQLUser() string {
+	user := "postgres"
+	if os.Getenv("SQL_USER") != "" {
+		user = os.Getenv("SQL_USER")
+	} else if config.Config.SQL.Username != "" {
+		user = config.Config.SQL.Username
+	}
+	return user
+}
+func GetSQLPass() string {
+	pass := "nopass"
+	if os.Getenv("SQL_PASS") != "" {
+		pass = os.Getenv("SQL_PASS")
+	} else if config.Config.SQL.Password != "" {
+		pass = config.Config.SQL.Password
+	}
+	return pass
+}
+func GetSQLDB() string {
+	db := "netmaker"
+	if os.Getenv("SQL_DB") != "" {
+		db = os.Getenv("SQL_DB")
+	} else if config.Config.SQL.DB != "" {
+		db = config.Config.SQL.DB
+	}
+	return db
+}
+func GetSQLSSLMode() string {
+	sslmode := "disable"
+	if os.Getenv("SQL_SSL_MODE") != "" {
+		sslmode = os.Getenv("SQL_SSL_MODE")
+	} else if config.Config.SQL.SSLMode != "" {
+		sslmode = config.Config.SQL.SSLMode
+	}
+	return sslmode
+}

+ 54 - 0
db/sqlite.go

@@ -0,0 +1,54 @@
+package db
+
+import (
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+	"os"
+	"path/filepath"
+)
+
+// sqliteConnector for initializing and
+// connecting to a sqlite database.
+type sqliteConnector struct{}
+
+// sqliteConnector.connect connects and
+// initializes a connection to sqlite.
+func (s *sqliteConnector) connect() (*gorm.DB, error) {
+	// ensure data dir exists.
+	_, err := os.Stat("data")
+	if err != nil {
+		if os.IsNotExist(err) {
+			err = os.Mkdir("data", 0700)
+			if err != nil {
+				return nil, err
+			}
+		} else {
+			return nil, err
+		}
+	}
+
+	dbFilePath := filepath.Join("data", "netmaker_v1.db")
+
+	// ensure netmaker_v1.db exists.
+	_, err = os.Stat(dbFilePath)
+	if err != nil {
+		if os.IsNotExist(err) {
+			file, err := os.Create(dbFilePath)
+			if err != nil {
+				return nil, err
+			}
+
+			err = file.Close()
+			if err != nil {
+				return nil, err
+			}
+		} else {
+			return nil, err
+		}
+	}
+
+	return gorm.Open(sqlite.Open(dbFilePath), &gorm.Config{
+		Logger: logger.Default.LogMode(logger.Silent),
+	})
+}

+ 16 - 5
go.mod

@@ -1,6 +1,8 @@
 module github.com/gravitl/netmaker
 module github.com/gravitl/netmaker
 
 
-go 1.23
+go 1.23.0
+
+toolchain go1.23.7
 
 
 require (
 require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/blang/semver v3.5.1+incompatible
@@ -18,11 +20,11 @@ require (
 	github.com/stretchr/testify v1.10.0
 	github.com/stretchr/testify v1.10.0
 	github.com/txn2/txeh v1.5.5
 	github.com/txn2/txeh v1.5.5
 	go.uber.org/automaxprocs v1.6.0
 	go.uber.org/automaxprocs v1.6.0
-	golang.org/x/crypto v0.32.0
+	golang.org/x/crypto v0.36.0
 	golang.org/x/net v0.34.0 // indirect
 	golang.org/x/net v0.34.0 // indirect
 	golang.org/x/oauth2 v0.24.0
 	golang.org/x/oauth2 v0.24.0
-	golang.org/x/sys v0.29.0 // indirect
-	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	golang.org/x/text v0.23.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	gopkg.in/yaml.v3 v3.0.1
 	gopkg.in/yaml.v3 v3.0.1
 )
 )
@@ -53,11 +55,20 @@ require (
 	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+	github.com/jackc/pgx/v5 v5.7.2 // indirect
+	github.com/jackc/puddle/v2 v2.2.2 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/seancfoley/bintree v1.3.1 // indirect
 	github.com/seancfoley/bintree v1.3.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gorm.io/driver/postgres v1.5.11 // indirect
+	gorm.io/driver/sqlite v1.5.7 // indirect
+	gorm.io/gorm v1.25.12 // indirect
 )
 )
 
 
 require (
 require (
@@ -69,5 +80,5 @@ require (
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/sync v0.10.0 // indirect
+	golang.org/x/sync v0.12.0 // indirect
 )
 )

+ 28 - 0
go.sum

@@ -49,8 +49,21 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
 github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
+github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -90,6 +103,7 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@@ -103,6 +117,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
 golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
 golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 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/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -121,6 +137,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
 golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -131,6 +149,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
 golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -144,6 +164,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -161,3 +183,9 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
+gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
+gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
+gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

+ 46 - 1
logic/acls.go

@@ -1334,6 +1334,51 @@ func getUserAclRulesForNode(targetnode *models.Node,
 	return rules
 	return rules
 }
 }
 
 
+func checkIfAnyActiveEgressPolicy(targetNode models.Node) bool {
+	if !targetNode.IsEgressGateway {
+		return false
+	}
+	var targetNodeTags = make(map[models.TagID]struct{})
+	if targetNode.Mutex != nil {
+		targetNode.Mutex.Lock()
+		targetNodeTags = maps.Clone(targetNode.Tags)
+		targetNode.Mutex.Unlock()
+	} else {
+		targetNodeTags = maps.Clone(targetNode.Tags)
+	}
+	if targetNodeTags == nil {
+		targetNodeTags = make(map[models.TagID]struct{})
+	}
+	targetNodeTags[models.TagID(targetNode.ID.String())] = struct{}{}
+	targetNodeTags["*"] = struct{}{}
+	acls, _ := ListAclsByNetwork(models.NetworkID(targetNode.Network))
+	for _, acl := range acls {
+		if !acl.Enabled {
+			continue
+		}
+		srcTags := convAclTagToValueMap(acl.Src)
+		dstTags := convAclTagToValueMap(acl.Dst)
+		for nodeTag := range targetNodeTags {
+			if acl.RuleType == models.DevicePolicy {
+				if _, ok := srcTags[nodeTag.String()]; ok {
+					return true
+				}
+				if _, ok := srcTags[targetNode.ID.String()]; ok {
+					return true
+				}
+			}
+
+			if _, ok := dstTags[nodeTag.String()]; ok {
+				return true
+			}
+			if _, ok := dstTags[targetNode.ID.String()]; ok {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool {
 func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool {
 	var targetNodeTags = make(map[models.TagID]struct{})
 	var targetNodeTags = make(map[models.TagID]struct{})
 	if targetNode.Mutex != nil {
 	if targetNode.Mutex != nil {
@@ -1617,7 +1662,7 @@ func GetEgressRulesForNode(targetnode models.Node) (rules map[string]models.AclR
 	/*
 	/*
 		 if target node is egress gateway
 		 if target node is egress gateway
 			if acl policy has egress route and it is present in target node egress ranges
 			if acl policy has egress route and it is present in target node egress ranges
-			fetches all the nodes in that policy and add rules
+			fetch all the nodes in that policy and add rules
 	*/
 	*/
 
 
 	for _, rangeI := range targetnode.EgressGatewayRanges {
 	for _, rangeI := range targetnode.EgressGatewayRanges {

+ 4 - 5
logic/auth.go

@@ -349,19 +349,18 @@ func ValidateUser(user *models.User) error {
 }
 }
 
 
 // DeleteUser - deletes a given user
 // DeleteUser - deletes a given user
-func DeleteUser(user string) (bool, error) {
+func DeleteUser(user string) error {
 
 
 	if userRecord, err := database.FetchRecord(database.USERS_TABLE_NAME, user); err != nil || len(userRecord) == 0 {
 	if userRecord, err := database.FetchRecord(database.USERS_TABLE_NAME, user); err != nil || len(userRecord) == 0 {
-		return false, errors.New("user does not exist")
+		return errors.New("user does not exist")
 	}
 	}
 
 
 	err := database.DeleteRecord(database.USERS_TABLE_NAME, user)
 	err := database.DeleteRecord(database.USERS_TABLE_NAME, user)
 	if err != nil {
 	if err != nil {
-		return false, err
+		return err
 	}
 	}
 	go RemoveUserFromAclPolicy(user)
 	go RemoveUserFromAclPolicy(user)
-
-	return true, nil
+	return (&models.UserAccessToken{UserName: user}).DeleteAllUserTokens()
 }
 }
 
 
 func SetAuthSecret(secret string) error {
 func SetAuthSecret(secret string) error {

+ 2 - 1
logic/enrollmentkey.go

@@ -38,7 +38,7 @@ var (
 )
 )
 
 
 // CreateEnrollmentKey - creates a new enrollment key in db
 // CreateEnrollmentKey - creates a new enrollment key in db
-func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, groups []models.TagID, unlimited bool, relay uuid.UUID, defaultKey bool) (*models.EnrollmentKey, error) {
+func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, groups []models.TagID, unlimited bool, relay uuid.UUID, defaultKey, autoEgress bool) (*models.EnrollmentKey, error) {
 	newKeyID, err := getUniqueEnrollmentID()
 	newKeyID, err := getUniqueEnrollmentID()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -54,6 +54,7 @@ func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string
 		Relay:         relay,
 		Relay:         relay,
 		Groups:        groups,
 		Groups:        groups,
 		Default:       defaultKey,
 		Default:       defaultKey,
+		AutoEgress:    autoEgress,
 	}
 	}
 	if uses > 0 {
 	if uses > 0 {
 		k.UsesRemaining = uses
 		k.UsesRemaining = uses

+ 13 - 13
logic/enrollmentkey_test.go

@@ -14,35 +14,35 @@ func TestCreateEnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
 	t.Run("Can_Not_Create_Key", func(t *testing.T) {
 	t.Run("Can_Not_Create_Key", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
 		assert.Nil(t, newKey)
 		assert.Nil(t, newKey)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.ErrorIs(t, err, models.ErrInvalidEnrollmentKey)
 		assert.ErrorIs(t, err, models.ErrInvalidEnrollmentKey)
 	})
 	})
 	t.Run("Can_Create_Key_Uses", func(t *testing.T) {
 	t.Run("Can_Create_Key_Uses", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.Equal(t, 1, newKey.UsesRemaining)
 		assert.Equal(t, 1, newKey.UsesRemaining)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 	})
 	})
 	t.Run("Can_Create_Key_Time", func(t *testing.T) {
 	t.Run("Can_Create_Key_Time", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, nil, false, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, nil, false, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 	})
 	})
 	t.Run("Can_Create_Key_Unlimited", func(t *testing.T) {
 	t.Run("Can_Create_Key_Unlimited", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 	})
 	})
 	t.Run("Can_Create_Key_WithNetworks", func(t *testing.T) {
 	t.Run("Can_Create_Key_WithNetworks", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 		assert.True(t, len(newKey.Networks) == 2)
 		assert.True(t, len(newKey.Networks) == 2)
 	})
 	})
 	t.Run("Can_Create_Key_WithTags", func(t *testing.T) {
 	t.Run("Can_Create_Key_WithTags", func(t *testing.T) {
-		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, nil, true, uuid.Nil, false)
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, nil, true, uuid.Nil, false, false)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 		assert.True(t, len(newKey.Tags) == 2)
 		assert.True(t, len(newKey.Tags) == 2)
@@ -62,7 +62,7 @@ func TestCreateEnrollmentKey(t *testing.T) {
 func TestDelete_EnrollmentKey(t *testing.T) {
 func TestDelete_EnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 	t.Run("Can_Delete_Key", func(t *testing.T) {
 	t.Run("Can_Delete_Key", func(t *testing.T) {
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 		err := DeleteEnrollmentKey(newKey.Value, false)
 		err := DeleteEnrollmentKey(newKey.Value, false)
@@ -83,7 +83,7 @@ func TestDelete_EnrollmentKey(t *testing.T) {
 func TestDecrement_EnrollmentKey(t *testing.T) {
 func TestDecrement_EnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
 	t.Run("Check_initial_uses", func(t *testing.T) {
 	t.Run("Check_initial_uses", func(t *testing.T) {
 		assert.True(t, newKey.IsValid())
 		assert.True(t, newKey.IsValid())
 		assert.Equal(t, newKey.UsesRemaining, 1)
 		assert.Equal(t, newKey.UsesRemaining, 1)
@@ -107,9 +107,9 @@ func TestDecrement_EnrollmentKey(t *testing.T) {
 func TestUsability_EnrollmentKey(t *testing.T) {
 func TestUsability_EnrollmentKey(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
-	key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
-	key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, nil, false, uuid.Nil, false)
-	key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false)
+	key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false, false)
+	key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, nil, false, uuid.Nil, false, false)
+	key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false, false)
 	t.Run("Check if valid use key can be used", func(t *testing.T) {
 	t.Run("Check if valid use key can be used", func(t *testing.T) {
 		assert.Equal(t, key1.UsesRemaining, 1)
 		assert.Equal(t, key1.UsesRemaining, 1)
 		ok := TryToUseEnrollmentKey(key1)
 		ok := TryToUseEnrollmentKey(key1)
@@ -145,7 +145,7 @@ func removeAllEnrollments() {
 func TestTokenize_EnrollmentKeys(t *testing.T) {
 func TestTokenize_EnrollmentKeys(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 	const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
 	const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
 	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const serverAddr = "api.myserver.com"
 	const serverAddr = "api.myserver.com"
@@ -178,7 +178,7 @@ func TestTokenize_EnrollmentKeys(t *testing.T) {
 func TestDeTokenize_EnrollmentKeys(t *testing.T) {
 func TestDeTokenize_EnrollmentKeys(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
-	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false, false)
 	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const serverAddr = "api.myserver.com"
 	const serverAddr = "api.myserver.com"
 
 

+ 55 - 16
logic/jwts.go

@@ -53,17 +53,19 @@ func CreateJWT(uuid string, macAddress string, network string) (response string,
 }
 }
 
 
 // CreateUserJWT - creates a user jwt token
 // CreateUserJWT - creates a user jwt token
-func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) {
-	expirationTime := time.Now().Add(GetJwtValidityDuration())
+func CreateUserAccessJwtToken(username string, role models.UserRoleID, d time.Time, tokenID string) (response string, err error) {
 	claims := &models.UserClaims{
 	claims := &models.UserClaims{
 		UserName:       username,
 		UserName:       username,
 		Role:           role,
 		Role:           role,
-		RacAutoDisable: GetRacAutoDisable() && (role != models.SuperAdminRole && role != models.AdminRole),
+		TokenType:      models.AccessTokenType,
+		Api:            servercfg.GetAPIHost(),
+		RacAutoDisable: servercfg.GetRacAutoDisable() && (role != models.SuperAdminRole && role != models.AdminRole),
 		RegisteredClaims: jwt.RegisteredClaims{
 		RegisteredClaims: jwt.RegisteredClaims{
 			Issuer:    "Netmaker",
 			Issuer:    "Netmaker",
 			Subject:   fmt.Sprintf("user|%s", username),
 			Subject:   fmt.Sprintf("user|%s", username),
 			IssuedAt:  jwt.NewNumericDate(time.Now()),
 			IssuedAt:  jwt.NewNumericDate(time.Now()),
-			ExpiresAt: jwt.NewNumericDate(expirationTime),
+			ExpiresAt: jwt.NewNumericDate(d),
+			ID:        tokenID,
 		},
 		},
 	}
 	}
 
 
@@ -75,16 +77,28 @@ func CreateUserJWT(username string, role models.UserRoleID) (response string, er
 	return "", err
 	return "", err
 }
 }
 
 
-// VerifyJWT verifies Auth Header
-func VerifyJWT(bearerToken string) (username string, issuperadmin, isadmin bool, err error) {
-	token := ""
-	tokenSplit := strings.Split(bearerToken, " ")
-	if len(tokenSplit) > 1 {
-		token = tokenSplit[1]
-	} else {
-		return "", false, false, errors.New("invalid auth header")
+// CreateUserJWT - creates a user jwt token
+func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) {
+	expirationTime := time.Now().Add(GetJwtValidityDuration())
+	claims := &models.UserClaims{
+		UserName:       username,
+		Role:           role,
+		TokenType:      models.UserIDTokenType,
+		RacAutoDisable: servercfg.GetRacAutoDisable() && (role != models.SuperAdminRole && role != models.AdminRole),
+		RegisteredClaims: jwt.RegisteredClaims{
+			Issuer:    "Netmaker",
+			Subject:   fmt.Sprintf("user|%s", username),
+			IssuedAt:  jwt.NewNumericDate(time.Now()),
+			ExpiresAt: jwt.NewNumericDate(expirationTime),
+		},
+	}
+
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	tokenString, err := token.SignedString(jwtSecretKey)
+	if err == nil {
+		return tokenString, nil
 	}
 	}
-	return VerifyUserToken(token)
+	return "", err
 }
 }
 
 
 func GetUserNameFromToken(authtoken string) (username string, err error) {
 func GetUserNameFromToken(authtoken string) (username string, err error) {
@@ -107,6 +121,20 @@ func GetUserNameFromToken(authtoken string) (username string, err error) {
 	if err != nil {
 	if err != nil {
 		return "", Unauthorized_Err
 		return "", Unauthorized_Err
 	}
 	}
+	if claims.TokenType == models.AccessTokenType {
+		jti := claims.ID
+		if jti != "" {
+			a := models.UserAccessToken{ID: jti}
+			// check if access token is active
+			err := a.Get()
+			if err != nil {
+				err = errors.New("token revoked")
+				return "", err
+			}
+			a.LastUsed = time.Now()
+			a.Update()
+		}
+	}
 
 
 	if token != nil && token.Valid {
 	if token != nil && token.Valid {
 		var user *models.User
 		var user *models.User
@@ -131,15 +159,26 @@ func GetUserNameFromToken(authtoken string) (username string, err error) {
 // VerifyUserToken func will used to Verify the JWT Token while using APIS
 // VerifyUserToken func will used to Verify the JWT Token while using APIS
 func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin bool, err error) {
 func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin bool, err error) {
 	claims := &models.UserClaims{}
 	claims := &models.UserClaims{}
-
 	if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" {
 	if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" {
 		return MasterUser, true, true, nil
 		return MasterUser, true, true, nil
 	}
 	}
-
 	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
 	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
 		return jwtSecretKey, nil
 		return jwtSecretKey, nil
 	})
 	})
-
+	if claims.TokenType == models.AccessTokenType {
+		jti := claims.ID
+		if jti != "" {
+			a := models.UserAccessToken{ID: jti}
+			// check if access token is active
+			err := a.Get()
+			if err != nil {
+				err = errors.New("token revoked")
+				return "", false, false, err
+			}
+			a.LastUsed = time.Now()
+			a.Update()
+		}
+	}
 	if token != nil && token.Valid {
 	if token != nil && token.Valid {
 		var user *models.User
 		var user *models.User
 		// check that user exists
 		// check that user exists

+ 1 - 0
logic/networks.go

@@ -302,6 +302,7 @@ func CreateNetwork(network models.Network) (models.Network, error) {
 		true,
 		true,
 		uuid.Nil,
 		uuid.Nil,
 		true,
 		true,
+		false,
 	)
 	)
 
 
 	return network, nil
 	return network, nil

+ 1 - 1
logic/peers.go

@@ -204,7 +204,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 		defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 
 
-		if (defaultDevicePolicy.Enabled && defaultUserPolicy.Enabled) || !checkIfAnyPolicyisUniDirectional(node) {
+		if (defaultDevicePolicy.Enabled && defaultUserPolicy.Enabled) || (!checkIfAnyPolicyisUniDirectional(node) && !checkIfAnyActiveEgressPolicy(node)) {
 			if node.NetworkRange.IP != nil {
 			if node.NetworkRange.IP != nil {
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange)
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange)
 			}
 			}

+ 50 - 1
main.go

@@ -3,6 +3,8 @@ package main
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/rand"
+	"encoding/json"
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
@@ -12,9 +14,11 @@ import (
 	"sync"
 	"sync"
 	"syscall"
 	"syscall"
 
 
+	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/config"
 	"github.com/gravitl/netmaker/config"
 	controller "github.com/gravitl/netmaker/controllers"
 	controller "github.com/gravitl/netmaker/controllers"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
@@ -22,9 +26,11 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/schema"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
 	"github.com/gravitl/netmaker/serverctl"
 	_ "go.uber.org/automaxprocs"
 	_ "go.uber.org/automaxprocs"
+	"golang.org/x/crypto/nacl/box"
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 )
 )
 
 
@@ -99,8 +105,13 @@ func initialize() { // Client Mode Prereq Check
 	if err = database.InitializeDatabase(); err != nil {
 	if err = database.InitializeDatabase(); err != nil {
 		logger.FatalLog("Error connecting to database: ", err.Error())
 		logger.FatalLog("Error connecting to database: ", err.Error())
 	}
 	}
+	// initialize sql schema db.
+	err = db.InitializeDB(schema.ListModels()...)
+	if err != nil {
+		logger.FatalLog("Error connecting to v1 database: ", err.Error())
+	}
 	logger.Log(0, "database successfully connected")
 	logger.Log(0, "database successfully connected")
-
+	initializeUUID()
 	//initialize cache
 	//initialize cache
 	_, _ = logic.GetNetworks()
 	_, _ = logic.GetNetworks()
 	_, _ = logic.GetAllNodes()
 	_, _ = logic.GetAllNodes()
@@ -247,3 +258,41 @@ func setGarbageCollection() {
 		debug.SetGCPercent(ncutils.DEFAULT_GC_PERCENT)
 		debug.SetGCPercent(ncutils.DEFAULT_GC_PERCENT)
 	}
 	}
 }
 }
+
+// initializeUUID - create a UUID record for server if none exists
+func initializeUUID() error {
+	records, err := database.FetchRecords(database.SERVER_UUID_TABLE_NAME)
+	if err != nil {
+		if !database.IsEmptyRecord(err) {
+			return err
+		}
+	} else if len(records) > 0 {
+		return nil
+	}
+	// setup encryption keys
+	var trafficPubKey, trafficPrivKey, errT = box.GenerateKey(rand.Reader) // generate traffic keys
+	if errT != nil {
+		return errT
+	}
+	tPriv, err := ncutils.ConvertKeyToBytes(trafficPrivKey)
+	if err != nil {
+		return err
+	}
+
+	tPub, err := ncutils.ConvertKeyToBytes(trafficPubKey)
+	if err != nil {
+		return err
+	}
+
+	telemetry := models.Telemetry{
+		UUID:           uuid.NewString(),
+		TrafficKeyPriv: tPriv,
+		TrafficKeyPub:  tPub,
+	}
+	telJSON, err := json.Marshal(&telemetry)
+	if err != nil {
+		return err
+	}
+
+	return database.Insert(database.SERVER_UUID_RECORD_KEY, string(telJSON), database.SERVER_UUID_TABLE_NAME)
+}

+ 1 - 0
migrate/migrate.go

@@ -152,6 +152,7 @@ func updateEnrollmentKeys() {
 			true,
 			true,
 			uuid.Nil,
 			uuid.Nil,
 			true,
 			true,
+			false,
 		)
 		)
 
 
 	}
 	}

+ 183 - 0
migrate/migrate_schema.go

@@ -0,0 +1,183 @@
+package migrate
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
+	"github.com/gravitl/netmaker/schema"
+	"github.com/gravitl/netmaker/servercfg"
+	"gorm.io/gorm"
+	"os"
+	"path/filepath"
+)
+
+// ToSQLSchema migrates the data from key-value
+// db to sql db.
+//
+// This function archives the old data and does not
+// delete it.
+//
+// Based on the db server, the archival is done in the
+// following way:
+//
+// 1. Sqlite: Moves the old data to a
+// netmaker_archive.db file.
+//
+// 2. Postgres: Moves the data to a netmaker_archive
+// schema within the same database.
+func ToSQLSchema() error {
+	// initialize sql schema db.
+	err := db.InitializeDB(schema.ListModels()...)
+	if err != nil {
+		return err
+	}
+
+	// migrate, if not done already.
+	err = migrate()
+	if err != nil {
+		return err
+	}
+
+	// archive key-value schema db, if not done already.
+	// ignore errors.
+	_ = archive()
+
+	return nil
+}
+
+func migrate() error {
+	// begin a new transaction.
+	dbctx := db.BeginTx(context.TODO())
+	commit := false
+	defer func() {
+		if commit {
+			db.FromContext(dbctx).Commit()
+		} else {
+			db.FromContext(dbctx).Rollback()
+		}
+	}()
+
+	// check if migrated already.
+	migrationJob := &schema.Job{
+		ID: "migration-v1.0.0",
+	}
+	err := migrationJob.Get(dbctx)
+	if err != nil {
+		if !errors.Is(err, gorm.ErrRecordNotFound) {
+			return err
+		}
+
+		// initialize key-value schema db.
+		err := database.InitializeDatabase()
+		if err != nil {
+			return err
+		}
+		defer database.CloseDB()
+
+		// migrate.
+		// TODO: add migration code.
+
+		// mark migration job completed.
+		err = migrationJob.Create(dbctx)
+		if err != nil {
+			return err
+		}
+
+		commit = true
+	}
+
+	return nil
+}
+
+func archive() error {
+	dbServer := servercfg.GetDB()
+	if dbServer != "sqlite" && dbServer != "postgres" {
+		return nil
+	}
+
+	// begin a new transaction.
+	dbctx := db.BeginTx(context.TODO())
+	commit := false
+	defer func() {
+		if commit {
+			db.FromContext(dbctx).Commit()
+		} else {
+			db.FromContext(dbctx).Rollback()
+		}
+	}()
+
+	// check if key-value schema db archived already.
+	archivalJob := &schema.Job{
+		ID: "archival-v1.0.0",
+	}
+	err := archivalJob.Get(dbctx)
+	if err != nil {
+		if !errors.Is(err, gorm.ErrRecordNotFound) {
+			return err
+		}
+
+		// archive.
+		switch dbServer {
+		case "sqlite":
+			err = sqliteArchiveOldData()
+		default:
+			err = pgArchiveOldData()
+		}
+		if err != nil {
+			return err
+		}
+
+		// mark archival job completed.
+		err = archivalJob.Create(dbctx)
+		if err != nil {
+			return err
+		}
+
+		commit = true
+	} else {
+		// remove the residual
+		if dbServer == "sqlite" {
+			_ = os.Remove(filepath.Join("data", "netmaker.db"))
+		}
+	}
+
+	return nil
+}
+
+func sqliteArchiveOldData() error {
+	oldDBFilePath := filepath.Join("data", "netmaker.db")
+	archiveDBFilePath := filepath.Join("data", "netmaker_archive.db")
+
+	// check if netmaker_archive.db exist.
+	_, err := os.Stat(archiveDBFilePath)
+	if err == nil {
+		return nil
+	} else if !os.IsNotExist(err) {
+		return err
+	}
+
+	// rename old db file to netmaker_archive.db.
+	return os.Rename(oldDBFilePath, archiveDBFilePath)
+}
+
+func pgArchiveOldData() error {
+	_, err := database.PGDB.Exec("CREATE SCHEMA IF NOT EXISTS netmaker_archive")
+	if err != nil {
+		return err
+	}
+
+	for _, table := range database.Tables {
+		_, err := database.PGDB.Exec(
+			fmt.Sprintf(
+				"ALTER TABLE public.%s SET SCHEMA netmaker_archive",
+				table,
+			),
+		)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 55 - 8
models/accessToken.go

@@ -1,13 +1,60 @@
 package models
 package models
 
 
-// AccessToken - token used to access netmaker
-type AccessToken struct {
-	APIConnString string `json:"apiconnstring"`
-	ClientConfig
+import (
+	"context"
+	"time"
+
+	"github.com/gravitl/netmaker/db"
+)
+
+// accessTokenTableName - access tokens table
+const accessTokenTableName = "user_access_tokens"
+
+// UserAccessToken - token used to access netmaker
+type UserAccessToken struct {
+	ID        string    `gorm:"id,primary_key" json:"id"`
+	Name      string    `gorm:"name" json:"name"`
+	UserName  string    `gorm:"user_name" json:"user_name"`
+	ExpiresAt time.Time `gorm:"expires_at" json:"expires_at"`
+	LastUsed  time.Time `gorm:"last_used" json:"last_used"`
+	CreatedBy string    `gorm:"created_by" json:"created_by"`
+	CreatedAt time.Time `gorm:"created_at" json:"created_at"`
+}
+
+func (a *UserAccessToken) Table() string {
+	return accessTokenTableName
+}
+
+func (a *UserAccessToken) Get() error {
+	return db.FromContext(context.TODO()).Table(a.Table()).First(&a).Where("id = ?", a.ID).Error
+}
+
+func (a *UserAccessToken) Update() error {
+	return db.FromContext(context.TODO()).Table(a.Table()).Where("id = ?", a.ID).Updates(&a).Error
 }
 }
 
 
-// ClientConfig - the config of the client
-type ClientConfig struct {
-	Network string `json:"network"`
-	Key     string `json:"key"`
+func (a *UserAccessToken) Create() error {
+	return db.FromContext(context.TODO()).Table(a.Table()).Create(&a).Error
+}
+
+func (a *UserAccessToken) List() (ats []UserAccessToken, err error) {
+	err = db.FromContext(context.TODO()).Table(a.Table()).Find(&ats).Error
+	return
+}
+
+func (a *UserAccessToken) ListByUser() (ats []UserAccessToken) {
+	db.FromContext(context.TODO()).Table(a.Table()).Where("user_name = ?", a.UserName).Find(&ats)
+	if ats == nil {
+		ats = []UserAccessToken{}
+	}
+	return
+}
+
+func (a *UserAccessToken) Delete() error {
+	return db.FromContext(context.TODO()).Table(a.Table()).Where("id = ?", a.ID).Delete(&a).Error
+}
+
+func (a *UserAccessToken) DeleteAllUserTokens() error {
+	return db.FromContext(context.TODO()).Table(a.Table()).Where("user_name = ?", a.UserName).Delete(&a).Error
+
 }
 }

+ 2 - 0
models/enrollment_key.go

@@ -54,6 +54,7 @@ type EnrollmentKey struct {
 	Relay         uuid.UUID `json:"relay"`
 	Relay         uuid.UUID `json:"relay"`
 	Groups        []TagID   `json:"groups"`
 	Groups        []TagID   `json:"groups"`
 	Default       bool      `json:"default"`
 	Default       bool      `json:"default"`
+	AutoEgress    bool      `json:"auto_egress"`
 }
 }
 
 
 // APIEnrollmentKey - used to create enrollment keys via API
 // APIEnrollmentKey - used to create enrollment keys via API
@@ -66,6 +67,7 @@ type APIEnrollmentKey struct {
 	Type          KeyType  `json:"type"`
 	Type          KeyType  `json:"type"`
 	Relay         string   `json:"relay"`
 	Relay         string   `json:"relay"`
 	Groups        []TagID  `json:"groups"`
 	Groups        []TagID  `json:"groups"`
+	AutoEgress    bool     `json:"auto_egress"`
 }
 }
 
 
 // RegisterResponse - the response to a successful enrollment register
 // RegisterResponse - the response to a successful enrollment register

+ 1 - 0
models/structs.go

@@ -263,6 +263,7 @@ type NodeJoinResponse struct {
 type ServerConfig struct {
 type ServerConfig struct {
 	CoreDNSAddr       string `yaml:"corednsaddr"`
 	CoreDNSAddr       string `yaml:"corednsaddr"`
 	API               string `yaml:"api"`
 	API               string `yaml:"api"`
+	APIHost           string `yaml:"apihost"`
 	APIPort           string `yaml:"apiport"`
 	APIPort           string `yaml:"apiport"`
 	DNSMode           string `yaml:"dnsmode"`
 	DNSMode           string `yaml:"dnsmode"`
 	Version           string `yaml:"version"`
 	Version           string `yaml:"version"`

+ 12 - 0
models/user_mgmt.go

@@ -13,6 +13,7 @@ type RsrcID string
 type UserRoleID string
 type UserRoleID string
 type UserGroupID string
 type UserGroupID string
 type AuthType string
 type AuthType string
+type TokenType string
 
 
 var (
 var (
 	BasicAuth AuthType = "basic_auth"
 	BasicAuth AuthType = "basic_auth"
@@ -35,6 +36,15 @@ func GetRAGRoleID(netID, hostID string) UserRoleID {
 	return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostID))
 	return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostID))
 }
 }
 
 
+func (t TokenType) String() string {
+	return string(t)
+}
+
+var (
+	UserIDTokenType TokenType = "user_id_token"
+	AccessTokenType TokenType = "access_token"
+)
+
 var RsrcTypeMap = map[RsrcType]struct{}{
 var RsrcTypeMap = map[RsrcType]struct{}{
 	HostRsrc:           {},
 	HostRsrc:           {},
 	RelayRsrc:          {},
 	RelayRsrc:          {},
@@ -185,6 +195,8 @@ type UserAuthParams struct {
 type UserClaims struct {
 type UserClaims struct {
 	Role           UserRoleID
 	Role           UserRoleID
 	UserName       string
 	UserName       string
+	Api            string
+	TokenType      TokenType
 	RacAutoDisable bool
 	RacAutoDisable bool
 	jwt.RegisteredClaims
 	jwt.RegisteredClaims
 }
 }

+ 36 - 0
schema/jobs.go

@@ -0,0 +1,36 @@
+package schema
+
+import (
+	"context"
+	"github.com/gravitl/netmaker/db"
+	"time"
+)
+
+// Job represents a task that netmaker server
+// wants to do.
+//
+// Ideally, a jobs table should have details
+// about its type, status, who initiated it,
+// etc. But, for now, the table only contains
+// records of jobs that have been done, so
+// that it is easier to prevent a task from
+// being executed again.
+type Job struct {
+	ID        string    `gorm:"id;primary_key"`
+	CreatedAt time.Time `gorm:"created_at"`
+}
+
+// TableName returns the name of the jobs table.
+func (j *Job) TableName() string {
+	return "jobs"
+}
+
+// Create creates a job record in the jobs table.
+func (j *Job) Create(ctx context.Context) error {
+	return db.FromContext(ctx).Table(j.TableName()).Create(j).Error
+}
+
+// Get returns a job record with the given Job.ID.
+func (j *Job) Get(ctx context.Context) error {
+	return db.FromContext(ctx).Table(j.TableName()).Where("id = ?", j.ID).First(j).Error
+}

+ 11 - 0
schema/models.go

@@ -0,0 +1,11 @@
+package schema
+
+import "github.com/gravitl/netmaker/models"
+
+// ListModels lists all the models in this schema.
+func ListModels() []interface{} {
+	return []interface{}{
+		&Job{},
+		&models.UserAccessToken{},
+	}
+}

+ 206 - 103
swagger.yaml

@@ -1,7 +1,6 @@
 definitions:
 definitions:
   acls.ACL:
   acls.ACL:
     additionalProperties:
     additionalProperties:
-      format: int32
       type: integer
       type: integer
     type: object
     type: object
   acls.ACLContainer:
   acls.ACLContainer:
@@ -56,15 +55,15 @@ definitions:
         type: string
         type: string
       egressesLimit:
       egressesLimit:
         type: integer
         type: integer
-      email_sender_addr:
+      emailSenderAddr:
         type: string
         type: string
-      email_sender_password:
+      emailSenderPassword:
         type: string
         type: string
-      email_sender_user:
+      emailSenderUser:
         type: string
         type: string
       emqxRestEndpoint:
       emqxRestEndpoint:
         type: string
         type: string
-      endpoint_detection:
+      endpointDetection:
         type: boolean
         type: boolean
       environment:
       environment:
         type: string
         type: string
@@ -91,6 +90,8 @@ definitions:
         type: string
         type: string
       metricsExporter:
       metricsExporter:
         type: string
         type: string
+      metricsPort:
+        type: integer
       mqpassword:
       mqpassword:
         type: string
         type: string
       mquserName:
       mquserName:
@@ -115,15 +116,17 @@ definitions:
         type: string
         type: string
       racAutoDisable:
       racAutoDisable:
         type: boolean
         type: boolean
+      racRestrictToSingleNetwork:
+        type: boolean
       restBackend:
       restBackend:
         type: string
         type: string
       server:
       server:
         type: string
         type: string
       serverBrokerEndpoint:
       serverBrokerEndpoint:
         type: string
         type: string
-      smtp_host:
+      smtpHost:
         type: string
         type: string
-      smtp_port:
+      smtpPort:
         type: integer
         type: integer
       sqlconn:
       sqlconn:
         type: string
         type: string
@@ -200,6 +203,14 @@ definitions:
         allOf:
         allOf:
         - $ref: '#/definitions/models.AllowedTrafficDirection'
         - $ref: '#/definitions/models.AllowedTrafficDirection'
         description: single or two-way
         description: single or two-way
+      dst:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
+      dst6:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
       id:
       id:
         type: string
         type: string
       ip_list:
       ip_list:
@@ -306,6 +317,10 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      egressgatewayranges_with_metric:
+        items:
+          $ref: '#/definitions/models.EgressRangeMetric'
+        type: array
       expdatetime:
       expdatetime:
         format: int64
         format: int64
         type: integer
         type: integer
@@ -424,9 +439,17 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      ranges_with_metric:
+        items:
+          $ref: '#/definitions/models.EgressRangeMetric'
+        type: array
     type: object
     type: object
   models.EgressInfo:
   models.EgressInfo:
     properties:
     properties:
+      egress_fw_rules:
+        additionalProperties:
+          $ref: '#/definitions/models.AclRule'
+        type: object
       egress_gateway_cfg:
       egress_gateway_cfg:
         $ref: '#/definitions/models.EgressGatewayRequest'
         $ref: '#/definitions/models.EgressGatewayRequest'
       egress_gw_addr:
       egress_gw_addr:
@@ -450,10 +473,26 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      egress_ranges_metric:
+        items:
+          $ref: '#/definitions/models.EgressRangeMetric'
+        type: array
+      network:
+        type: string
       node_addr:
       node_addr:
         $ref: '#/definitions/net.IPNet'
         $ref: '#/definitions/net.IPNet'
       node_addr6:
       node_addr6:
         $ref: '#/definitions/net.IPNet'
         $ref: '#/definitions/net.IPNet'
+      peer_key:
+        type: string
+    type: object
+  models.EgressRangeMetric:
+    properties:
+      network:
+        type: string
+      route_metric:
+        description: preffered range 1-999
+        type: integer
     type: object
     type: object
   models.EnrollmentKey:
   models.EnrollmentKey:
     properties:
     properties:
@@ -687,6 +726,7 @@ definitions:
   models.HostMqAction:
   models.HostMqAction:
     enum:
     enum:
     - UPGRADE
     - UPGRADE
+    - FORCE_UPGRADE
     - SIGNAL_HOST
     - SIGNAL_HOST
     - UPDATE_HOST
     - UPDATE_HOST
     - DELETE_HOST
     - DELETE_HOST
@@ -701,6 +741,7 @@ definitions:
     type: string
     type: string
     x-enum-varnames:
     x-enum-varnames:
     - Upgrade
     - Upgrade
+    - ForceUpgrade
     - SignalHost
     - SignalHost
     - UpdateHost
     - UpdateHost
     - DeleteHost
     - DeleteHost
@@ -724,6 +765,8 @@ definitions:
         type: boolean
         type: boolean
       listen_port:
       listen_port:
         type: integer
         type: integer
+      version:
+        type: string
     type: object
     type: object
   models.HostPull:
   models.HostPull:
     properties:
     properties:
@@ -821,8 +864,6 @@ definitions:
     type: object
     type: object
   models.IngressInfo:
   models.IngressInfo:
     properties:
     properties:
-      allow_all:
-        type: boolean
       egress_ranges:
       egress_ranges:
         items:
         items:
           $ref: '#/definitions/net.IPNet'
           $ref: '#/definitions/net.IPNet'
@@ -933,6 +974,10 @@ definitions:
         type: string
         type: string
       defaultudpholepunch:
       defaultudpholepunch:
         type: string
         type: string
+      dns_nameservers:
+        items:
+          type: string
+        type: array
       isipv4:
       isipv4:
         type: string
         type: string
       isipv6:
       isipv6:
@@ -1013,6 +1058,8 @@ definitions:
         type: string
         type: string
       is_fail_over:
       is_fail_over:
         type: boolean
         type: boolean
+      is_gw:
+        type: boolean
       is_static:
       is_static:
         type: boolean
         type: boolean
       is_user_node:
       is_user_node:
@@ -1090,6 +1137,7 @@ definitions:
     - warning
     - warning
     - error
     - error
     - unknown
     - unknown
+    - disconnected
     type: string
     type: string
     x-enum-varnames:
     x-enum-varnames:
     - OnlineSt
     - OnlineSt
@@ -1097,6 +1145,7 @@ definitions:
     - WarningSt
     - WarningSt
     - ErrorSt
     - ErrorSt
     - UnKnown
     - UnKnown
+    - Disconnected
   models.PeerMap:
   models.PeerMap:
     additionalProperties:
     additionalProperties:
       $ref: '#/definitions/models.IDandAddr'
       $ref: '#/definitions/models.IDandAddr'
@@ -1120,17 +1169,6 @@ definitions:
       server_config:
       server_config:
         $ref: '#/definitions/models.ServerConfig'
         $ref: '#/definitions/models.ServerConfig'
     type: object
     type: object
-  models.RelayRequest:
-    properties:
-      netid:
-        type: string
-      nodeid:
-        type: string
-      relayaddrs:
-        items:
-          type: string
-        type: array
-    type: object
   models.ReturnUser:
   models.ReturnUser:
     properties:
     properties:
       auth_type:
       auth_type:
@@ -1194,10 +1232,14 @@ definitions:
         type: string
         type: string
       dnsmode:
       dnsmode:
         type: string
         type: string
+      endpointDetection:
+        type: boolean
       manageDNS:
       manageDNS:
         type: boolean
         type: boolean
       metricInterval:
       metricInterval:
         type: string
         type: string
+      metricsPort:
+        type: integer
       mqpassword:
       mqpassword:
         type: string
         type: string
       mqport:
       mqport:
@@ -1293,7 +1335,6 @@ definitions:
           type: object
           type: object
         type: object
         type: object
       username:
       username:
-        maxLength: 40
         minLength: 3
         minLength: 3
         type: string
         type: string
     required:
     required:
@@ -1308,12 +1349,16 @@ definitions:
     type: object
     type: object
   models.UserRemoteGws:
   models.UserRemoteGws:
     properties:
     properties:
+      addresses:
+        type: string
       allowed_endpoints:
       allowed_endpoints:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
       connected:
       connected:
         type: boolean
         type: boolean
+      dns_address:
+        type: string
       gw_client:
       gw_client:
         $ref: '#/definitions/models.ExtClient'
         $ref: '#/definitions/models.ExtClient'
       gw_listen_port:
       gw_listen_port:
@@ -1334,6 +1379,8 @@ definitions:
         type: array
         type: array
       remote_access_gw_id:
       remote_access_gw_id:
         type: string
         type: string
+      status:
+        $ref: '#/definitions/models.NodeStatus'
     type: object
     type: object
   models.UserRoleID:
   models.UserRoleID:
     enum:
     enum:
@@ -1390,7 +1437,6 @@ definitions:
       mask:
       mask:
         description: network mask
         description: network mask
         items:
         items:
-          format: int32
           type: integer
           type: integer
         type: array
         type: array
     type: object
     type: object
@@ -1427,7 +1473,6 @@ definitions:
           for this peer, if not nil.
           for this peer, if not nil.
 
 
           A non-nil value of 0 will clear the persistent keepalive interval.
           A non-nil value of 0 will clear the persistent keepalive interval.
-        format: int64
         type: integer
         type: integer
       presharedKey:
       presharedKey:
         description: |-
         description: |-
@@ -1826,6 +1871,28 @@ paths:
       summary: Get the current public IP address.
       summary: Get the current public IP address.
       tags:
       tags:
       - IP Service
       - IP Service
+  /api/host/{hostid}/peer_info:
+    get:
+      parameters:
+      - description: Host ID
+        in: path
+        name: hostid
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.SuccessResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      security:
+      - oauth: []
+      summary: Fetches host peerinfo
+      tags:
+      - Hosts
   /api/hosts:
   /api/hosts:
     get:
     get:
       responses:
       responses:
@@ -2006,6 +2073,10 @@ paths:
         name: hostid
         name: hostid
         required: true
         required: true
         type: string
         type: string
+      - description: Force upgrade
+        in: query
+        name: force
+        type: boolean
       responses:
       responses:
         "200":
         "200":
           description: passed message to upgrade host
           description: passed message to upgrade host
@@ -2067,6 +2138,35 @@ paths:
       summary: Update keys for all hosts
       summary: Update keys for all hosts
       tags:
       tags:
       - Hosts
       - Hosts
+  /api/hosts/sync:
+    post:
+      responses:
+        "200":
+          description: sync all hosts request received
+          schema:
+            type: string
+      security:
+      - oauth: []
+      summary: Requests all the hosts to pull
+      tags:
+      - Hosts
+  /api/hosts/upgrade:
+    post:
+      parameters:
+      - description: Force upgrade
+        in: query
+        name: force
+        type: boolean
+      responses:
+        "200":
+          description: upgrade all hosts request received
+          schema:
+            type: string
+      security:
+      - oauth: []
+      summary: Requests all the hosts to upgrade their version
+      tags:
+      - Hosts
   /api/networks:
   /api/networks:
     get:
     get:
       produces:
       produces:
@@ -2117,6 +2217,10 @@ paths:
         name: networkname
         name: networkname
         required: true
         required: true
         type: string
         type: string
+      - description: Force Delete
+        in: query
+        name: force
+        type: boolean
       produces:
       produces:
       - application/json
       - application/json
       responses:
       responses:
@@ -2280,6 +2384,30 @@ paths:
       summary: Update a network ACL (Access Control List)
       summary: Update a network ACL (Access Control List)
       tags:
       tags:
       - Networks
       - Networks
+  /api/networks/{networkname}/egress_routes:
+    get:
+      parameters:
+      - description: Network name
+        in: path
+        name: networkname
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.SuccessResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      security:
+      - oauth: []
+      summary: Get a network Egress routes
+      tags:
+      - Networks
   /api/nodes:
   /api/nodes:
     get:
     get:
       responses:
       responses:
@@ -2358,61 +2486,6 @@ paths:
       summary: Create an egress gateway
       summary: Create an egress gateway
       tags:
       tags:
       - Nodes
       - Nodes
-  /api/nodes/{network}/{nodeid}/createingress:
-    post:
-      responses:
-        "200":
-          description: OK
-          schema:
-            $ref: '#/definitions/models.ApiNode'
-        "500":
-          description: Internal Server Error
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-      security:
-      - oauth2: []
-      summary: Create an remote access gateway
-      tags:
-      - Nodes
-  /api/nodes/{network}/{nodeid}/createrelay:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Network ID
-        in: path
-        name: network
-        required: true
-        type: string
-      - description: Node ID
-        in: path
-        name: nodeid
-        required: true
-        type: string
-      - description: Relay request parameters
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/models.RelayRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: OK
-          schema:
-            $ref: '#/definitions/models.ApiNode'
-        "400":
-          description: Bad Request
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-        "500":
-          description: Internal Server Error
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-      summary: Create a relay
-      tags:
-      - PRO
   /api/nodes/{network}/{nodeid}/deletegateway:
   /api/nodes/{network}/{nodeid}/deletegateway:
     delete:
     delete:
       responses:
       responses:
@@ -2429,7 +2502,7 @@ paths:
       summary: Delete an egress gateway
       summary: Delete an egress gateway
       tags:
       tags:
       - Nodes
       - Nodes
-  /api/nodes/{network}/{nodeid}/deleteingress:
+  /api/nodes/{network}/{nodeid}/gateway:
     delete:
     delete:
       responses:
       responses:
         "200":
         "200":
@@ -2442,42 +2515,24 @@ paths:
             $ref: '#/definitions/models.ErrorResponse'
             $ref: '#/definitions/models.ErrorResponse'
       security:
       security:
       - oauth2: []
       - oauth2: []
-      summary: Delete an remote access gateway
+      summary: Delete a gateway
       tags:
       tags:
       - Nodes
       - Nodes
-  /api/nodes/{network}/{nodeid}/deleterelay:
-    delete:
-      consumes:
-      - application/json
-      parameters:
-      - description: Network ID
-        in: path
-        name: network
-        required: true
-        type: string
-      - description: Node ID
-        in: path
-        name: nodeid
-        required: true
-        type: string
-      produces:
-      - application/json
+    post:
       responses:
       responses:
         "200":
         "200":
           description: OK
           description: OK
           schema:
           schema:
             $ref: '#/definitions/models.ApiNode'
             $ref: '#/definitions/models.ApiNode'
-        "400":
-          description: Bad Request
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
         "500":
         "500":
           description: Internal Server Error
           description: Internal Server Error
           schema:
           schema:
             $ref: '#/definitions/models.ErrorResponse'
             $ref: '#/definitions/models.ErrorResponse'
-      summary: Remove a relay
+      security:
+      - oauth2: []
+      summary: Create a gateway
       tags:
       tags:
-      - PRO
+      - Nodes
   /api/nodes/{network}/{nodeid}/inet_gw:
   /api/nodes/{network}/{nodeid}/inet_gw:
     delete:
     delete:
       parameters:
       parameters:
@@ -3425,6 +3480,38 @@ paths:
       summary: Create failover node
       summary: Create failover node
       tags:
       tags:
       - PRO
       - PRO
+  /api/v1/node/{nodeid}/failover_check:
+    get:
+      consumes:
+      - application/json
+      parameters:
+      - description: Node ID
+        in: path
+        name: nodeid
+        required: true
+        type: string
+      - description: Failover request
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/models.FailOverMeReq'
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.SuccessResponse'
+        "400":
+          description: Bad Request
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: checkfailOverCtx
+      tags:
+      - PRO
   /api/v1/node/{nodeid}/failover_me:
   /api/v1/node/{nodeid}/failover_me:
     post:
     post:
       consumes:
       consumes:
@@ -3457,6 +3544,22 @@ paths:
       summary: Failover me
       summary: Failover me
       tags:
       tags:
       - PRO
       - PRO
+  /api/v1/nodes/{network}/status:
+    get:
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.ApiNode'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Get all nodes status on the network
+      tags:
+      - Nodes
   /api/v1/tags:
   /api/v1/tags:
     delete:
     delete:
       consumes:
       consumes: