NET-551: User Mgmt Re-Design (#2547)

* add superadmin role, apis to create superadmin user

* apis to attach and remove user from remote access gateways

* add api to list user's remote client has gateway clients

* remove code related user groups

* remove networks and groups from user model

* refactor user CRUD operations

* fix network permission test

* add superadmin to authorize func

* remove user network and groups from cli

* api to transfer superadmin role

* add api to list users on a ingress gw

* restrict user access to resources on server

* deny request from remote access client if extclient is already created

* fix user tests

* fix static checks

* fix static checks

* add limits to extclient create handler

* set username to superadmin on if masterkey is used

* allow creation of extclients using masterkey

* add migration func to assign superadmin role for existing admin user

* check for superadmin on migration if users are present

* allowe masterkey to extcleint apis

* check ownerid

* format error, on jwt token verification failure return unauthorized rather than forbidden

* user update fix

* move user remote functionality to ee

* fix update user api

* security patch

* initalise ee user handlers

* allow user to use master key to update any user

* use slog

* fix auth user test

* table headers

* remove user role, it's covered in middleware

* setuser defaults fix
This commit is contained in:
Abhishek K 2023-09-01 14:27:08 +05:30 committed by GitHub
parent 1a1ba1ccf4
commit 719e0c254d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 835 additions and 2703 deletions

View file

@ -10,6 +10,7 @@ import (
"time"
"golang.org/x/crypto/bcrypt"
"golang.org/x/exp/slog"
"golang.org/x/oauth2"
"github.com/gorilla/websocket"
@ -238,9 +239,9 @@ func HandleHeadlessSSO(w http.ResponseWriter, r *http.Request) {
// == private methods ==
func addUser(email string) error {
var hasAdmin, err = logic.HasAdmin()
var hasSuperAdmin, err = logic.HasSuperAdmin()
if err != nil {
logger.Log(1, "error checking for existence of admin user during OAuth login for", email, "; user not added")
slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err)
return err
} // generate random password to adapt to current model
var newPass, fetchErr = fetchPassValue("")
@ -251,11 +252,11 @@ func addUser(email string) error {
UserName: email,
Password: newPass,
}
if !hasAdmin { // must be first attempt, create an admin
if err = logic.CreateAdmin(&newUser); err != nil {
logger.Log(1, "error creating admin from user,", email, "; user not added")
if !hasSuperAdmin { // must be first attempt, create a superadmin
if err = logic.CreateSuperAdmin(&newUser); err != nil {
slog.Error("error creating super admin from user", "email", email, "error", err)
} else {
logger.Log(1, "admin created from user,", email, "; was first user added")
slog.Info("superadmin created from user", "email", email)
}
} else { // otherwise add to db as admin..?
// TODO: add ability to add users with preemptive permissions

View file

@ -9,10 +9,8 @@ import (
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/logic/pro/netcache"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
)
var (
@ -165,25 +163,5 @@ func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User
user, _ = logic.GetUser(username)
}
if !user.IsAdmin { // perform check to see if user is allowed to join a node to network
netUser, err := pro.GetNetworkUser(network, promodels.NetworkUserID(user.UserName))
if err != nil {
logger.Log(0, "failed to get net user details for user", user.UserName, "during node SSO")
return nil, fmt.Errorf("failed to verify network user")
}
if netUser.AccessLevel != pro.NET_ADMIN { // if user is a net admin on network, good to go
// otherwise, check if they have node access + haven't reached node limit on network
if netUser.AccessLevel == pro.NODE_ACCESS {
if len(netUser.Nodes) >= netUser.NodeLimit {
logger.Log(0, "user", user.UserName, "has reached their node limit on network", network)
return nil, fmt.Errorf("user node limit exceeded")
}
} else {
logger.Log(0, "user", user.UserName, "attempted to access network", network, "via node SSO")
return nil, fmt.Errorf("network user not allowed")
}
}
}
return user, nil
}

View file

@ -1,43 +0,0 @@
package network_user
import (
"fmt"
"strings"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models/promodels"
"github.com/spf13/cobra"
)
var networkuserCreateCmd = &cobra.Command{
Use: "create [NETWORK NAME]",
Args: cobra.ExactArgs(1),
Short: "Create a network user",
Long: `Create a network user`,
Run: func(cmd *cobra.Command, args []string) {
user := &promodels.NetworkUser{
AccessLevel: accessLevel,
ClientLimit: clientLimit,
NodeLimit: nodeLimit, ID: promodels.NetworkUserID(id),
}
if clients != "" {
user.Clients = strings.Split(clients, ",")
}
if nodes != "" {
user.Nodes = strings.Split(nodes, ",")
}
functions.CreateNetworkUser(args[0], user)
fmt.Println("Success")
},
}
func init() {
networkuserCreateCmd.Flags().IntVar(&accessLevel, "access_level", 0, "Custom access level")
networkuserCreateCmd.Flags().IntVar(&clientLimit, "client_limit", 0, "Maximum number of external clients that can be created")
networkuserCreateCmd.Flags().IntVar(&nodeLimit, "node_limit", 999999999, "Maximum number of nodes that can be attached to a network")
networkuserCreateCmd.Flags().StringVar(&clients, "clients", "", "Access to list of external clients (comma separated)")
networkuserCreateCmd.Flags().StringVar(&nodes, "nodes", "", "Access to list of nodes (comma separated)")
networkuserCreateCmd.Flags().StringVar(&id, "id", "", "ID of the network user")
networkuserCreateCmd.MarkFlagRequired("id")
rootCmd.AddCommand(networkuserCreateCmd)
}

View file

@ -1,23 +0,0 @@
package network_user
import (
"fmt"
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var networkuserDeleteCmd = &cobra.Command{
Use: "delete [NETWORK NAME] [NETWORK USER NAME]",
Args: cobra.ExactArgs(2),
Short: "Delete a network user",
Long: `Delete a network user`,
Run: func(cmd *cobra.Command, args []string) {
functions.DeleteNetworkUser(args[0], args[1])
fmt.Println("Success")
},
}
func init() {
rootCmd.AddCommand(networkuserDeleteCmd)
}

View file

@ -1,10 +0,0 @@
package network_user
var (
accessLevel int
clientLimit int
nodeLimit int
clients string
nodes string
id string
)

View file

@ -1,27 +0,0 @@
package network_user
import (
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var data bool
var networkuserGetCmd = &cobra.Command{
Use: "get [NETWORK NAME] [NETWORK USER NAME]",
Args: cobra.ExactArgs(2),
Short: "Fetch a network user",
Long: `Fetch a network user`,
Run: func(cmd *cobra.Command, args []string) {
if data {
functions.PrettyPrint(functions.GetNetworkUserData(args[1]))
} else {
functions.PrettyPrint(functions.GetNetworkUser(args[0], args[1]))
}
},
}
func init() {
networkuserGetCmd.Flags().BoolVar(&data, "data", false, "Fetch entire data of a network user")
rootCmd.AddCommand(networkuserGetCmd)
}

View file

@ -1,27 +0,0 @@
package network_user
import (
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var networkName string
var networkuserListCmd = &cobra.Command{
Use: "list",
Args: cobra.NoArgs,
Short: "List network users",
Long: `List network users`,
Run: func(cmd *cobra.Command, args []string) {
if networkName != "" {
functions.PrettyPrint(functions.GetNetworkUsers(networkName))
} else {
functions.PrettyPrint(functions.GetAllNetworkUsers())
}
},
}
func init() {
networkuserListCmd.Flags().StringVar(&networkName, "network", "", "Name of the network")
rootCmd.AddCommand(networkuserListCmd)
}

View file

@ -1,28 +0,0 @@
package network_user
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "network_user",
Short: "Manage Network Users",
Long: `Manage Network Users`,
}
// GetRoot returns the root subcommand
func GetRoot() *cobra.Command {
return rootCmd
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

View file

@ -1,43 +0,0 @@
package network_user
import (
"fmt"
"strings"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models/promodels"
"github.com/spf13/cobra"
)
var networkuserUpdateCmd = &cobra.Command{
Use: "update [NETWORK NAME]",
Args: cobra.ExactArgs(1),
Short: "Update a network user",
Long: `Update a network user`,
Run: func(cmd *cobra.Command, args []string) {
user := &promodels.NetworkUser{
AccessLevel: accessLevel,
ClientLimit: clientLimit,
NodeLimit: nodeLimit, ID: promodels.NetworkUserID(id),
}
if clients != "" {
user.Clients = strings.Split(clients, ",")
}
if nodes != "" {
user.Nodes = strings.Split(nodes, ",")
}
functions.UpdateNetworkUser(args[0], user)
fmt.Println("Success")
},
}
func init() {
networkuserUpdateCmd.Flags().IntVar(&accessLevel, "access_level", 0, "Custom access level")
networkuserUpdateCmd.Flags().IntVar(&clientLimit, "client_limit", 0, "Maximum number of external clients that can be created")
networkuserUpdateCmd.Flags().IntVar(&nodeLimit, "node_limit", 999999999, "Maximum number of nodes that can be attached to a network")
networkuserUpdateCmd.Flags().StringVar(&clients, "clients", "", "Access to list of external clients (comma separated)")
networkuserUpdateCmd.Flags().StringVar(&nodes, "nodes", "", "Access to list of nodes (comma separated)")
networkuserUpdateCmd.Flags().StringVar(&id, "id", "", "ID of the network user")
networkuserUpdateCmd.MarkFlagRequired("id")
rootCmd.AddCommand(networkuserUpdateCmd)
}

View file

@ -12,11 +12,9 @@ import (
"github.com/gravitl/netmaker/cli/cmd/host"
"github.com/gravitl/netmaker/cli/cmd/metrics"
"github.com/gravitl/netmaker/cli/cmd/network"
"github.com/gravitl/netmaker/cli/cmd/network_user"
"github.com/gravitl/netmaker/cli/cmd/node"
"github.com/gravitl/netmaker/cli/cmd/server"
"github.com/gravitl/netmaker/cli/cmd/user"
"github.com/gravitl/netmaker/cli/cmd/usergroup"
"github.com/spf13/cobra"
)
@ -52,9 +50,7 @@ func init() {
rootCmd.AddCommand(server.GetRoot())
rootCmd.AddCommand(ext_client.GetRoot())
rootCmd.AddCommand(user.GetRoot())
rootCmd.AddCommand(usergroup.GetRoot())
rootCmd.AddCommand(metrics.GetRoot())
rootCmd.AddCommand(network_user.GetRoot())
rootCmd.AddCommand(host.GetRoot())
rootCmd.AddCommand(enrollment_key.GetRoot())
}

View file

@ -1,8 +1,6 @@
package user
import (
"strings"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models"
"github.com/spf13/cobra"
@ -15,12 +13,6 @@ var userCreateCmd = &cobra.Command{
Long: `Create a new user`,
Run: func(cmd *cobra.Command, args []string) {
user := &models.User{UserName: username, Password: password, IsAdmin: admin}
if networks != "" {
user.Networks = strings.Split(networks, ",")
}
if groups != "" {
user.Groups = strings.Split(groups, ",")
}
functions.PrettyPrint(functions.CreateUser(user))
},
}

View file

@ -3,7 +3,6 @@ package user
import (
"os"
"strconv"
"strings"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
@ -23,9 +22,9 @@ var userListCmd = &cobra.Command{
functions.PrettyPrint(data)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Admin", "Networks", "Groups"})
table.SetHeader([]string{"Name", "SuperAdmin", "Admin"})
for _, d := range *data {
table.Append([]string{d.UserName, strconv.FormatBool(d.IsAdmin), strings.Join(d.Networks, ", "), strings.Join(d.Groups, ", ")})
table.Append([]string{d.UserName, strconv.FormatBool(d.IsSuperAdmin), strconv.FormatBool(d.IsAdmin)})
}
table.Render()
}

View file

@ -1,8 +1,6 @@
package user
import (
"strings"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models"
"github.com/spf13/cobra"
@ -15,14 +13,6 @@ var userUpdateCmd = &cobra.Command{
Long: `Update a user`,
Run: func(cmd *cobra.Command, args []string) {
user := &models.User{UserName: args[0], IsAdmin: admin}
if networks != "" {
user.Networks = strings.Split(networks, ",")
}
if groups != "" {
user.Groups = strings.Split(groups, ",")
} else {
user.Groups = []string{"*"}
}
functions.PrettyPrint(functions.UpdateUser(user))
},
}

View file

@ -1,23 +0,0 @@
package usergroup
import (
"fmt"
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var usergroupCreateCmd = &cobra.Command{
Use: "create [GROUP NAME]",
Args: cobra.ExactArgs(1),
Short: "Create a usergroup",
Long: `Create a usergroup`,
Run: func(cmd *cobra.Command, args []string) {
functions.CreateUsergroup(args[0])
fmt.Println("Success")
},
}
func init() {
rootCmd.AddCommand(usergroupCreateCmd)
}

View file

@ -1,23 +0,0 @@
package usergroup
import (
"fmt"
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var usergroupDeleteCmd = &cobra.Command{
Use: "delete [GROUP NAME]",
Args: cobra.ExactArgs(1),
Short: "Delete a usergroup",
Long: `Delete a usergroup`,
Run: func(cmd *cobra.Command, args []string) {
functions.DeleteUsergroup(args[0])
fmt.Println("Success")
},
}
func init() {
rootCmd.AddCommand(usergroupDeleteCmd)
}

View file

@ -1,20 +0,0 @@
package usergroup
import (
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var usergroupGetCmd = &cobra.Command{
Use: "get",
Args: cobra.NoArgs,
Short: "Fetch all usergroups",
Long: `Fetch all usergroups`,
Run: func(cmd *cobra.Command, args []string) {
functions.PrettyPrint(functions.GetUsergroups())
},
}
func init() {
rootCmd.AddCommand(usergroupGetCmd)
}

View file

@ -1,28 +0,0 @@
package usergroup
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "usergroup",
Short: "Manage User Groups",
Long: `Manage User Groups`,
}
// GetRoot returns the root subcommand
func GetRoot() *cobra.Command {
return rootCmd
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

View file

@ -1,44 +0,0 @@
package functions
import (
"fmt"
"net/http"
"github.com/gravitl/netmaker/models/promodels"
proControllers "github.com/gravitl/netmaker/pro/controllers"
)
// GetAllNetworkUsers - fetch all network users
func GetAllNetworkUsers() *map[string][]promodels.NetworkUser {
return request[map[string][]promodels.NetworkUser](http.MethodGet, "/api/networkusers", nil)
}
// GetNetworkUsers - fetch network users belonging to a particular network
func GetNetworkUsers(networkName string) *promodels.NetworkUserMap {
return request[promodels.NetworkUserMap](http.MethodGet, "/api/networkusers/"+networkName, nil)
}
// GetNetworkUser - fetch a single network user
func GetNetworkUser(networkName, networkUserName string) *promodels.NetworkUser {
return request[promodels.NetworkUser](http.MethodGet, fmt.Sprintf("/api/networkusers/%s/%s", networkName, networkUserName), nil)
}
// CreateNetworkUser - create a network user
func CreateNetworkUser(networkName string, payload *promodels.NetworkUser) {
request[any](http.MethodPost, "/api/networkusers/"+networkName, payload)
}
// UpdateNetworkUser - update a network user
func UpdateNetworkUser(networkName string, payload *promodels.NetworkUser) {
request[any](http.MethodPut, "/api/networkusers/"+networkName, payload)
}
// GetNetworkUserData - fetch a network user's complete data
func GetNetworkUserData(networkUserName string) *proControllers.NetworkUserDataMap {
return request[proControllers.NetworkUserDataMap](http.MethodGet, fmt.Sprintf("/api/networkusers/data/%s/me", networkUserName), nil)
}
// DeleteNetworkUser - delete a network user
func DeleteNetworkUser(networkName, networkUserName string) {
request[any](http.MethodDelete, fmt.Sprintf("/api/networkusers/%s/%s", networkName, networkUserName), nil)
}

View file

@ -1,22 +0,0 @@
package functions
import (
"net/http"
"github.com/gravitl/netmaker/models/promodels"
)
// GetUsergroups - fetch all usergroups
func GetUsergroups() *promodels.UserGroups {
return request[promodels.UserGroups](http.MethodGet, "/api/usergroups", nil)
}
// CreateUsergroup - create a usergroup
func CreateUsergroup(usergroupName string) {
request[any](http.MethodPost, "/api/usergroups/"+usergroupName, nil)
}
// DeleteUsergroup - delete a usergroup
func DeleteUsergroup(usergroupName string) {
request[any](http.MethodDelete, "/api/usergroups/"+usergroupName, nil)
}

View file

@ -17,12 +17,12 @@ import (
func dnsHandlers(r *mux.Router) {
r.HandleFunc("/api/dns", logic.SecurityCheck(true, http.HandlerFunc(getAllDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/adm/{network}/nodes", logic.SecurityCheck(false, http.HandlerFunc(getNodeDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/adm/{network}/custom", logic.SecurityCheck(false, http.HandlerFunc(getCustomDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(false, http.HandlerFunc(getDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(false, http.HandlerFunc(createDNS))).Methods(http.MethodPost)
r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(false, http.HandlerFunc(pushDNS))).Methods(http.MethodPost)
r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(false, http.HandlerFunc(deleteDNS))).Methods(http.MethodDelete)
r.HandleFunc("/api/dns/adm/{network}/nodes", logic.SecurityCheck(true, http.HandlerFunc(getNodeDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/adm/{network}/custom", logic.SecurityCheck(true, http.HandlerFunc(getCustomDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(true, http.HandlerFunc(getDNS))).Methods(http.MethodGet)
r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(true, http.HandlerFunc(createDNS))).Methods(http.MethodPost)
r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(true, http.HandlerFunc(pushDNS))).Methods(http.MethodPost)
r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(true, http.HandlerFunc(deleteDNS))).Methods(http.MethodDelete)
}
// swagger:route GET /api/dns/adm/{network}/nodes dns getNodeDNS

View file

@ -17,7 +17,7 @@ import (
func enrollmentKeyHandlers(r *mux.Router) {
r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodPost)
r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(false, http.HandlerFunc(getEnrollmentKeys))).Methods(http.MethodGet)
r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).Methods(http.MethodGet)
r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).Methods(http.MethodDelete)
r.HandleFunc("/api/v1/host/register/{token}", http.HandlerFunc(handleHostRegister)).Methods(http.MethodPost)
}
@ -40,20 +40,10 @@ func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
isMasterAdmin := r.Header.Get("ismaster") == "yes"
// regular user flow
user, err := logic.GetUser(r.Header.Get("user"))
if err != nil && !isMasterAdmin {
logger.Log(0, r.Header.Get("user"), "failed to fetch user: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
// TODO drop double pointer
ret := []*models.EnrollmentKey{}
for _, key := range keys {
if !isMasterAdmin && !logic.UserHasNetworksAccess(key.Networks, user) {
continue
}
key := key
if err = logic.Tokenize(key, servercfg.GetAPIHost()); err != nil {
logger.Log(0, r.Header.Get("user"), "failed to get token values for keys:", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

View file

@ -12,9 +12,9 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
"github.com/gravitl/netmaker/mq"
"github.com/skip2/go-qrcode"
"golang.org/x/exp/slog"
@ -23,13 +23,13 @@ import (
func extClientHandlers(r *mux.Router) {
r.HandleFunc("/api/extclients", logic.SecurityCheck(false, http.HandlerFunc(getAllExtClients))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}", logic.SecurityCheck(false, http.HandlerFunc(getNetworkExtClients))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients", logic.SecurityCheck(true, http.HandlerFunc(getAllExtClients))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkExtClients))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.SecurityCheck(false, http.HandlerFunc(getExtClient))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods(http.MethodPut)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods(http.MethodDelete)
r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.NetUserSecurityCheck(false, true, checkFreeTierLimits(limitChoiceMachines, http.HandlerFunc(createExtClient)))).Methods(http.MethodPost)
r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", logic.SecurityCheck(false, http.HandlerFunc(getExtClientConf))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.SecurityCheck(false, http.HandlerFunc(updateExtClient))).Methods(http.MethodPut)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.SecurityCheck(false, http.HandlerFunc(deleteExtClient))).Methods(http.MethodDelete)
r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.SecurityCheck(false, checkFreeTierLimits(limitChoiceMachines, http.HandlerFunc(createExtClient)))).Methods(http.MethodPost)
}
func checkIngressExists(nodeID string) bool {
@ -94,29 +94,18 @@ func getAllExtClients(w http.ResponseWriter, r *http.Request) {
networksSlice := []string{}
marshalErr := json.Unmarshal([]byte(headerNetworks), &networksSlice)
if marshalErr != nil {
logger.Log(0, "error unmarshalling networks: ",
marshalErr.Error())
slog.Error("error unmarshalling networks", "error", marshalErr.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(marshalErr, "internal"))
return
}
clients := []models.ExtClient{}
var err error
if len(networksSlice) > 0 && networksSlice[0] == logic.ALL_NETWORK_ACCESS {
clients, err = logic.GetAllExtClients()
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, "failed to get all extclients: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
} else {
for _, network := range networksSlice {
extclients, err := logic.GetNetworkExtClients(network)
if err == nil {
clients = append(clients, extclients...)
}
}
}
var err error
clients, err := logic.GetAllExtClients()
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, "failed to get all extclients: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
//Return all the extclients in JSON format
logic.SortExtClient(clients[:])
w.WriteHeader(http.StatusOK)
@ -149,6 +138,14 @@ func getExtClient(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), client) {
// check if user has access to extclient
slog.Error("failed to get extclient", "network", network, "clientID",
clientid, "error", errors.New("access is denied"))
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden"))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(client)
@ -179,6 +176,12 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), client) {
slog.Error("failed to get extclient", "network", networkid, "clientID",
clientid, "error", errors.New("access is denied"))
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden"))
return
}
gwnode, err := logic.GetNodeByID(client.IngressGatewayID)
if err != nil {
@ -323,7 +326,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
var customExtClient models.CustomExtClient
if err := json.NewDecoder(r.Body).Decode(&customExtClient); err != nil {
@ -334,9 +336,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
extclient := logic.UpdateExtClient(&models.ExtClient{}, &customExtClient)
extclient.IngressGatewayID = nodeid
node, err := logic.GetNodeByID(nodeid)
if err != nil {
logger.Log(0, r.Header.Get("user"),
@ -344,6 +343,48 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
var userName string
if r.Header.Get("ismaster") == "yes" {
userName = logic.MasterUser
} else {
caller, err := logic.GetUser(r.Header.Get("user"))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
userName = caller.UserName
if !caller.IsAdmin && !caller.IsSuperAdmin {
if _, ok := caller.RemoteGwIDs[nodeid]; !ok {
err = errors.New("permission denied")
slog.Error("failed to create extclient", "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
return
}
// check if user has a config already for remote access client
extclients, err := logic.GetNetworkExtClients(node.Network)
if err != nil {
slog.Error("failed to get extclients", "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, extclient := range extclients {
if extclient.RemoteAccessClientID != "" &&
extclient.RemoteAccessClientID == customExtClient.RemoteAccessClientID && nodeid == extclient.IngressGatewayID {
// extclient on the gw already exists for the remote access client
err = errors.New("remote client config already exists on the gateway")
slog.Error("failed to get extclients", "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
}
}
}
extclient := logic.UpdateExtClient(&models.ExtClient{}, &customExtClient)
extclient.OwnerID = userName
extclient.RemoteAccessClientID = customExtClient.RemoteAccessClientID
extclient.IngressGatewayID = nodeid
extclient.Network = node.Network
host, err := logic.GetHost(node.HostID.String())
if err != nil {
@ -372,26 +413,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
return
}
var isAdmin bool
if r.Header.Get("ismaster") != "yes" {
userID := r.Header.Get("user")
if isAdmin, err = checkProClientAccess(userID, extclient.ClientID, &parentNetwork); err != nil {
slog.Error("pro client access check failed", "user", userID, "network", node.Network, "error", err)
logic.DeleteExtClient(node.Network, extclient.ClientID)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if !isAdmin {
if err = pro.AssociateNetworkUserClient(userID, node.Network, extclient.ClientID); err != nil {
logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID)
}
extclient.OwnerID = userID
if err := logic.SaveExtClient(&extclient); err != nil {
logger.Log(0, "failed to add owner id", userID, "to client", extclient.ClientID)
}
}
}
slog.Info("created extclient", "user", r.Header.Get("user"), "network", node.Network, "clientid", extclient.ClientID)
w.WriteHeader(http.StatusOK)
go func() {
@ -431,12 +452,21 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
return
}
clientid := params["clientid"]
network := params["network"]
oldExtClient, err := logic.GetExtClientByName(clientid)
if err != nil {
slog.Error("failed to retrieve extclient", "user", r.Header.Get("user"), "id", clientid, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), oldExtClient) {
// check if user has access to extclient
slog.Error("failed to get extclient", "network", network, "clientID",
clientid, "error", errors.New("access is denied"))
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden"))
return
}
if oldExtClient.ClientID == update.ClientID {
if err := validateCustomExtClient(&update, false); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
@ -448,31 +478,12 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
return
}
}
// == PRO ==
//networkName := params["network"]
var changedID = update.ClientID != oldExtClient.ClientID
if r.Header.Get("ismaster") != "yes" {
userID := r.Header.Get("user")
_, doesOwn := doesUserOwnClient(userID, params["clientid"], oldExtClient.Network)
if !doesOwn {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "internal"))
return
}
}
if changedID && oldExtClient.OwnerID != "" {
if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, oldExtClient.Network, oldExtClient.ClientID); err != nil {
logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
}
if err := pro.AssociateNetworkUserClient(oldExtClient.OwnerID, oldExtClient.Network, update.ClientID); err != nil {
logger.Log(0, "failed to associate client", update.ClientID, "to user", oldExtClient.OwnerID)
}
}
if len(update.DeniedACLs) != len(oldExtClient.DeniedACLs) {
sendPeerUpdate = true
logic.SetClientACLs(&oldExtClient, update.DeniedACLs)
}
// == END PRO ==
if update.Enabled != oldExtClient.Enabled {
sendPeerUpdate = true
@ -535,6 +546,12 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), extclient) {
slog.Error("failed to get extclient", "network", network, "clientID",
clientid, "error", errors.New("access is denied"))
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden"))
return
}
ingressnode, err := logic.GetNodeByID(extclient.IngressGatewayID)
if err != nil {
logger.Log(0, r.Header.Get("user"),
@ -543,24 +560,6 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
return
}
// == PRO ==
if r.Header.Get("ismaster") != "yes" {
userID, clientID, networkName := r.Header.Get("user"), params["clientid"], params["network"]
_, doesOwn := doesUserOwnClient(userID, clientID, networkName)
if !doesOwn {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "internal"))
return
}
}
if extclient.OwnerID != "" {
if err = pro.DissociateNetworkUserClient(extclient.OwnerID, extclient.Network, extclient.ClientID); err != nil {
logger.Log(0, "failed to dissociate client", extclient.ClientID, "from user", extclient.OwnerID)
}
}
// == END PRO ==
err = logic.DeleteExtClient(params["network"], params["clientid"])
if err != nil {
logger.Log(0, r.Header.Get("user"),
@ -584,63 +583,6 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
logic.ReturnSuccessResponse(w, r, params["clientid"]+" deleted.")
}
func checkProClientAccess(username, clientID string, network *models.Network) (bool, error) {
u, err := logic.GetUser(username)
if err != nil {
return false, err
}
if u.IsAdmin {
return true, nil
}
netUser, err := pro.GetNetworkUser(network.NetID, promodels.NetworkUserID(u.UserName))
if err != nil {
return false, err
}
if netUser.AccessLevel == pro.NET_ADMIN {
return false, nil
}
if netUser.AccessLevel == pro.NO_ACCESS {
return false, fmt.Errorf("user does not have access")
}
if !(len(netUser.Clients) < netUser.ClientLimit) {
return false, fmt.Errorf("user can not create more clients")
}
if netUser.AccessLevel < pro.NO_ACCESS {
netUser.Clients = append(netUser.Clients, clientID)
if err = pro.UpdateNetworkUser(network.NetID, netUser); err != nil {
return false, err
}
}
return false, nil
}
// checks if net user owns an ext client or is an admin
func doesUserOwnClient(username, clientID, network string) (bool, bool) {
u, err := logic.GetUser(username)
if err != nil {
return false, false
}
if u.IsAdmin {
return true, true
}
netUser, err := pro.GetNetworkUser(network, promodels.NetworkUserID(u.UserName))
if err != nil {
return false, false
}
if netUser.AccessLevel == pro.NET_ADMIN {
return false, true
}
return false, logic.StringSliceContains(netUser.Clients, clientID)
}
// validateCustomExtClient Validates the extclient object
func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bool) error {
//validate clientid

View file

@ -17,7 +17,7 @@ import (
)
func hostHandlers(r *mux.Router) {
r.HandleFunc("/api/hosts", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).Methods(http.MethodPut)
r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).Methods(http.MethodPut)
r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))).Methods(http.MethodPost)

View file

@ -20,9 +20,9 @@ import (
)
func networkHandlers(r *mux.Router) {
r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
r.HandleFunc("/api/networks", logic.SecurityCheck(true, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceNetworks, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
// ACLs
@ -42,29 +42,14 @@ func networkHandlers(r *mux.Router) {
// Responses:
// 200: getNetworksSliceResponse
func getNetworks(w http.ResponseWriter, r *http.Request) {
networksSlice, marshalErr := getHeaderNetworks(r)
if marshalErr != nil {
logger.Log(0, r.Header.Get("user"), "error unmarshalling networks: ",
marshalErr.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(marshalErr, "badrequest"))
return
}
allnetworks := []models.Network{}
var err error
if len(networksSlice) > 0 && networksSlice[0] == logic.ALL_NETWORK_ACCESS {
allnetworks, err = logic.GetNetworks()
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, r.Header.Get("user"), "failed to fetch networks: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
} else {
for _, network := range networksSlice {
netObject, parentErr := logic.GetParentNetwork(network)
if parentErr == nil {
allnetworks = append(allnetworks, netObject)
}
}
allnetworks, err := logic.GetNetworks()
if err != nil && !database.IsEmptyRecord(err) {
slog.Error("failed to fetch networks", "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
logger.Log(2, r.Header.Get("user"), "fetched networks.")
@ -326,8 +311,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
}
// partial update
netOld2 := netOld1
netOld2.ProSettings = payload.ProSettings
_, _, _, _, _, err = logic.UpdateNetwork(&netOld1, &netOld2)
_, _, _, err = logic.UpdateNetwork(&netOld1, &netOld2)
if err != nil {
slog.Info("failed to update network", "user", r.Header.Get("user"), "err", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))

View file

@ -25,12 +25,10 @@ var netHost models.Host
func TestMain(m *testing.M) {
database.InitializeDatabase()
defer database.CloseDB()
logic.CreateAdmin(&models.User{
logic.CreateSuperAdmin(&models.User{
UserName: "admin",
Password: "password",
IsAdmin: true,
Networks: []string{},
Groups: []string{},
})
peerUpdate := make(chan *models.Node)
go logic.ManageZombies(context.Background(), peerUpdate)
@ -91,27 +89,16 @@ func TestSecurityCheck(t *testing.T) {
os.Setenv("MASTER_KEY", "secretkey")
t.Run("NoNetwork", func(t *testing.T) {
networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey")
username, err := logic.UserPermissions(false, "Bearer secretkey")
assert.Nil(t, err)
t.Log(networks, username)
})
t.Run("WithNetwork", func(t *testing.T) {
networks, username, err := logic.UserPermissions(false, "skynet", "Bearer secretkey")
assert.Nil(t, err)
t.Log(networks, username)
})
t.Run("BadNet", func(t *testing.T) {
t.Skip()
networks, username, err := logic.UserPermissions(false, "badnet", "Bearer secretkey")
assert.NotNil(t, err)
t.Log(err)
t.Log(networks, username)
t.Log(username)
})
t.Run("BadToken", func(t *testing.T) {
networks, username, err := logic.UserPermissions(false, "skynet", "Bearer badkey")
username, err := logic.UserPermissions(false, "Bearer badkey")
assert.NotNil(t, err)
t.Log(err)
t.Log(networks, username)
t.Log(username)
})
}

View file

@ -10,9 +10,7 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
"github.com/gravitl/netmaker/mq"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/crypto/bcrypt"
@ -26,13 +24,12 @@ func nodeHandlers(r *mux.Router) {
r.HandleFunc("/api/nodes", Authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet)
r.HandleFunc("/api/nodes/{network}", Authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
r.HandleFunc("/api/nodes/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(updateNode))).Methods(http.MethodPut)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", Authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
}
@ -198,23 +195,18 @@ func Authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Han
var isAuthorized = false
var nodeID = ""
username, networks, isadmin, errN := logic.VerifyUserToken(authToken)
username, issuperadmin, isadmin, errN := logic.VerifyUserToken(authToken)
if errN != nil {
logic.ReturnErrorResponse(w, r, errorResponse)
logic.ReturnErrorResponse(w, r, logic.FormatError(errN, logic.Unauthorized_Msg))
return
}
isnetadmin := isadmin
if errN == nil && isadmin {
isnetadmin := issuperadmin || isadmin
if errN == nil && (issuperadmin || isadmin) {
nodeID = "mastermac"
isAuthorized = true
r.Header.Set("ismasterkey", "yes")
}
if !isadmin && params["network"] != "" {
if logic.StringSliceContains(networks, params["network"]) && pro.IsUserNetAdmin(params["network"], username) {
isnetadmin = true
}
}
//The mastermac (login with masterkey from config) can do everything!! May be dangerous.
if nodeID == "mastermac" {
isAuthorized = true
@ -326,14 +318,6 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
} else {
nodes, err = getUsersNodes(*user)
if err != nil {
logger.Log(0, r.Header.Get("user"),
"error fetching nodes: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
}
// return all the nodes in JSON/API format
apiNodes := logic.GetAllNodesAPI(nodes[:])
@ -343,19 +327,6 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(apiNodes)
}
func getUsersNodes(user models.User) ([]models.Node, error) {
var nodes []models.Node
var err error
for _, networkName := range user.Networks {
tmpNodes, err := logic.GetNetworkNodes(networkName)
if err != nil {
continue
}
nodes = append(nodes, tmpNodes...)
}
return nodes, err
}
// swagger:route GET /api/nodes/{network}/{nodeid} nodes getNode
//
// Get an individual node.
@ -727,13 +698,6 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
}
forceDelete := r.URL.Query().Get("force") == "true"
fromNode := r.Header.Get("requestfrom") == "node"
if r.Header.Get("ismaster") != "yes" {
username := r.Header.Get("user")
if username != "" && !doesUserOwnNode(username, params["network"], nodeid) {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "badrequest"))
return
}
}
if node.IsRelayed {
// cleanup node from relayednodes on relay node
relayNode, err := logic.GetNodeByID(node.RelayedBy)
@ -780,27 +744,6 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
}()
}
func doesUserOwnNode(username, network, nodeID string) bool {
u, err := logic.GetUser(username)
if err != nil {
return false
}
if u.IsAdmin {
return true
}
netUser, err := pro.GetNetworkUser(network, promodels.NetworkUserID(u.UserName))
if err != nil {
return false
}
if netUser.AccessLevel == pro.NET_ADMIN {
return true
}
return logic.StringSliceContains(netUser.Nodes, nodeID)
}
func validateParams(nodeid, netid string) (models.Node, error) {
node, err := logic.GetNodeByID(nodeid)
if err != nil {

View file

@ -13,23 +13,19 @@ import (
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/exp/slog"
)
var (
upgrader = websocket.Upgrader{}
)
// verifyJWT makes logic.VerifyJWT fakeable/mockable in tests
var verifyJWT = logic.VerifyJWT
func userHandlers(r *mux.Router) {
r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet)
r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods(http.MethodPost)
r.HandleFunc("/api/users/adm/hassuperadmin", hasSuperAdmin).Methods(http.MethodGet)
r.HandleFunc("/api/users/adm/createsuperadmin", createSuperAdmin).Methods(http.MethodPost)
r.HandleFunc("/api/users/adm/transfersuperadmin/{username}", logic.SecurityCheck(true, http.HandlerFunc(transferSuperAdmin))).Methods(http.MethodPost)
r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods(http.MethodPost)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(updateUser)))).Methods(http.MethodPut)
r.HandleFunc("/api/users/networks/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods(http.MethodPut)
r.HandleFunc("/api/users/{username}/adm", logic.SecurityCheck(true, http.HandlerFunc(updateUserAdm))).Methods(http.MethodPut)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUser))).Methods(http.MethodPut)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceUsers, http.HandlerFunc(createUser)))).Methods(http.MethodPost)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet)
@ -112,7 +108,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
response.Write(successJSONResponse)
}
// swagger:route GET /api/users/adm/hasadmin user hasAdmin
// swagger:route GET /api/users/adm/hassuperadmin user hasSuperAdmin
//
// Checks whether the server has an admin.
//
@ -123,18 +119,18 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
//
// Responses:
// 200: successResponse
func hasAdmin(w http.ResponseWriter, r *http.Request) {
func hasSuperAdmin(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
hasadmin, err := logic.HasAdmin()
hasSuperAdmin, err := logic.HasSuperAdmin()
if err != nil {
logger.Log(0, "failed to check for admin: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
json.NewEncoder(w).Encode(hasadmin)
json.NewEncoder(w).Encode(hasSuperAdmin)
}
@ -194,7 +190,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(users)
}
// swagger:route POST /api/users/adm/createadmin user createAdmin
// swagger:route POST /api/users/adm/createsuperadmin user createAdmin
//
// Make a user an admin.
//
@ -205,16 +201,14 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
//
// Responses:
// 200: userBodyResponse
func createAdmin(w http.ResponseWriter, r *http.Request) {
func createSuperAdmin(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var admin models.User
var u models.User
err := json.NewDecoder(r.Body).Decode(&admin)
err := json.NewDecoder(r.Body).Decode(&u)
if err != nil {
logger.Log(0, admin.UserName, "error decoding request body: ",
err.Error())
slog.Error("error decoding request body", "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
@ -224,16 +218,72 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
return
}
err = logic.CreateAdmin(&admin)
err = logic.CreateSuperAdmin(&u)
if err != nil {
logger.Log(0, admin.UserName, "failed to create admin: ",
err.Error())
slog.Error("failed to create admin", "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
logger.Log(1, u.UserName, "was made a super admin")
json.NewEncoder(w).Encode(logic.ToReturnUser(u))
}
logger.Log(1, admin.UserName, "was made a new admin")
json.NewEncoder(w).Encode(logic.ToReturnUser(admin))
// swagger:route POST /api/users/adm/transfersuperadmin user transferSuperAdmin
//
// Transfers superadmin role to an admin user.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func transferSuperAdmin(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
caller, err := logic.GetUser(r.Header.Get("user"))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
}
if !caller.IsSuperAdmin {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only superadmin can assign the superadmin role to another user"), "forbidden"))
return
}
var params = mux.Vars(r)
username := params["username"]
u, err := logic.GetUser(username)
if err != nil {
slog.Error("error getting user", "user", u.UserName, "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
if !u.IsAdmin {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only admins can be promoted to superadmin role"), "forbidden"))
return
}
if !servercfg.IsBasicAuthEnabled() {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("basic auth is disabled"), "badrequest"))
return
}
u.IsSuperAdmin = true
u.IsAdmin = false
err = logic.UpsertUser(*u)
if err != nil {
slog.Error("error updating user to superadmin: ", "user", u.UserName, "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
caller.IsSuperAdmin = false
caller.IsAdmin = true
err = logic.UpsertUser(*caller)
if err != nil {
slog.Error("error demoting user to admin: ", "user", caller.UserName, "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
slog.Info("user was made a super admin", "user", u.UserName)
json.NewEncoder(w).Encode(logic.ToReturnUser(*u))
}
// swagger:route POST /api/users/{username} user createUser
@ -249,83 +299,41 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
// 200: userBodyResponse
func createUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
caller, err := logic.GetUser(r.Header.Get("user"))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
}
var user models.User
err := json.NewDecoder(r.Body).Decode(&user)
err = json.NewDecoder(r.Body).Decode(&user)
if err != nil {
logger.Log(0, user.UserName, "error decoding request body: ",
err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
if !caller.IsSuperAdmin && user.IsAdmin {
err = errors.New("only superadmin can create admin users")
slog.Error("error creating new user: ", "user", user.UserName, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
return
}
if user.IsSuperAdmin {
err = errors.New("additional superadmins cannot be created")
slog.Error("error creating new user: ", "user", user.UserName, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
return
}
err = logic.CreateUser(&user)
if err != nil {
logger.Log(0, user.UserName, "error creating new user: ",
err.Error())
slog.Error("error creating new user: ", "user", user.UserName, "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
logger.Log(1, user.UserName, "was created")
slog.Info("user was created", "username", user.UserName)
json.NewEncoder(w).Encode(logic.ToReturnUser(user))
}
// swagger:route PUT /api/users/networks/{username} user updateUserNetworks
//
// Updates the networks of the given user.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
// start here
username := params["username"]
user, err := logic.GetUser(username)
if err != nil {
logger.Log(0, username,
"failed to update user networks: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
userChange := &models.User{}
// we decode our body request params
err = json.NewDecoder(r.Body).Decode(userChange)
if err != nil {
logger.Log(0, username, "error decoding request body: ",
err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
err = logic.UpdateUserNetworks(userChange.Networks, userChange.Groups, userChange.IsAdmin, &models.ReturnUser{
Groups: user.Groups,
IsAdmin: user.IsAdmin,
Networks: user.Networks,
UserName: user.UserName,
})
if err != nil {
logger.Log(0, username,
"failed to update user networks: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
logger.Log(1, username, "status was updated")
// re-read and return the new user struct
returnUser, err := logic.GetReturnUser(username)
if err != nil {
logger.Log(0, username, "failed to fetch user: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
json.NewEncoder(w).Encode(returnUser)
}
// swagger:route PUT /api/users/{username} user updateUser
//
// Update a user.
@ -341,18 +349,19 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
// start here
jwtUser, _, isadmin, err := verifyJWT(r.Header.Get("Authorization"))
if err != nil {
logger.Log(0, "verifyJWT error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
var caller *models.User
var err error
var ismaster bool
if r.Header.Get("user") == logic.MasterUser {
ismaster = true
} else {
caller, err = logic.GetUser(r.Header.Get("user"))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
}
}
username := params["username"]
if username != jwtUser && !isadmin {
logger.Log(0, "non-admin user", jwtUser, "attempted to update user", username)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("not authorizied"), "unauthorized"))
return
}
user, err := logic.GetUser(username)
if err != nil {
logger.Log(0, username,
@ -360,27 +369,70 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
var userchange models.User
// we decode our body request params
err = json.NewDecoder(r.Body).Decode(&userchange)
if err != nil {
slog.Error("failed to decode body", "error ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
if user.UserName != userchange.UserName {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user in param and request body not matching"), "badrequest"))
return
}
selfUpdate := false
if !ismaster && caller.UserName == user.UserName {
selfUpdate = true
}
if !ismaster && !selfUpdate {
if caller.IsAdmin && user.IsSuperAdmin {
slog.Error("non-superadmin user", "caller", caller.UserName, "attempted to update superadmin user", username)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot update superadmin user"), "forbidden"))
return
}
if !caller.IsAdmin && !caller.IsSuperAdmin {
slog.Error("operation not allowed", "caller", caller.UserName, "attempted to update user", username)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot update superadmin user"), "forbidden"))
return
}
if caller.IsAdmin && user.IsAdmin {
slog.Error("admin user cannot update another admin", "caller", caller.UserName, "attempted to update admin user", username)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("admin user cannot update another admin"), "forbidden"))
return
}
if caller.IsAdmin && userchange.IsAdmin {
err = errors.New("admin user cannot update role of an another user to admin")
slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
return
}
}
if !ismaster && selfUpdate {
if user.IsAdmin != userchange.IsAdmin || user.IsSuperAdmin != userchange.IsSuperAdmin {
slog.Error("user cannot change his own role", "caller", caller.UserName, "attempted to update user role", username)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user not allowed to self assign role"), "forbidden"))
return
}
}
if ismaster {
if !user.IsSuperAdmin && userchange.IsSuperAdmin {
slog.Error("operation not allowed", "caller", logic.MasterUser, "attempted to update user role to superadmin", username)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("attempted to update user role to superadmin"), "forbidden"))
return
}
}
if auth.IsOauthUser(user) == nil {
err := fmt.Errorf("cannot update user info for oauth user %s", username)
logger.Log(0, err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
return
}
var userchange models.User
// we decode our body request params
err = json.NewDecoder(r.Body).Decode(&userchange)
if err != nil {
logger.Log(0, username, "error decoding request body: ",
err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
if userchange.IsAdmin && !isadmin {
logger.Log(0, "non-admin user", jwtUser, "attempted get admin privilages")
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("not authorizied"), "unauthorized"))
return
}
userchange.Networks = nil
user, err = logic.UpdateUser(&userchange, user)
if err != nil {
logger.Log(0, username,
@ -392,57 +444,6 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
// swagger:route PUT /api/users/{username}/adm user updateUserAdm
//
// Updates the given admin user's info (as long as the user is an admin).
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func updateUserAdm(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
// start here
username := params["username"]
user, err := logic.GetUser(username)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if auth.IsOauthUser(user) == nil {
err := fmt.Errorf("cannot update user info for oauth user %s", username)
logger.Log(0, err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
return
}
var userchange models.User
// we decode our body request params
err = json.NewDecoder(r.Body).Decode(&userchange)
if err != nil {
logger.Log(0, username, "error decoding request body: ",
err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
if !user.IsAdmin {
logger.Log(0, username, "not an admin user")
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("not a admin user"), "badrequest"))
}
user, err = logic.UpdateUser(&userchange, user)
if err != nil {
logger.Log(0, username,
"failed to update user (admin) info: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
logger.Log(1, username, "was updated (admin)")
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
// swagger:route DELETE /api/users/{username} user deleteUser
//
// Delete a user.
@ -460,9 +461,32 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
// get params
var params = mux.Vars(r)
caller, err := logic.GetUser(r.Header.Get("user"))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
}
username := params["username"]
user, err := logic.GetUser(username)
if err != nil {
logger.Log(0, username,
"failed to update user info: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if user.IsSuperAdmin {
slog.Error(
"failed to delete user: ", "user", username, "error", "superadmin cannot be deleted")
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if !caller.IsSuperAdmin {
if caller.IsAdmin && user.IsAdmin {
slog.Error(
"failed to delete user: ", "user", username, "error", "admin cannot delete another admin user")
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
}
success, err := logic.DeleteUser(username)
if err != nil {
logger.Log(0, username,
@ -495,14 +519,3 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
// Start handling the session
go auth.SessionHandler(conn)
}
// getHeaderNetworks returns a slice of networks parsed form the request header.
func getHeaderNetworks(r *http.Request) ([]string, error) {
headerNetworks := r.Header.Get("networks")
networksSlice := []string{}
err := json.Unmarshal([]byte(headerNetworks), &networksSlice)
if err != nil {
return nil, err
}
return networksSlice, nil
}

View file

@ -2,13 +2,14 @@ package controller
import (
"bytes"
"github.com/go-jose/go-jose/v3/json"
"github.com/gorilla/mux"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-jose/go-jose/v3/json"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/gravitl/netmaker/logic"
@ -47,72 +48,10 @@ func TestCreateAdminNoHashedPassword(t *testing.T) {
rec, req := prepareUserRequest(t, user, "")
// test response
createAdmin(rec, req)
createSuperAdmin(rec, req)
assertUserNameButNoPassword(t, rec.Body, user.UserName)
}
func TestCreateUserNoHashedPassword(t *testing.T) {
// prepare existing user base
deleteAllUsers(t)
// prepare request
user := models.User{UserName: "jonathan", Password: "password"}
rec, req := prepareUserRequest(t, user, "")
// test response
createUser(rec, req)
assertUserNameButNoPassword(t, rec.Body, user.UserName)
}
func TestUpdateUserNetworksNoHashedPassword(t *testing.T) {
// prepare existing user base
user1 := models.User{UserName: "joestar", Password: "jonathan"}
haveOnlyOneUser(t, user1)
// prepare request
user2 := models.User{UserName: "joestar", Password: "joseph"}
rec, req := prepareUserRequest(t, user2, user1.UserName)
// test response
updateUserNetworks(rec, req)
assertUserNameButNoPassword(t, rec.Body, user1.UserName)
}
func TestUpdateUserNoHashedPassword(t *testing.T) {
// prepare existing user base
user1 := models.User{UserName: "dio", Password: "brando"}
haveOnlyOneUser(t, user1)
// prepare request
user2 := models.User{UserName: "giorno", Password: "giovanna"}
rec, req := prepareUserRequest(t, user2, user1.UserName)
// mock the jwt verification
oldVerify := verifyJWT
verifyJWT = func(bearerToken string) (username string, networks []string, isadmin bool, err error) {
return user1.UserName, user1.Networks, user1.IsAdmin, nil
}
defer func() { verifyJWT = oldVerify }()
// test response
updateUser(rec, req)
assertUserNameButNoPassword(t, rec.Body, user2.UserName)
}
func TestUpdateUserAdmNoHashedPassword(t *testing.T) {
// prepare existing user base
user1 := models.User{UserName: "dio", Password: "brando", IsAdmin: true}
haveOnlyOneUser(t, user1)
// prepare request
user2 := models.User{UserName: "giorno", Password: "giovanna"}
rec, req := prepareUserRequest(t, user2, user1.UserName)
// test response
updateUserAdm(rec, req)
assertUserNameButNoPassword(t, rec.Body, user2.UserName)
}
func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam string) (*httptest.ResponseRecorder, *http.Request) {
bits, err := json.Marshal(userForBody)
assert.Nil(t, err)
@ -120,14 +59,15 @@ func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam
rec := httptest.NewRecorder()
req := httptest.NewRequest("ANY", "https://example.com", body) // only the body matters here
req = mux.SetURLVars(req, map[string]string{"username": userNameForParam})
req.Header.Set("user", userForBody.UserName)
return rec, req
}
func haveOnlyOneUser(t *testing.T, user models.User) {
deleteAllUsers(t)
var err error
if user.IsAdmin {
err = logic.CreateAdmin(&user)
if user.IsSuperAdmin {
err = logic.CreateSuperAdmin(&user)
} else {
err = logic.CreateUser(&user)
}
@ -142,7 +82,7 @@ func assertUserNameButNoPassword(t *testing.T, r io.Reader, userName string) {
assert.Empty(t, resp.Password)
}
func TestHasAdmin(t *testing.T) {
func TestHasSuperAdmin(t *testing.T) {
// delete all current users
users, _ := logic.GetUsers()
for _, user := range users {
@ -151,31 +91,31 @@ func TestHasAdmin(t *testing.T) {
assert.True(t, success)
}
t.Run("NoUser", func(t *testing.T) {
found, err := logic.HasAdmin()
found, err := logic.HasSuperAdmin()
assert.Nil(t, err)
assert.False(t, found)
})
t.Run("No admin user", func(t *testing.T) {
var user = models.User{UserName: "noadmin", Password: "password"}
t.Run("No superadmin user", func(t *testing.T) {
var user = models.User{UserName: "nosuperadmin", Password: "password"}
err := logic.CreateUser(&user)
assert.Nil(t, err)
found, err := logic.HasAdmin()
found, err := logic.HasSuperAdmin()
assert.Nil(t, err)
assert.False(t, found)
})
t.Run("admin user", func(t *testing.T) {
var user = models.User{UserName: "admin", Password: "password", IsAdmin: true}
t.Run("superadmin user", func(t *testing.T) {
var user = models.User{UserName: "superadmin", Password: "password", IsSuperAdmin: true}
err := logic.CreateUser(&user)
assert.Nil(t, err)
found, err := logic.HasAdmin()
found, err := logic.HasSuperAdmin()
assert.Nil(t, err)
assert.True(t, found)
})
t.Run("multiple admins", func(t *testing.T) {
var user = models.User{UserName: "admin1", Password: "password", IsAdmin: true}
t.Run("multiple superadmins", func(t *testing.T) {
var user = models.User{UserName: "superadmin1", Password: "password", IsSuperAdmin: true}
err := logic.CreateUser(&user)
assert.Nil(t, err)
found, err := logic.HasAdmin()
found, err := logic.HasSuperAdmin()
assert.Nil(t, err)
assert.True(t, found)
})
@ -195,20 +135,20 @@ func TestCreateUser(t *testing.T) {
})
}
func TestCreateAdmin(t *testing.T) {
func TestCreateSuperAdmin(t *testing.T) {
deleteAllUsers(t)
var user models.User
t.Run("NoAdmin", func(t *testing.T) {
t.Run("NoSuperAdmin", func(t *testing.T) {
user.UserName = "admin"
user.Password = "password"
err := logic.CreateAdmin(&user)
err := logic.CreateSuperAdmin(&user)
assert.Nil(t, err)
})
t.Run("AdminExists", func(t *testing.T) {
t.Run("SuperAdminExists", func(t *testing.T) {
user.UserName = "admin2"
user.Password = "password1"
err := logic.CreateAdmin(&user)
assert.EqualError(t, err, "admin user already exists")
err := logic.CreateSuperAdmin(&user)
assert.EqualError(t, err, "superadmin user already exists")
})
}
@ -280,7 +220,7 @@ func TestValidateUser(t *testing.T) {
func TestGetUser(t *testing.T) {
deleteAllUsers(t)
user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
t.Run("NonExistantUser", func(t *testing.T) {
admin, err := logic.GetUser("admin")
@ -338,7 +278,7 @@ func TestGetUsers(t *testing.T) {
func TestUpdateUser(t *testing.T) {
deleteAllUsers(t)
user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
newuser := models.User{UserName: "hello", Password: "world", Networks: []string{"wirecat, netmaker"}, IsAdmin: true, Groups: []string{}}
newuser := models.User{UserName: "hello", Password: "world", IsAdmin: true}
t.Run("NonExistantUser", func(t *testing.T) {
admin, err := logic.UpdateUser(&newuser, &user)
assert.EqualError(t, err, "could not find any records")
@ -381,7 +321,7 @@ func TestUpdateUser(t *testing.T) {
func TestVerifyAuthRequest(t *testing.T) {
deleteAllUsers(t)
user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
user := models.User{UserName: "admin", Password: "password", IsSuperAdmin: false, IsAdmin: true}
var authRequest models.UserAuthParams
t.Run("EmptyUserName", func(t *testing.T) {
authRequest.UserName = ""
@ -402,7 +342,7 @@ func TestVerifyAuthRequest(t *testing.T) {
authRequest.Password = "password"
jwt, err := logic.VerifyAuthRequest(authRequest)
assert.Equal(t, "", jwt)
assert.EqualError(t, err, "error retrieving user from db: could not find any records")
assert.EqualError(t, err, "incorrect credentials")
})
t.Run("Non-Admin", func(t *testing.T) {
user.IsAdmin = false
@ -417,7 +357,7 @@ func TestVerifyAuthRequest(t *testing.T) {
assert.Nil(t, err)
})
t.Run("WrongPassword", func(t *testing.T) {
user := models.User{UserName: "admin", Password: "password", Groups: []string{}}
user := models.User{UserName: "admin", Password: "password"}
if err := logic.CreateUser(&user); err != nil {
t.Error(err)
}

View file

@ -25,12 +25,10 @@ var (
func TestMain(m *testing.M) {
database.InitializeDatabase()
defer database.CloseDB()
logic.CreateAdmin(&models.User{
UserName: "admin",
Password: "password",
IsAdmin: true,
Networks: []string{},
Groups: []string{},
logic.CreateSuperAdmin(&models.User{
UserName: "superadmin",
Password: "password",
IsSuperAdmin: true,
})
peerUpdate := make(chan *models.Node)
go logic.ManageZombies(context.Background(), peerUpdate)

View file

@ -11,14 +11,11 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
"github.com/gravitl/netmaker/servercfg"
)
// HasAdmin - checks if server has an admin
func HasAdmin() (bool, error) {
// HasSuperAdmin - checks if server has an superadmin/owner
func HasSuperAdmin() (bool, error) {
collection, err := database.FetchRecords(database.USERS_TABLE_NAME)
if err != nil {
@ -34,7 +31,7 @@ func HasAdmin() (bool, error) {
if err != nil {
continue
}
if user.IsAdmin {
if user.IsSuperAdmin {
return true, nil
}
}
@ -85,9 +82,8 @@ func CreateUser(user *models.User) error {
// set password to encrypted password
user.Password = string(hash)
tokenString, _ := CreateProUserJWT(user.UserName, user.Networks, user.Groups, user.IsAdmin)
tokenString, _ := CreateUserJWT(user.UserName, user.IsSuperAdmin, user.IsAdmin)
if tokenString == "" {
// logic.ReturnErrorResponse(w, r, errorResponse)
return err
}
@ -103,56 +99,20 @@ func CreateUser(user *models.User) error {
return err
}
// == PRO == Add user to every network as network user ==
currentNets, err := GetNetworks()
if err != nil {
currentNets = []models.Network{}
}
for i := range currentNets {
newUser := promodels.NetworkUser{
ID: promodels.NetworkUserID(user.UserName),
Clients: []string{},
Nodes: []string{},
}
pro.AddProNetDefaults(&currentNets[i])
if pro.IsUserAllowed(&currentNets[i], user.UserName, user.Groups) {
newUser.AccessLevel = currentNets[i].ProSettings.DefaultAccessLevel
newUser.ClientLimit = currentNets[i].ProSettings.DefaultUserClientLimit
newUser.NodeLimit = currentNets[i].ProSettings.DefaultUserNodeLimit
} else {
newUser.AccessLevel = pro.NO_ACCESS
newUser.ClientLimit = 0
newUser.NodeLimit = 0
}
// legacy
if StringSliceContains(user.Networks, currentNets[i].NetID) {
if !servercfg.IsPro {
newUser.AccessLevel = pro.NET_ADMIN
}
}
userErr := pro.CreateNetworkUser(&currentNets[i], &newUser)
if userErr != nil {
logger.Log(0, "failed to add network user data on network", currentNets[i].NetID, "for user", user.UserName)
}
}
// == END PRO ==
return nil
}
// CreateAdmin - creates an admin user
func CreateAdmin(admin *models.User) error {
hasadmin, err := HasAdmin()
// CreateSuperAdmin - creates an super admin user
func CreateSuperAdmin(u *models.User) error {
hassuperadmin, err := HasSuperAdmin()
if err != nil {
return err
}
if hasadmin {
return errors.New("admin user already exists")
if hassuperadmin {
return errors.New("superadmin user already exists")
}
admin.IsAdmin = true
return CreateUser(admin)
u.IsSuperAdmin = true
return CreateUser(u)
}
// VerifyAuthRequest - verifies an auth request
@ -166,7 +126,7 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
// Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved).
record, err := database.FetchRecord(database.USERS_TABLE_NAME, authRequest.UserName)
if err != nil {
return "", errors.New("error retrieving user from db: " + err.Error())
return "", errors.New("incorrect credentials")
}
if err = json.Unmarshal([]byte(record), &result); err != nil {
return "", errors.New("error unmarshalling user json: " + err.Error())
@ -180,65 +140,20 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
}
// Create a new JWT for the node
tokenString, _ := CreateProUserJWT(authRequest.UserName, result.Networks, result.Groups, result.IsAdmin)
tokenString, _ := CreateUserJWT(authRequest.UserName, result.IsSuperAdmin, result.IsAdmin)
return tokenString, nil
}
// UpdateUserNetworks - updates the networks of a given user
func UpdateUserNetworks(newNetworks, newGroups []string, isadmin bool, currentUser *models.ReturnUser) error {
// check if user exists
returnedUser, err := GetUser(currentUser.UserName)
// UpsertUser - updates user in the db
func UpsertUser(user models.User) error {
data, err := json.Marshal(&user)
if err != nil {
return err
} else if returnedUser.IsAdmin {
return fmt.Errorf("can not make changes to an admin user, attempted to change %s", returnedUser.UserName)
}
if isadmin {
currentUser.IsAdmin = true
currentUser.Networks = nil
} else {
// == PRO ==
currentUser.Groups = newGroups
for _, n := range newNetworks {
if !StringSliceContains(currentUser.Networks, n) {
// make net admin of any network not previously assigned
pro.MakeNetAdmin(n, currentUser.UserName)
}
}
// Compare networks, find networks not in previous
for _, n := range currentUser.Networks {
if !StringSliceContains(newNetworks, n) {
// if user was removed from a network, re-assign access to net default level
if network, err := GetNetwork(n); err == nil {
if network.ProSettings != nil {
ok := pro.AssignAccessLvl(n, currentUser.UserName, network.ProSettings.DefaultAccessLevel)
if ok {
logger.Log(0, "changed", currentUser.UserName, "access level on network", network.NetID, "to", fmt.Sprintf("%d", network.ProSettings.DefaultAccessLevel))
}
}
}
}
}
if err := AdjustGroupPermissions(currentUser); err != nil {
logger.Log(0, "failed to update user", currentUser.UserName, "after group update", err.Error())
}
// == END PRO ==
currentUser.Networks = newNetworks
if err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME); err != nil {
return err
}
userChange := models.User{
UserName: currentUser.UserName,
Networks: currentUser.Networks,
IsAdmin: currentUser.IsAdmin,
Password: "",
Groups: currentUser.Groups,
}
_, err = UpdateUser(&userChange, returnedUser)
return err
return nil
}
// UpdateUser - updates a given user
@ -249,16 +164,13 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
}
queryUser := user.UserName
if userchange.UserName != "" {
if userchange.UserName != "" && user.UserName != userchange.UserName {
// check if username is available
if _, err := GetUser(userchange.UserName); err == nil {
return &models.User{}, errors.New("username exists already")
}
user.UserName = userchange.UserName
}
if len(userchange.Networks) > 0 {
user.Networks = userchange.Networks
}
if len(userchange.Groups) > 0 {
user.Groups = userchange.Groups
}
if userchange.Password != "" {
// encrypt that password so we never see it again
hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
@ -271,10 +183,7 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
user.Password = userchange.Password
}
if (userchange.IsAdmin != user.IsAdmin) && !user.IsAdmin {
user.IsAdmin = userchange.IsAdmin
}
user.IsAdmin = userchange.IsAdmin
err := ValidateUser(user)
if err != nil {
@ -326,23 +235,6 @@ func DeleteUser(user string) (bool, error) {
return false, err
}
// == pro - remove user from all network user instances ==
currentNets, err := GetNetworks()
if err != nil {
if database.IsEmptyRecord(err) {
currentNets = []models.Network{}
} else {
return true, err
}
}
for i := range currentNets {
netID := currentNets[i].NetID
if err = pro.DeleteNetworkUser(netID, user); err != nil {
logger.Log(0, "failed to remove", user, "as network user from network", netID, err.Error())
}
}
return true, nil
}
@ -414,51 +306,3 @@ func IsStateValid(state string) (string, bool) {
func delState(state string) error {
return database.DeleteRecord(database.SSO_STATE_CACHE, state)
}
// PRO
// AdjustGroupPermissions - adjusts a given user's network access based on group changes
func AdjustGroupPermissions(user *models.ReturnUser) error {
networks, err := GetNetworks()
if err != nil {
return err
}
// UPDATE
// go through all networks and see if new group is in
// if access level of current user is greater (value) than network's default
// assign network's default
// DELETE
// if user not allowed on network a
for i := range networks {
AdjustNetworkUserPermissions(user, &networks[i])
}
return nil
}
// AdjustNetworkUserPermissions - adjusts a given user's network access based on group changes
func AdjustNetworkUserPermissions(user *models.ReturnUser, network *models.Network) error {
networkUser, err := pro.GetNetworkUser(
network.NetID,
promodels.NetworkUserID(user.UserName),
)
if err == nil && network.ProSettings != nil {
if pro.IsUserAllowed(network, user.UserName, user.Groups) {
if networkUser.AccessLevel > network.ProSettings.DefaultAccessLevel {
networkUser.AccessLevel = network.ProSettings.DefaultAccessLevel
}
if networkUser.NodeLimit < network.ProSettings.DefaultUserNodeLimit {
networkUser.NodeLimit = network.ProSettings.DefaultUserNodeLimit
}
if networkUser.ClientLimit < network.ProSettings.DefaultUserClientLimit {
networkUser.ClientLimit = network.ProSettings.DefaultUserClientLimit
}
} else {
networkUser.AccessLevel = pro.NO_ACCESS
networkUser.NodeLimit = 0
networkUser.ClientLimit = 0
}
pro.UpdateNetworkUser(network.NetID, networkUser)
}
return err
}

View file

@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"golang.org/x/exp/slices"
"time"
"github.com/gravitl/netmaker/database"
@ -224,16 +223,3 @@ func getEnrollmentKeysMap() (map[string]*models.EnrollmentKey, error) {
}
return currentKeys, nil
}
// UserHasNetworksAccess - checks if a user `u` has access to all `networks`
func UserHasNetworksAccess(networks []string, u *models.User) bool {
if u.IsAdmin {
return true
}
for _, n := range networks {
if !slices.Contains(u.Networks, n) {
return false
}
}
return true
}

View file

@ -204,77 +204,3 @@ func TestDeTokenize_EnrollmentKeys(t *testing.T) {
removeAllEnrollments()
}
func TestHasNetworksAccess(t *testing.T) {
type Case struct {
// network names
n []string
u models.User
}
pass := []Case{
{
n: []string{"n1", "n2"},
u: models.User{
Networks: []string{"n1", "n2"},
IsAdmin: false,
},
},
{
n: []string{"n1", "n2"},
u: models.User{
Networks: []string{},
IsAdmin: true,
},
},
{
n: []string{"n1", "n2"},
u: models.User{
Networks: []string{"n1", "n2", "n3"},
IsAdmin: false,
},
},
{
n: []string{"n2"},
u: models.User{
Networks: []string{"n2"},
IsAdmin: false,
},
},
}
deny := []Case{
{
n: []string{"n1", "n2"},
u: models.User{
Networks: []string{"n2"},
IsAdmin: false,
},
},
{
n: []string{"n1", "n2"},
u: models.User{
Networks: []string{},
IsAdmin: false,
},
},
{
n: []string{"n1", "n2"},
u: models.User{
Networks: []string{"n3"},
IsAdmin: false,
},
},
{
n: []string{"n2"},
u: models.User{
Networks: []string{"n1"},
IsAdmin: false,
},
},
}
for _, tc := range pass {
assert.True(t, UserHasNetworksAccess(tc.n, &tc.u))
}
for _, tc := range deny {
assert.False(t, UserHasNetworksAccess(tc.n, &tc.u))
}
}

View file

@ -159,6 +159,25 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
return node, err
}
// GetIngressGwUsers - lists the users having to access to ingressGW
func GetIngressGwUsers(node models.Node) (models.IngressGwUsers, error) {
gwUsers := models.IngressGwUsers{
NodeID: node.ID.String(),
Network: node.Network,
}
users, err := GetUsers()
if err != nil {
return gwUsers, err
}
for _, user := range users {
if !user.IsAdmin && !user.IsSuperAdmin {
gwUsers.Users = append(gwUsers.Users, user)
}
}
return gwUsers, nil
}
// DeleteIngressGateway - deletes an ingress gateway
func DeleteIngressGateway(nodeid string) (models.Node, bool, []models.ExtClient, error) {
removedClients := []models.ExtClient{}
@ -210,3 +229,20 @@ func DeleteGatewayExtClients(gatewayID string, networkName string) error {
}
return nil
}
// IsUserAllowedAccessToExtClient - checks if user has permission to access extclient
func IsUserAllowedAccessToExtClient(username string, client models.ExtClient) bool {
if username == MasterUser {
return true
}
user, err := GetUser(username)
if err != nil {
return false
}
if !user.IsAdmin && !user.IsSuperAdmin {
if user.UserName != client.OwnerID {
return false
}
}
return true
}

View file

@ -52,37 +52,13 @@ func CreateJWT(uuid string, macAddress string, network string) (response string,
return "", err
}
// CreateProUserJWT - creates a user jwt token
func CreateProUserJWT(username string, networks, groups []string, isadmin bool) (response string, err error) {
expirationTime := time.Now().Add(60 * 12 * time.Minute)
claims := &models.UserClaims{
UserName: username,
Networks: networks,
IsAdmin: isadmin,
Groups: groups,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "Netmaker",
Subject: fmt.Sprintf("user|%s", username),
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtSecretKey)
if err == nil {
return tokenString, nil
}
return "", err
}
// CreateUserJWT - creates a user jwt token
func CreateUserJWT(username string, networks []string, isadmin bool) (response string, err error) {
func CreateUserJWT(username string, issuperadmin, isadmin bool) (response string, err error) {
expirationTime := time.Now().Add(60 * 12 * time.Minute)
claims := &models.UserClaims{
UserName: username,
Networks: networks,
IsAdmin: isadmin,
UserName: username,
IsSuperAdmin: issuperadmin,
IsAdmin: isadmin,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "Netmaker",
Subject: fmt.Sprintf("user|%s", username),
@ -100,23 +76,23 @@ func CreateUserJWT(username string, networks []string, isadmin bool) (response s
}
// VerifyJWT verifies Auth Header
func VerifyJWT(bearerToken string) (username string, networks []string, isadmin bool, err error) {
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 "", nil, false, errors.New("invalid auth header")
return "", false, false, errors.New("invalid auth header")
}
return VerifyUserToken(token)
}
// VerifyUserToken func will used to Verify the JWT Token while using APIS
func VerifyUserToken(tokenString string) (username string, networks []string, isadmin bool, err error) {
func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin bool, err error) {
claims := &models.UserClaims{}
if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" {
return "masteradministrator", nil, true, nil
return MasterUser, true, true, nil
}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
@ -128,15 +104,15 @@ func VerifyUserToken(tokenString string) (username string, networks []string, is
// check that user exists
user, err = GetUser(claims.UserName)
if err != nil {
return "", nil, false, err
return "", false, false, err
}
if user.UserName != "" {
return claims.UserName, claims.Networks, claims.IsAdmin, nil
return claims.UserName, claims.IsSuperAdmin, claims.IsAdmin, nil
}
err = errors.New("user does not exist")
}
return "", nil, false, err
return "", false, false, err
}
// VerifyHostToken - [hosts] Only

View file

@ -14,7 +14,6 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic/acls/nodeacls"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/validation"
)
@ -51,9 +50,6 @@ func DeleteNetwork(network string) error {
nodeCount, err := GetNetworkNonServerNodeCount(network)
if nodeCount == 0 || database.IsEmptyRecord(err) {
// delete server nodes first then db records
if err = pro.RemoveAllNetworkUsers(network); err != nil {
logger.Log(0, "failed to remove network users on network delete for network", network, err.Error())
}
return database.DeleteRecord(database.NETWORKS_TABLE_NAME, network)
}
return errors.New("node check failed. All nodes must be deleted before deleting network")
@ -81,22 +77,12 @@ func CreateNetwork(network models.Network) (models.Network, error) {
network.SetNodesLastModified()
network.SetNetworkLastModified()
pro.AddProNetDefaults(&network)
if len(network.ProSettings.AllowedGroups) == 0 {
network.ProSettings.AllowedGroups = []string{pro.DEFAULT_ALLOWED_GROUPS}
}
err := ValidateNetwork(&network, false)
if err != nil {
//logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return models.Network{}, err
}
if err = pro.InitializeNetworkUsers(network.NetID); err != nil {
return models.Network{}, err
}
data, err := json.Marshal(&network)
if err != nil {
return models.Network{}, err
@ -106,11 +92,6 @@ func CreateNetwork(network models.Network) (models.Network, error) {
return models.Network{}, err
}
// == add all current users to network as network users ==
if err = InitializeNetUsers(&network); err != nil {
return network, err
}
return network, nil
}
@ -302,28 +283,24 @@ func IsNetworkNameUnique(network *models.Network) (bool, error) {
}
// UpdateNetwork - updates a network with another network's fields
func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (bool, bool, bool, []string, []string, error) {
func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (bool, bool, bool, error) {
if err := ValidateNetwork(newNetwork, true); err != nil {
return false, false, false, nil, nil, err
return false, false, false, err
}
if newNetwork.NetID == currentNetwork.NetID {
hasrangeupdate4 := newNetwork.AddressRange != currentNetwork.AddressRange
hasrangeupdate6 := newNetwork.AddressRange6 != currentNetwork.AddressRange6
hasholepunchupdate := newNetwork.DefaultUDPHolePunch != currentNetwork.DefaultUDPHolePunch
groupDelta := append(StringDifference(newNetwork.ProSettings.AllowedGroups, currentNetwork.ProSettings.AllowedGroups),
StringDifference(currentNetwork.ProSettings.AllowedGroups, newNetwork.ProSettings.AllowedGroups)...)
userDelta := append(StringDifference(newNetwork.ProSettings.AllowedUsers, currentNetwork.ProSettings.AllowedUsers),
StringDifference(currentNetwork.ProSettings.AllowedUsers, newNetwork.ProSettings.AllowedUsers)...)
data, err := json.Marshal(newNetwork)
if err != nil {
return false, false, false, nil, nil, err
return false, false, false, err
}
newNetwork.SetNetworkLastModified()
err = database.Insert(newNetwork.NetID, string(data), database.NETWORKS_TABLE_NAME)
return hasrangeupdate4, hasrangeupdate6, hasholepunchupdate, groupDelta, userDelta, err
return hasrangeupdate4, hasrangeupdate6, hasholepunchupdate, err
}
// copy values
return false, false, false, nil, nil, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.")
return false, false, false, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.")
}
// GetNetwork - gets a network from database
@ -375,15 +352,6 @@ func ValidateNetwork(network *models.Network, isUpdate bool) error {
}
}
if network.ProSettings != nil {
if network.ProSettings.DefaultAccessLevel < pro.NET_ADMIN || network.ProSettings.DefaultAccessLevel > pro.NO_ACCESS {
return fmt.Errorf("invalid access level")
}
if network.ProSettings.DefaultUserClientLimit < 0 || network.ProSettings.DefaultUserNodeLimit < 0 {
return fmt.Errorf("invalid node/client limit provided")
}
}
return err
}

View file

@ -16,7 +16,6 @@ import (
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic/acls"
"github.com/gravitl/netmaker/logic/acls/nodeacls"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
"github.com/gravitl/netmaker/validation"
@ -235,12 +234,6 @@ func deleteNodeByID(node *models.Node) error {
if servercfg.IsDNSMode() {
SetDNS()
}
if node.OwnerID != "" {
err = pro.DissociateNetworkUserNode(node.OwnerID, node.Network, node.ID.String())
if err != nil {
logger.Log(0, "failed to dissasociate", node.OwnerID, "from node", node.ID.String(), ":", err.Error())
}
}
_, err = nodeacls.RemoveNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()))
if err != nil {
// ignoring for now, could hit a nil pointer if delete called twice

View file

@ -1,68 +0,0 @@
package pro
import (
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
)
// AddProNetDefaults - adds default values to a network model
func AddProNetDefaults(network *models.Network) {
if network.ProSettings == nil {
newProSettings := promodels.ProNetwork{
DefaultAccessLevel: NO_ACCESS,
DefaultUserNodeLimit: 0,
DefaultUserClientLimit: 0,
AllowedUsers: []string{},
AllowedGroups: []string{DEFAULT_ALLOWED_GROUPS},
}
network.ProSettings = &newProSettings
}
if network.ProSettings.AllowedUsers == nil {
network.ProSettings.AllowedUsers = []string{}
}
if network.ProSettings.AllowedGroups == nil {
network.ProSettings.AllowedGroups = []string{DEFAULT_ALLOWED_GROUPS}
}
}
// isUserGroupAllowed - checks if a user group is allowed on a network
func isUserGroupAllowed(network *models.Network, groupName string) bool {
if network.ProSettings != nil {
if len(network.ProSettings.AllowedGroups) > 0 {
for i := range network.ProSettings.AllowedGroups {
currentGroup := network.ProSettings.AllowedGroups[i]
if currentGroup == DEFAULT_ALLOWED_GROUPS || currentGroup == groupName {
return true
}
}
}
}
return false
}
func isUserInAllowedUsers(network *models.Network, userName string) bool {
if network.ProSettings != nil {
if len(network.ProSettings.AllowedUsers) > 0 {
for i := range network.ProSettings.AllowedUsers {
currentUser := network.ProSettings.AllowedUsers[i]
if currentUser == DEFAULT_ALLOWED_USERS || currentUser == userName {
return true
}
}
}
}
return false
}
// IsUserAllowed - checks if given username + groups if a user is allowed on network
func IsUserAllowed(network *models.Network, userName string, groups []string) bool {
isGroupAllowed := false
for _, g := range groups {
if isUserGroupAllowed(network, g) {
isGroupAllowed = true
break
}
}
return isUserInAllowedUsers(network, userName) || isGroupAllowed
}

View file

@ -1,64 +0,0 @@
package pro
import (
"testing"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
"github.com/stretchr/testify/assert"
)
func TestNetworkProSettings(t *testing.T) {
t.Run("Uninitialized with pro", func(t *testing.T) {
network := models.Network{
NetID: "helloworld",
}
assert.Nil(t, network.ProSettings)
})
t.Run("Initialized with pro", func(t *testing.T) {
network := models.Network{
NetID: "helloworld",
}
AddProNetDefaults(&network)
assert.NotNil(t, network.ProSettings)
})
t.Run("Net Zero Defaults set correctly with Pro", func(t *testing.T) {
network := models.Network{
NetID: "helloworld",
}
AddProNetDefaults(&network)
assert.NotNil(t, network.ProSettings)
assert.Equal(t, NO_ACCESS, network.ProSettings.DefaultAccessLevel)
assert.Equal(t, 0, network.ProSettings.DefaultUserClientLimit)
assert.Equal(t, 0, network.ProSettings.DefaultUserNodeLimit)
})
t.Run("Net Defaults set correctly with Pro", func(t *testing.T) {
network := models.Network{
NetID: "helloworld",
ProSettings: &promodels.ProNetwork{
DefaultAccessLevel: NET_ADMIN,
DefaultUserNodeLimit: 10,
DefaultUserClientLimit: 25,
},
}
AddProNetDefaults(&network)
assert.NotNil(t, network.ProSettings)
assert.Equal(t, NET_ADMIN, network.ProSettings.DefaultAccessLevel)
assert.Equal(t, 25, network.ProSettings.DefaultUserClientLimit)
assert.Equal(t, 10, network.ProSettings.DefaultUserNodeLimit)
})
t.Run("Net Defaults set to allow all groups/users", func(t *testing.T) {
network := models.Network{
NetID: "helloworld",
ProSettings: &promodels.ProNetwork{
DefaultAccessLevel: NET_ADMIN,
DefaultUserNodeLimit: 10,
DefaultUserClientLimit: 25,
},
}
AddProNetDefaults(&network)
assert.NotNil(t, network.ProSettings)
assert.Equal(t, len(network.ProSettings.AllowedGroups), 1)
assert.Equal(t, len(network.ProSettings.AllowedUsers), 0)
})
}

View file

@ -1,251 +0,0 @@
package pro
import (
"encoding/json"
"fmt"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
)
// InitializeNetworkUsers - intializes network users for a given network
func InitializeNetworkUsers(network string) error {
_, err := database.FetchRecord(database.NETWORK_USER_TABLE_NAME, network)
if err != nil && database.IsEmptyRecord(err) {
newNetUserMap := make(promodels.NetworkUserMap)
netUserData, err := json.Marshal(newNetUserMap)
if err != nil {
return err
}
return database.Insert(network, string(netUserData), database.NETWORK_USER_TABLE_NAME)
}
return err
}
// GetNetworkUsers - gets the network users table
func GetNetworkUsers(network string) (promodels.NetworkUserMap, error) {
currentUsers, err := database.FetchRecord(database.NETWORK_USER_TABLE_NAME, network)
if err != nil {
return nil, err
}
var userMap promodels.NetworkUserMap
if err = json.Unmarshal([]byte(currentUsers), &userMap); err != nil {
return nil, err
}
return userMap, nil
}
// CreateNetworkUser - adds a network user to db
func CreateNetworkUser(network *models.Network, user *promodels.NetworkUser) error {
if DoesNetworkUserExist(network.NetID, user.ID) {
return nil
}
currentUsers, err := GetNetworkUsers(network.NetID)
if err != nil {
return err
}
user.SetDefaults()
currentUsers.Add(user)
data, err := json.Marshal(currentUsers)
if err != nil {
return err
}
return database.Insert(network.NetID, string(data), database.NETWORK_USER_TABLE_NAME)
}
// DeleteNetworkUser - deletes a network user and removes from all networks
func DeleteNetworkUser(network, userid string) error {
currentUsers, err := GetNetworkUsers(network)
if err != nil {
return err
}
currentUsers.Delete(promodels.NetworkUserID(userid))
data, err := json.Marshal(currentUsers)
if err != nil {
return err
}
return database.Insert(network, string(data), database.NETWORK_USER_TABLE_NAME)
}
// DissociateNetworkUserNode - removes a node from a given user's node list
func DissociateNetworkUserNode(userid, networkid, nodeid string) error {
nuser, err := GetNetworkUser(networkid, promodels.NetworkUserID(userid))
if err != nil {
return err
}
for i, n := range nuser.Nodes {
if n == nodeid {
nuser.Nodes = removeStringIndex(nuser.Nodes, i)
break
}
}
return UpdateNetworkUser(networkid, nuser)
}
// DissociateNetworkUserClient - removes a client from a given user's client list
func DissociateNetworkUserClient(userid, networkid, clientid string) error {
nuser, err := GetNetworkUser(networkid, promodels.NetworkUserID(userid))
if err != nil {
return err
}
for i, n := range nuser.Clients {
if n == clientid {
nuser.Clients = removeStringIndex(nuser.Clients, i)
break
}
}
return UpdateNetworkUser(networkid, nuser)
}
// AssociateNetworkUserClient - removes a client from a given user's client list
func AssociateNetworkUserClient(userid, networkid, clientid string) error {
nuser, err := GetNetworkUser(networkid, promodels.NetworkUserID(userid))
if err != nil {
return err
}
var found bool
for _, n := range nuser.Clients {
if n == clientid {
found = true
break
}
}
if found {
return nil
} else {
nuser.Clients = append(nuser.Clients, clientid)
}
return UpdateNetworkUser(networkid, nuser)
}
func removeStringIndex(s []string, index int) []string {
ret := make([]string, 0)
ret = append(ret, s[:index]...)
return append(ret, s[index+1:]...)
}
// GetNetworkUser - fetches a network user from a given network
func GetNetworkUser(network string, userID promodels.NetworkUserID) (*promodels.NetworkUser, error) {
currentUsers, err := GetNetworkUsers(network)
if err != nil {
return nil, err
}
if currentUsers[userID].ID == "" {
return nil, fmt.Errorf("user %s does not exist", userID)
}
currentNetUser := currentUsers[userID]
return &currentNetUser, nil
}
// DoesNetworkUserExist - check if networkuser exists
func DoesNetworkUserExist(network string, userID promodels.NetworkUserID) bool {
_, err := GetNetworkUser(network, userID)
return err == nil
}
// UpdateNetworkUser - gets a network user from given network
func UpdateNetworkUser(network string, newUser *promodels.NetworkUser) error {
currentUsers, err := GetNetworkUsers(network)
if err != nil {
return err
}
currentUsers[newUser.ID] = *newUser
newUsersData, err := json.Marshal(&currentUsers)
if err != nil {
return err
}
return database.Insert(network, string(newUsersData), database.NETWORK_USER_TABLE_NAME)
}
// RemoveAllNetworkUsers - removes all network users from given network
func RemoveAllNetworkUsers(network string) error {
return database.DeleteRecord(database.NETWORK_USER_TABLE_NAME, network)
}
// IsUserNodeAllowed - given a list of nodes, determine if the user's node is allowed based on ID
// Checks if node is in given nodes list as well as being in user's list
func IsUserNodeAllowed(nodes []models.Node, network, userID, nodeID string) bool {
netUser, err := GetNetworkUser(network, promodels.NetworkUserID(userID))
if err != nil {
return false
}
for i := range nodes {
if nodes[i].ID.String() == nodeID {
for j := range netUser.Nodes {
if netUser.Nodes[j] == nodeID {
return true
}
}
}
}
return false
}
// IsUserClientAllowed - given a list of clients, determine if the user's client is allowed based on ID
// Checks if client is in given ext client list as well as being in user's list
func IsUserClientAllowed(clients []models.ExtClient, network, userID, clientID string) bool {
netUser, err := GetNetworkUser(network, promodels.NetworkUserID(userID))
if err != nil {
return false
}
for i := range clients {
if clients[i].ClientID == clientID {
for j := range netUser.Clients {
if netUser.Clients[j] == clientID {
return true
}
}
}
}
return false
}
// IsUserNetAdmin - checks if a user is a net admin or not
func IsUserNetAdmin(network, userID string) bool {
user, err := GetNetworkUser(network, promodels.NetworkUserID(userID))
if err != nil {
return false
}
return user.AccessLevel == NET_ADMIN
}
// MakeNetAdmin - makes a given user a network admin on given network
func MakeNetAdmin(network, userID string) (ok bool) {
user, err := GetNetworkUser(network, promodels.NetworkUserID(userID))
if err != nil {
return ok
}
user.AccessLevel = NET_ADMIN
if err = UpdateNetworkUser(network, user); err != nil {
return ok
}
return true
}
// AssignAccessLvl - gives a user a specified access level
func AssignAccessLvl(network, userID string, accesslvl int) (ok bool) {
user, err := GetNetworkUser(network, promodels.NetworkUserID(userID))
if err != nil {
return ok
}
user.AccessLevel = accesslvl
if err = UpdateNetworkUser(network, user); err != nil {
return ok
}
return true
}

View file

@ -1,110 +0,0 @@
package pro
import (
"os"
"testing"
"github.com/google/uuid"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
database.InitializeDatabase()
defer database.CloseDB()
os.Exit(m.Run())
}
func TestNetworkUserLogic(t *testing.T) {
networkUser := promodels.NetworkUser{
ID: "helloworld",
}
network := models.Network{
NetID: "skynet",
AddressRange: "192.168.0.0/24",
}
tmpCNode := models.CommonNode{
ID: uuid.New(),
}
tempNode := models.Node{}
tempNode.CommonNode = tmpCNode
nodes := []models.Node{
tempNode,
}
clients := []models.ExtClient{
{
ClientID: "coolclient",
},
}
AddProNetDefaults(&network)
t.Run("Net Users initialized successfully", func(t *testing.T) {
err := InitializeNetworkUsers(network.NetID)
assert.Nil(t, err)
})
t.Run("Error when no network users", func(t *testing.T) {
user, err := GetNetworkUser(network.NetID, networkUser.ID)
assert.Nil(t, user)
assert.NotNil(t, err)
})
t.Run("Successful net user create", func(t *testing.T) {
DeleteNetworkUser(network.NetID, string(networkUser.ID))
err := CreateNetworkUser(&network, &networkUser)
assert.Nil(t, err)
user, err := GetNetworkUser(network.NetID, networkUser.ID)
assert.NotNil(t, user)
assert.Nil(t, err)
assert.Equal(t, 0, user.AccessLevel)
assert.Equal(t, 0, user.ClientLimit)
})
t.Run("Successful net user update", func(t *testing.T) {
networkUser.AccessLevel = 0
networkUser.ClientLimit = 1
err := UpdateNetworkUser(network.NetID, &networkUser)
assert.Nil(t, err)
user, err := GetNetworkUser(network.NetID, networkUser.ID)
assert.NotNil(t, user)
assert.Nil(t, err)
assert.Equal(t, 0, user.AccessLevel)
assert.Equal(t, 1, user.ClientLimit)
})
t.Run("Successful net user node isallowed", func(t *testing.T) {
networkUser.Nodes = append(networkUser.Nodes, nodes[0].ID.String())
err := UpdateNetworkUser(network.NetID, &networkUser)
assert.Nil(t, err)
isUserNodeAllowed := IsUserNodeAllowed(nodes[:], network.NetID, string(networkUser.ID), nodes[0].ID.String())
assert.True(t, isUserNodeAllowed)
})
t.Run("Successful net user node not allowed", func(t *testing.T) {
isUserNodeAllowed := IsUserNodeAllowed(nodes[:], network.NetID, string(networkUser.ID), "notanode")
assert.False(t, isUserNodeAllowed)
})
t.Run("Successful net user client isallowed", func(t *testing.T) {
networkUser.Clients = append(networkUser.Clients, "coolclient")
err := UpdateNetworkUser(network.NetID, &networkUser)
assert.Nil(t, err)
isUserClientAllowed := IsUserClientAllowed(clients[:], network.NetID, string(networkUser.ID), "coolclient")
assert.True(t, isUserClientAllowed)
})
t.Run("Successful net user client not allowed", func(t *testing.T) {
isUserClientAllowed := IsUserClientAllowed(clients[:], network.NetID, string(networkUser.ID), "notaclient")
assert.False(t, isUserClientAllowed)
})
t.Run("Successful net user delete", func(t *testing.T) {
err := DeleteNetworkUser(network.NetID, string(networkUser.ID))
assert.Nil(t, err)
user, err := GetNetworkUser(network.NetID, networkUser.ID)
assert.Nil(t, user)
assert.NotNil(t, err)
})
}

View file

@ -1,20 +0,0 @@
package pro
const (
// == NET ACCESS END == indicates access for system admin (control of netmaker)
// NET_ADMIN - indicates access for network admin (control of network)
NET_ADMIN = 0
// NODE_ACCESS - indicates access for
NODE_ACCESS = 1
// CLIENT_ACCESS - indicates access for network user (limited to nodes + ext clients)
CLIENT_ACCESS = 2
// NO_ACCESS - indicates user has no access to network
NO_ACCESS = 3
// == NET ACCESS END ==
// DEFAULT_ALLOWED_GROUPS - default user group for all networks
DEFAULT_ALLOWED_GROUPS = "*"
// DEFAULT_ALLOWED_USERS - default allowed users for a network
DEFAULT_ALLOWED_USERS = "*"
// DB_GROUPS_KEY - represents db groups
DB_GROUPS_KEY = "netmaker-groups"
)

View file

@ -1,80 +0,0 @@
package pro
import (
"encoding/json"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/models/promodels"
)
// InitializeGroups - initialize groups data structure if not present in the DB
func InitializeGroups() error {
if !DoesUserGroupExist(DEFAULT_ALLOWED_GROUPS) {
return InsertUserGroup(DEFAULT_ALLOWED_GROUPS)
}
return nil
}
// InsertUserGroup - inserts a group into the
func InsertUserGroup(groupName promodels.UserGroupName) error {
currentGroups, err := GetUserGroups()
if err != nil {
return err
}
currentGroups[groupName] = promodels.Void{}
newData, err := json.Marshal(&currentGroups)
if err != nil {
return err
}
return database.Insert(DB_GROUPS_KEY, string(newData), database.USER_GROUPS_TABLE_NAME)
}
// DeleteUserGroup - deletes a group from database
func DeleteUserGroup(groupName promodels.UserGroupName) error {
var newGroups promodels.UserGroups
currentGroupRecords, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, DB_GROUPS_KEY)
if err != nil && !database.IsEmptyRecord(err) {
return err
}
if err = json.Unmarshal([]byte(currentGroupRecords), &newGroups); err != nil {
return err
}
delete(newGroups, groupName)
newData, err := json.Marshal(&newGroups)
if err != nil {
return err
}
return database.Insert(DB_GROUPS_KEY, string(newData), database.USER_GROUPS_TABLE_NAME)
}
// GetUserGroups - get groups of users
func GetUserGroups() (promodels.UserGroups, error) {
var returnGroups promodels.UserGroups
groupsRecord, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, DB_GROUPS_KEY)
if err != nil {
if database.IsEmptyRecord(err) {
return make(promodels.UserGroups, 1), nil
}
return returnGroups, err
}
if err = json.Unmarshal([]byte(groupsRecord), &returnGroups); err != nil {
return returnGroups, err
}
return returnGroups, nil
}
// DoesUserGroupExist - checks if a user group exists
func DoesUserGroupExist(group promodels.UserGroupName) bool {
currentGroups, err := GetUserGroups()
if err != nil {
return true
}
for k := range currentGroups {
if k == group {
return true
}
}
return false
}

View file

@ -1,41 +0,0 @@
package pro
import (
"testing"
"github.com/gravitl/netmaker/models/promodels"
"github.com/stretchr/testify/assert"
)
func TestUserGroupLogic(t *testing.T) {
t.Run("User Groups initialized successfully", func(t *testing.T) {
err := InitializeGroups()
assert.Nil(t, err)
})
t.Run("Check for default group", func(t *testing.T) {
groups, err := GetUserGroups()
assert.Nil(t, err)
var hasdefault bool
for k := range groups {
if string(k) == DEFAULT_ALLOWED_GROUPS {
hasdefault = true
}
}
assert.True(t, hasdefault)
})
t.Run("User Groups created successfully", func(t *testing.T) {
err := InsertUserGroup(promodels.UserGroupName("group1"))
assert.Nil(t, err)
err = InsertUserGroup(promodels.UserGroupName("group2"))
assert.Nil(t, err)
})
t.Run("User Groups deleted successfully", func(t *testing.T) {
err := DeleteUserGroup(promodels.UserGroupName("group1"))
assert.Nil(t, err)
assert.False(t, DoesUserGroupExist(promodels.UserGroupName("group1")))
})
}

View file

@ -1,23 +1,16 @@
package logic
import (
"encoding/json"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
"github.com/gravitl/netmaker/servercfg"
)
const (
// ALL_NETWORK_ACCESS - represents all networks
ALL_NETWORK_ACCESS = "THIS_USER_HAS_ALL"
master_uname = "masteradministrator"
MasterUser = "masteradministrator"
Forbidden_Msg = "forbidden"
Forbidden_Err = models.Error(Forbidden_Msg)
Unauthorized_Msg = "unauthorized"
@ -28,152 +21,46 @@ const (
func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var errorResponse = models.ErrorResponse{
Code: http.StatusForbidden, Message: Forbidden_Msg,
}
r.Header.Set("ismaster", "no")
var params = mux.Vars(r)
bearerToken := r.Header.Get("Authorization")
// to have a custom DNS service adding entries
// we should refactor this, but is for the special case of an external service to query the DNS api
if strings.Contains(r.RequestURI, "/dns") && strings.ToUpper(r.Method) == "GET" && authenticateDNSToken(bearerToken) {
// do dns stuff
r.Header.Set("user", "nameserver")
networks, _ := json.Marshal([]string{ALL_NETWORK_ACCESS})
r.Header.Set("networks", string(networks))
next.ServeHTTP(w, r)
return
}
var networkName = params["networkname"]
if len(networkName) == 0 {
networkName = params["network"]
}
networks, username, err := UserPermissions(reqAdmin, networkName, bearerToken)
username, err := UserPermissions(reqAdmin, bearerToken)
if err != nil {
ReturnErrorResponse(w, r, errorResponse)
ReturnErrorResponse(w, r, FormatError(err, err.Error()))
return
}
// detect masteradmin
if len(networks) > 0 && networks[0] == ALL_NETWORK_ACCESS {
if username == MasterUser {
r.Header.Set("ismaster", "yes")
}
networksJson, err := json.Marshal(&networks)
if err != nil {
ReturnErrorResponse(w, r, errorResponse)
return
}
r.Header.Set("user", username)
r.Header.Set("networks", string(networksJson))
next.ServeHTTP(w, r)
}
}
// NetUserSecurityCheck - Check if network user has appropriate permissions
func NetUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var errorResponse = models.ErrorResponse{
Code: http.StatusForbidden, Message: Forbidden_Msg,
}
r.Header.Set("ismaster", "no")
var params = mux.Vars(r)
var netUserName = params["networkuser"]
var network = params["network"]
bearerToken := r.Header.Get("Authorization")
var tokenSplit = strings.Split(bearerToken, " ")
var authToken = ""
if len(tokenSplit) < 2 {
ReturnErrorResponse(w, r, errorResponse)
return
} else {
authToken = tokenSplit[1]
}
isMasterAuthenticated := authenticateMaster(authToken)
if isMasterAuthenticated {
r.Header.Set("user", "master token user")
r.Header.Set("ismaster", "yes")
next.ServeHTTP(w, r)
return
}
userName, _, isadmin, err := VerifyUserToken(authToken)
if err != nil {
ReturnErrorResponse(w, r, errorResponse)
return
}
r.Header.Set("user", userName)
if isadmin {
next.ServeHTTP(w, r)
return
}
if isNodes || isClients {
necessaryAccess := pro.NET_ADMIN
if isClients {
necessaryAccess = pro.CLIENT_ACCESS
}
if isNodes {
necessaryAccess = pro.NODE_ACCESS
}
u, err := pro.GetNetworkUser(network, promodels.NetworkUserID(userName))
if err != nil {
ReturnErrorResponse(w, r, errorResponse)
return
}
if u.AccessLevel > necessaryAccess {
ReturnErrorResponse(w, r, errorResponse)
return
}
} else if netUserName != userName {
ReturnErrorResponse(w, r, errorResponse)
return
}
next.ServeHTTP(w, r)
}
}
// UserPermissions - checks token stuff
func UserPermissions(reqAdmin bool, netname string, token string) ([]string, string, error) {
func UserPermissions(reqAdmin bool, token string) (string, error) {
var tokenSplit = strings.Split(token, " ")
var authToken = ""
userNetworks := []string{}
if len(tokenSplit) < 2 {
return userNetworks, "", Unauthorized_Err
return "", Unauthorized_Err
} else {
authToken = tokenSplit[1]
}
//all endpoints here require master so not as complicated
if authenticateMaster(authToken) {
// TODO log in as an actual admin user
return []string{ALL_NETWORK_ACCESS}, master_uname, nil
return MasterUser, nil
}
username, networks, isadmin, err := VerifyUserToken(authToken)
username, issuperadmin, isadmin, err := VerifyUserToken(authToken)
if err != nil {
return nil, username, Unauthorized_Err
return username, Unauthorized_Err
}
if !isadmin && reqAdmin {
return nil, username, Forbidden_Err
if reqAdmin && !(issuperadmin || isadmin) {
return username, Forbidden_Err
}
userNetworks = networks
if isadmin {
return []string{ALL_NETWORK_ACCESS}, username, nil
}
// check network admin access
if len(netname) > 0 && (len(userNetworks) == 0 || !authenticateNetworkUser(netname, userNetworks)) {
return nil, username, Forbidden_Err
}
if servercfg.IsPro && len(netname) > 0 && !pro.IsUserNetAdmin(netname, username) {
return nil, "", Forbidden_Err
}
return userNetworks, username, nil
return username, nil
}
// Consider a more secure way of setting master key
@ -181,23 +68,6 @@ func authenticateMaster(tokenString string) bool {
return tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != ""
}
func authenticateNetworkUser(network string, userNetworks []string) bool {
networkexists, err := NetworkExists(network)
if (err != nil && !database.IsEmptyRecord(err)) || !networkexists {
return false
}
return StringSliceContains(userNetworks, network)
}
// Consider a more secure way of setting master key
func authenticateDNSToken(tokenString string) bool {
tokens := strings.Split(tokenString, " ")
if len(tokens) < 2 {
return false
}
return len(servercfg.GetDNSKey()) > 0 && tokens[1] == servercfg.GetDNSKey()
}
func ContinueIfUserMatch(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var errorResponse = models.ErrorResponse{

View file

@ -2,13 +2,11 @@ package logic
import (
"encoding/json"
"errors"
"sort"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
)
// GetUser - gets a user
@ -43,64 +41,17 @@ func GetReturnUser(username string) (models.ReturnUser, error) {
// ToReturnUser - gets a user as a return user
func ToReturnUser(user models.User) models.ReturnUser {
return models.ReturnUser{
UserName: user.UserName,
Networks: user.Networks,
IsAdmin: user.IsAdmin,
Groups: user.Groups,
UserName: user.UserName,
IsSuperAdmin: user.IsSuperAdmin,
IsAdmin: user.IsAdmin,
RemoteGwIDs: user.RemoteGwIDs,
}
}
// GetGroupUsers - gets users in a group
func GetGroupUsers(group string) ([]models.ReturnUser, error) {
var returnUsers []models.ReturnUser
users, err := GetUsers()
if err != nil {
return returnUsers, err
}
for _, user := range users {
if StringSliceContains(user.Groups, group) {
users = append(users, user)
}
}
return users, err
}
// == PRO ==
// InitializeNetUsers - intializes network users for all users/networks
func InitializeNetUsers(network *models.Network) error {
// == add all current users to network as network users ==
currentUsers, err := GetUsers()
if err != nil {
return err
}
for i := range currentUsers { // add all users to given network
newUser := promodels.NetworkUser{
ID: promodels.NetworkUserID(currentUsers[i].UserName),
Clients: []string{},
Nodes: []string{},
AccessLevel: pro.NO_ACCESS,
ClientLimit: 0,
NodeLimit: 0,
}
if pro.IsUserAllowed(network, currentUsers[i].UserName, currentUsers[i].Groups) {
newUser.AccessLevel = network.ProSettings.DefaultAccessLevel
newUser.ClientLimit = network.ProSettings.DefaultUserClientLimit
newUser.NodeLimit = network.ProSettings.DefaultUserNodeLimit
}
if err = pro.CreateNetworkUser(network, &newUser); err != nil {
logger.Log(0, "failed to add network user settings to user", string(newUser.ID), "on network", network.NetID)
}
}
return nil
}
// SetUserDefaults - sets the defaults of a user to avoid empty fields
func SetUserDefaults(user *models.User) {
if user.Groups == nil {
user.Groups = []string{pro.DEFAULT_ALLOWED_GROUPS}
if user.RemoteGwIDs == nil {
user.RemoteGwIDs = make(map[string]struct{})
}
}
@ -110,3 +61,17 @@ func SortUsers(unsortedUsers []models.ReturnUser) {
return unsortedUsers[i].UserName < unsortedUsers[j].UserName
})
}
// GetSuperAdmin - fetches superadmin user
func GetSuperAdmin() (models.ReturnUser, error) {
users, err := GetUsers()
if err != nil {
return models.ReturnUser{}, err
}
for _, user := range users {
if user.IsSuperAdmin {
return user, nil
}
}
return models.ReturnUser{}, errors.New("superadmin not found")
}

View file

@ -19,7 +19,6 @@ import (
"github.com/gravitl/netmaker/functions"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/migrate"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/mq"
@ -83,10 +82,6 @@ func initialize() { // Client Mode Prereq Check
logic.SetJWTSecret()
if err = pro.InitializeGroups(); err != nil {
logger.Log(0, "could not initialize default user group, \"*\"")
}
err = logic.TimerCheckpoint()
if err != nil {
logger.Log(1, "Timer error occurred: ", err.Error())

View file

@ -5,12 +5,51 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"golang.org/x/exp/slog"
)
// Run - runs all migrations
func Run() {
updateEnrollmentKeys()
assignSuperAdmin()
}
func assignSuperAdmin() {
users, err := logic.GetUsers()
if err != nil || len(users) == 0 {
return
}
if ok, _ := logic.HasSuperAdmin(); ok {
return
}
createdSuperAdmin := false
for _, u := range users {
if u.IsAdmin {
user, err := logic.GetUser(u.UserName)
if err != nil {
slog.Error("error getting user", "user", u.UserName, "error", err.Error())
continue
}
user.IsSuperAdmin = true
user.IsAdmin = false
err = logic.UpsertUser(*user)
if err != nil {
slog.Error("error updating user to superadmin", "user", user.UserName, "error", err.Error())
continue
} else {
createdSuperAdmin = true
}
break
}
}
if !createdSuperAdmin {
slog.Error("failed to create superadmin!!")
}
}
func updateEnrollmentKeys() {

View file

@ -16,14 +16,16 @@ type ExtClient struct {
Enabled bool `json:"enabled" bson:"enabled"`
OwnerID string `json:"ownerid" bson:"ownerid"`
DeniedACLs map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
RemoteAccessClientID string `json:"remote_access_client_id"`
}
// CustomExtClient - struct for CustomExtClient params
type CustomExtClient struct {
ClientID string `json:"clientid,omitempty"`
PublicKey string `json:"publickey,omitempty"`
DNS string `json:"dns,omitempty"`
ExtraAllowedIPs []string `json:"extraallowedips,omitempty"`
Enabled bool `json:"enabled,omitempty"`
DeniedACLs map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
ClientID string `json:"clientid,omitempty"`
PublicKey string `json:"publickey,omitempty"`
DNS string `json:"dns,omitempty"`
ExtraAllowedIPs []string `json:"extraallowedips,omitempty"`
Enabled bool `json:"enabled,omitempty"`
DeniedACLs map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
RemoteAccessClientID string `json:"remote_access_client_id"`
}

View file

@ -2,30 +2,27 @@ package models
import (
"time"
"github.com/gravitl/netmaker/models/promodels"
)
// Network Struct - contains info for a given unique network
// At some point, need to replace all instances of Name with something else like Identifier
type Network struct {
AddressRange string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"`
AddressRange6 string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"`
NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"`
NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified"`
NetworkLastModified int64 `json:"networklastmodified" bson:"networklastmodified"`
DefaultInterface string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=35"`
DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
NodeLimit int32 `json:"nodelimit" bson:"nodelimit"`
DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"`
DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
DefaultMTU int32 `json:"defaultmtu" bson:"defaultmtu"`
DefaultACL string `json:"defaultacl" bson:"defaultacl" yaml:"defaultacl" validate:"checkyesorno"`
ProSettings *promodels.ProNetwork `json:"prosettings,omitempty" bson:"prosettings,omitempty" yaml:"prosettings,omitempty"`
AddressRange string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"`
AddressRange6 string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"`
NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"`
NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified"`
NetworkLastModified int64 `json:"networklastmodified" bson:"networklastmodified"`
DefaultInterface string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=35"`
DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
NodeLimit int32 `json:"nodelimit" bson:"nodelimit"`
DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"`
DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
DefaultMTU int32 `json:"defaultmtu" bson:"defaultmtu"`
DefaultACL string `json:"defaultacl" bson:"defaultacl" yaml:"defaultacl" validate:"checkyesorno"`
}
// SaveData - sensitive fields of a network that should be kept the same

View file

@ -1,37 +0,0 @@
package promodels
// NetworkUserID - ID field for a network user
type NetworkUserID string
// NetworkUser - holds fields for a network user
type NetworkUser struct {
AccessLevel int `json:"accesslevel" bson:"accesslevel" yaml:"accesslevel"`
ClientLimit int `json:"clientlimit" bson:"clientlimit" yaml:"clientlimit"`
NodeLimit int `json:"nodelimit" bson:"nodelimit" yaml:"nodelimit"`
ID NetworkUserID `json:"id" bson:"id" yaml:"id"`
Clients []string `json:"clients" bson:"clients" yaml:"clients"`
Nodes []string `json:"nodes" bson:"nodes" yaml:"nodes"`
}
// NetworkUserMap - map of network users
type NetworkUserMap map[NetworkUserID]NetworkUser
// NetworkUserMap.Delete - deletes a network user struct from a given map in memory
func (N NetworkUserMap) Delete(ID NetworkUserID) {
delete(N, ID)
}
// NetworkUserMap.Add - adds a network user struct to given network user map in memory
func (N NetworkUserMap) Add(User *NetworkUser) {
N[User.ID] = *User
}
// SetDefaults - adds the defaults to network user
func (U *NetworkUser) SetDefaults() {
if U.Clients == nil {
U.Clients = []string{}
}
if U.Nodes == nil {
U.Nodes = []string{}
}
}

View file

@ -1,10 +0,0 @@
package promodels
// ProNetwork - struct for all pro Network related fields
type ProNetwork struct {
DefaultAccessLevel int `json:"defaultaccesslevel" bson:"defaultaccesslevel" yaml:"defaultaccesslevel"`
DefaultUserNodeLimit int `json:"defaultusernodelimit" bson:"defaultusernodelimit" yaml:"defaultusernodelimit"`
DefaultUserClientLimit int `json:"defaultuserclientlimit" bson:"defaultuserclientlimit" yaml:"defaultuserclientlimit"`
AllowedUsers []string `json:"allowedusers" bson:"allowedusers" yaml:"allowedusers"`
AllowedGroups []string `json:"allowedgroups" bson:"allowedgroups" yaml:"allowedgroups"`
}

View file

@ -1,9 +0,0 @@
package promodels
type Void struct{}
// UserGroupName - string representing a group name
type UserGroupName string
// UserGroups - groups type, holds group names
type UserGroups map[UserGroupName]Void

View file

@ -24,19 +24,19 @@ type AuthParams struct {
// User struct - struct for Users
type User struct {
UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
Password string `json:"password" bson:"password" validate:"required,min=5"`
Networks []string `json:"networks" bson:"networks"`
IsAdmin bool `json:"isadmin" bson:"isadmin"`
Groups []string `json:"groups" bson:"groups" yaml:"groups"`
UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
Password string `json:"password" bson:"password" validate:"required,min=5"`
IsAdmin bool `json:"isadmin" bson:"isadmin"`
IsSuperAdmin bool `json:"issuperadmin"`
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"`
}
// ReturnUser - return user struct
type ReturnUser struct {
UserName string `json:"username" bson:"username"`
Networks []string `json:"networks" bson:"networks"`
IsAdmin bool `json:"isadmin" bson:"isadmin"`
Groups []string `json:"groups" bson:"groups"`
UserName string `json:"username"`
IsAdmin bool `json:"isadmin"`
IsSuperAdmin bool `json:"issuperadmin"`
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"`
}
// UserAuthParams - user auth params struct
@ -47,13 +47,33 @@ type UserAuthParams struct {
// UserClaims - user claims struct
type UserClaims struct {
IsAdmin bool
UserName string
Networks []string
Groups []string
IsAdmin bool
IsSuperAdmin bool
UserName string
jwt.RegisteredClaims
}
// IngressGwUsers - struct to hold users on a ingress gw
type IngressGwUsers struct {
NodeID string `json:"node_id"`
Network string `json:"network"`
Users []ReturnUser `json:"users"`
}
// UserRemoteGws - struct to hold user's remote gws
type UserRemoteGws struct {
GwID string `json:"remote_access_gw_id"`
GWName string `json:"gw_name"`
Network string `json:"network"`
Connected bool `json:"connected"`
GwClient ExtClient `json:"gw_client"`
}
// UserRemoteGwsReq - struct to hold user remote acccess gws req
type UserRemoteGwsReq struct {
RemoteAccessClientID string `json:"remote_access_clientid"`
}
// SuccessfulUserLoginResponse - successlogin struct
type SuccessfulUserLoginResponse struct {
UserName string

View file

@ -1,365 +0,0 @@
package controllers
import (
"encoding/json"
"errors"
"net/http"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/models/promodels"
)
func NetworkUsersHandlers(r *mux.Router) {
r.HandleFunc("/api/networkusers", logic.SecurityCheck(true, http.HandlerFunc(getAllNetworkUsers))).Methods(http.MethodGet)
r.HandleFunc("/api/networkusers/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkUsers))).Methods(http.MethodGet)
r.HandleFunc("/api/networkusers/{network}/{networkuser}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkUser))).Methods(http.MethodGet)
r.HandleFunc("/api/networkusers/{network}", logic.SecurityCheck(true, http.HandlerFunc(createNetworkUser))).Methods(http.MethodPost)
r.HandleFunc("/api/networkusers/{network}", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkUser))).Methods(http.MethodPut)
r.HandleFunc("/api/networkusers/data/{networkuser}/me", logic.NetUserSecurityCheck(false, false, http.HandlerFunc(getNetworkUserData))).Methods(http.MethodGet)
r.HandleFunc("/api/networkusers/{network}/{networkuser}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetworkUser))).Methods(http.MethodDelete)
}
// == RETURN TYPES ==
// NetworkName - represents a network name/ID
type NetworkName string
// NetworkUserDataMap - map of all data per network for a user
type NetworkUserDataMap map[NetworkName]NetworkUserData
// NetworkUserData - data struct for network users
type NetworkUserData struct {
Nodes []models.Node `json:"nodes" bson:"nodes" yaml:"nodes"`
Clients []models.ExtClient `json:"clients" bson:"clients" yaml:"clients"`
Vpn []models.Node `json:"vpns" bson:"vpns" yaml:"vpns"`
Networks []models.Network `json:"networks" bson:"networks" yaml:"networks"`
User promodels.NetworkUser `json:"user" bson:"user" yaml:"user"`
}
// == END RETURN TYPES ==
// returns a map of a network user's data across all networks
func getNetworkUserData(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
networkUserName := params["networkuser"]
logger.Log(1, r.Header.Get("user"), "requested fetching network user data for user", networkUserName)
networks, err := logic.GetNetworks()
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if networkUserName == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("netuserToGet"), "badrequest"))
return
}
u, err := logic.GetUser(networkUserName)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("could not find user"), "badrequest"))
return
}
// initialize the return data of network users
returnData := make(NetworkUserDataMap)
// go through each network and get that user's data
// if user has no access, give no data
// if user is a net admin, give all nodes
// if user has node access, give user's nodes if any
// if user has client access, git user's clients if any
for i := range networks {
netID := networks[i].NetID
newData := NetworkUserData{
Nodes: []models.Node{},
Clients: []models.ExtClient{},
Vpn: []models.Node{},
Networks: []models.Network{},
}
netUser, err := pro.GetNetworkUser(netID, promodels.NetworkUserID(networkUserName))
// check if user has access
if err == nil && netUser.AccessLevel != pro.NO_ACCESS {
newData.User = promodels.NetworkUser{
AccessLevel: netUser.AccessLevel,
ClientLimit: netUser.ClientLimit,
NodeLimit: netUser.NodeLimit,
Nodes: netUser.Nodes,
Clients: netUser.Clients,
}
newData.User.SetDefaults()
// check network level permissions
if doesNetworkAllow := pro.IsUserAllowed(&networks[i], networkUserName, u.Groups); doesNetworkAllow || netUser.AccessLevel == pro.NET_ADMIN {
netNodes, err := logic.GetNetworkNodes(netID)
if err != nil {
if database.IsEmptyRecord(err) && netUser.AccessLevel == pro.NET_ADMIN {
newData.Networks = append(newData.Networks, networks[i])
} else {
logger.Log(0, "failed to retrieve nodes on network", netID, "for user", string(netUser.ID))
}
} else {
if netUser.AccessLevel <= pro.NODE_ACCESS { // handle nodes
// if access level is NODE_ACCESS, filter nodes
if netUser.AccessLevel == pro.NODE_ACCESS {
for i := range netNodes {
if logic.StringSliceContains(netUser.Nodes, netNodes[i].ID.String()) {
newData.Nodes = append(newData.Nodes, netNodes[i])
}
}
} else { // net admin so, get all nodes and ext clients on network...
newData.Nodes = netNodes
for i := range netNodes {
if netNodes[i].IsIngressGateway {
newData.Vpn = append(newData.Vpn, netNodes[i])
if clients, err := logic.GetExtClientsByID(netNodes[i].ID.String(), netID); err == nil {
newData.Clients = append(newData.Clients, clients...)
}
}
}
newData.Networks = append(newData.Networks, networks[i])
}
}
if netUser.AccessLevel <= pro.CLIENT_ACCESS && netUser.AccessLevel != pro.NET_ADMIN {
for _, c := range netUser.Clients {
if client, err := logic.GetExtClient(c, netID); err == nil {
newData.Clients = append(newData.Clients, client)
}
}
for i := range netNodes {
if netNodes[i].IsIngressGateway {
newData.Vpn = append(newData.Vpn, netNodes[i])
}
}
}
}
}
returnData[NetworkName(netID)] = newData
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(returnData)
}
// returns a map of all network users mapped to each network
func getAllNetworkUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
logger.Log(1, r.Header.Get("user"), "requested fetching all network users")
type allNetworkUsers = map[string][]promodels.NetworkUser
networks, err := logic.GetNetworks()
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
var allNetUsers = make(allNetworkUsers, len(networks))
for i := range networks {
netusers, err := pro.GetNetworkUsers(networks[i].NetID)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, v := range netusers {
allNetUsers[networks[i].NetID] = append(allNetUsers[networks[i].NetID], v)
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(allNetUsers)
}
func getNetworkUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
netname := params["network"]
logger.Log(1, r.Header.Get("user"), "requested fetching network users for network", netname)
_, err := logic.GetNetwork(netname)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
netusers, err := pro.GetNetworkUsers(netname)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(netusers)
}
func getNetworkUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
netname := params["network"]
logger.Log(1, r.Header.Get("user"), "requested fetching network user", params["networkuser"], "on network", netname)
_, err := logic.GetNetwork(netname)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
netuserToGet := params["networkuser"]
if netuserToGet == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("netuserToGet"), "badrequest"))
return
}
netuser, err := pro.GetNetworkUser(netname, promodels.NetworkUserID(netuserToGet))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(netuser)
}
func createNetworkUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
netname := params["network"]
logger.Log(1, r.Header.Get("user"), "requested creating a network user on network", netname)
network, err := logic.GetNetwork(netname)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
var networkuser promodels.NetworkUser
// we decode our body request params
err = json.NewDecoder(r.Body).Decode(&networkuser)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
err = pro.CreateNetworkUser(&network, &networkuser)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
w.WriteHeader(http.StatusOK)
}
func updateNetworkUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
netname := params["network"]
logger.Log(1, r.Header.Get("user"), "requested updating a network user on network", netname)
network, err := logic.GetNetwork(netname)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
var networkuser promodels.NetworkUser
// we decode our body request params
err = json.NewDecoder(r.Body).Decode(&networkuser)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if networkuser.ID == "" || !pro.DoesNetworkUserExist(netname, networkuser.ID) {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest"))
return
}
if networkuser.AccessLevel < pro.NET_ADMIN || networkuser.AccessLevel > pro.NO_ACCESS {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid user access level provided"), "badrequest"))
return
}
if networkuser.ClientLimit < 0 || networkuser.NodeLimit < 0 {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("negative user limit provided"), "badrequest"))
return
}
u, err := logic.GetUser(string(networkuser.ID))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid user "+string(networkuser.ID)), "badrequest"))
return
}
if !pro.IsUserAllowed(&network, u.UserName, u.Groups) {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user must be in allowed groups or users"), "badrequest"))
return
}
if networkuser.AccessLevel == pro.NET_ADMIN {
currentUser, err := logic.GetUser(string(networkuser.ID))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user model not found for "+string(networkuser.ID)), "badrequest"))
return
}
if !logic.StringSliceContains(currentUser.Networks, netname) {
// append network name to user model to conform to old model
if err = logic.UpdateUserNetworks(
append(currentUser.Networks, netname),
currentUser.Groups,
currentUser.IsAdmin,
&models.ReturnUser{
Groups: currentUser.Groups,
IsAdmin: currentUser.IsAdmin,
Networks: currentUser.Networks,
UserName: currentUser.UserName,
},
); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user model failed net admin update "+string(networkuser.ID)+" (are they an admin?"), "badrequest"))
return
}
}
}
err = pro.UpdateNetworkUser(netname, &networkuser)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
w.WriteHeader(http.StatusOK)
}
func deleteNetworkUser(w http.ResponseWriter, r *http.Request) {
var params = mux.Vars(r)
netname := params["network"]
logger.Log(1, r.Header.Get("user"), "requested deleting network user", params["networkuser"], "on network", netname)
_, err := logic.GetNetwork(netname)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
netuserToDelete := params["networkuser"]
if netuserToDelete == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("no group name provided"), "badrequest"))
return
}
if err := pro.DeleteNetworkUser(netname, netuserToDelete); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
w.WriteHeader(http.StatusOK)
}

View file

@ -1,73 +0,0 @@
package controllers
import (
"encoding/json"
"errors"
"net/http"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/logic/pro"
"github.com/gravitl/netmaker/models/promodels"
)
func UserGroupsHandlers(r *mux.Router) {
r.HandleFunc("/api/usergroups", logic.SecurityCheck(true, http.HandlerFunc(getUserGroups))).Methods(http.MethodGet)
r.HandleFunc("/api/usergroups/{usergroup}", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost)
r.HandleFunc("/api/usergroups/{usergroup}", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete)
}
func getUserGroups(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
logger.Log(1, r.Header.Get("user"), "requested fetching user groups")
userGroups, err := pro.GetUserGroups()
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
// Returns all the groups in JSON format
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(userGroups)
}
func createUserGroup(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
newGroup := params["usergroup"]
logger.Log(1, r.Header.Get("user"), "requested creating user group", newGroup)
if newGroup == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("no group name provided"), "badrequest"))
return
}
err := pro.InsertUserGroup(promodels.UserGroupName(newGroup))
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
w.WriteHeader(http.StatusOK)
}
func deleteUserGroup(w http.ResponseWriter, r *http.Request) {
var params = mux.Vars(r)
groupToDelete := params["usergroup"]
logger.Log(1, r.Header.Get("user"), "requested deleting user group", groupToDelete)
if groupToDelete == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("no group name provided"), "badrequest"))
return
}
if err := pro.DeleteUserGroup(promodels.UserGroupName(groupToDelete)); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
w.WriteHeader(http.StatusOK)
}

268
pro/controllers/users.go Normal file
View file

@ -0,0 +1,268 @@
package controllers
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"golang.org/x/exp/slog"
)
func UserHandlers(r *mux.Router) {
r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(attachUserToRemoteAccessGw))).Methods(http.MethodPost)
r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(removeUserFromRemoteAccessGW))).Methods(http.MethodDelete)
r.HandleFunc("/api/users/{username}/remote_access_gw", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserRemoteAccessGws)))).Methods(http.MethodGet)
r.HandleFunc("/api/users/ingress/{ingress_id}", logic.SecurityCheck(true, http.HandlerFunc(ingressGatewayUsers))).Methods(http.MethodGet)
}
// swagger:route POST /api/users/{username}/remote_access_gw user attachUserToRemoteAccessGateway
//
// Attach User to a remote access gateway.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func attachUserToRemoteAccessGw(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
username := params["username"]
remoteGwID := params["remote_access_gateway_id"]
if username == "" || remoteGwID == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params `username` and `remote_access_gateway_id`"), "badrequest"))
return
}
user, err := logic.GetUser(username)
if err != nil {
slog.Error("failed to fetch user: ", "username", username, "error", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest"))
return
}
if user.IsAdmin || user.IsSuperAdmin {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("superadmins/admins have access to all gateways"), "badrequest"))
return
}
node, err := logic.GetNodeByID(remoteGwID)
if err != nil {
slog.Error("failed to fetch gateway node", "nodeID", remoteGwID, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch remote access gateway node, error: %v", err), "badrequest"))
return
}
if !node.IsIngressGateway {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("node is not a remote access gateway"), "badrequest"))
return
}
if user.RemoteGwIDs == nil {
user.RemoteGwIDs = make(map[string]struct{})
}
user.RemoteGwIDs[node.ID.String()] = struct{}{}
err = logic.UpsertUser(*user)
if err != nil {
slog.Error("failed to update user's gateways", "user", username, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch remote access gateway node,error: %v", err), "badrequest"))
return
}
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
// swagger:route DELETE /api/users/{username}/remote_access_gw user removeUserFromRemoteAccessGW
//
// Attach User to a remote access gateway.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func removeUserFromRemoteAccessGW(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
username := params["username"]
remoteGwID := params["remote_access_gateway_id"]
if username == "" || remoteGwID == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params `username` and `remote_access_gateway_id`"), "badrequest"))
return
}
user, err := logic.GetUser(username)
if err != nil {
logger.Log(0, username, "failed to fetch user: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest"))
return
}
delete(user.RemoteGwIDs, remoteGwID)
go func(user models.User, remoteGwID string) {
extclients, err := logic.GetAllExtClients()
if err != nil {
slog.Error("failed to fetch extclients", "error", err)
return
}
for _, extclient := range extclients {
if extclient.OwnerID == user.UserName && remoteGwID == extclient.IngressGatewayID {
logic.DeleteExtClient(extclient.Network, extclient.ClientID)
}
}
}(*user, remoteGwID)
err = logic.UpsertUser(*user)
if err != nil {
slog.Error("failed to update user gateways", "user", username, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to fetch remote access gaetway node "+err.Error()), "badrequest"))
return
}
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
// swagger:route GET "/api/users/{username}/remote_access_gw" nodes getUserRemoteAccessGws
//
// Get an individual node.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: nodeResponse
func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
username := params["username"]
if username == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params username"), "badrequest"))
return
}
var req models.UserRemoteGwsReq
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
slog.Error("error decoding request body: ", "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
if req.RemoteAccessClientID == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("remote access client id cannot be empty"), "badrequest"))
return
}
userGws := make(map[string][]models.UserRemoteGws)
user, err := logic.GetUser(username)
if err != nil {
logger.Log(0, username, "failed to fetch user: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest"))
return
}
if user.IsAdmin || user.IsSuperAdmin {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("admins can visit dashboard to create remote clients"), "badrequest"))
return
}
allextClients, err := logic.GetAllExtClients()
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, extClient := range allextClients {
if extClient.RemoteAccessClientID == req.RemoteAccessClientID && extClient.OwnerID == username {
node, err := logic.GetNodeByID(extClient.IngressGatewayID)
if err != nil {
continue
}
if node.PendingDelete {
continue
}
host, err := logic.GetHost(node.HostID.String())
if err != nil {
continue
}
if _, ok := user.RemoteGwIDs[node.ID.String()]; ok {
gws := userGws[node.Network]
gws = append(gws, models.UserRemoteGws{
GwID: node.ID.String(),
GWName: host.Name,
Network: node.Network,
GwClient: extClient,
Connected: true,
})
userGws[node.Network] = gws
delete(user.RemoteGwIDs, node.ID.String())
}
}
}
// add remaining gw nodes to resp
for gwID := range user.RemoteGwIDs {
node, err := logic.GetNodeByID(gwID)
if err != nil {
continue
}
if node.PendingDelete {
continue
}
host, err := logic.GetHost(node.HostID.String())
if err != nil {
continue
}
gws := userGws[node.Network]
gws = append(gws, models.UserRemoteGws{
GwID: node.ID.String(),
GWName: host.Name,
Network: node.Network,
})
userGws[node.Network] = gws
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(userGws)
}
// swagger:route GET /api/nodes/{network}/{nodeid}/ingress/users users ingressGatewayUsers
//
// Lists all the users attached to an ingress gateway.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: nodeResponse
func ingressGatewayUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
ingressID := params["ingress_id"]
node, err := logic.GetNodeByID(ingressID)
if err != nil {
slog.Error("failed to get ingress node", "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
gwUsers, err := logic.GetIngressGwUsers(node)
if err != nil {
slog.Error("failed to get users on ingress gateway", "nodeid", ingressID, "network", node.Network, "user", r.Header.Get("user"),
"error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(gwUsers)
}

View file

@ -25,9 +25,8 @@ func InitPro() {
controller.HttpHandlers = append(
controller.HttpHandlers,
proControllers.MetricHandlers,
proControllers.NetworkUsersHandlers,
proControllers.UserGroupsHandlers,
proControllers.RelayHandlers,
proControllers.UserHandlers,
)
logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
// == License Handling ==

View file

@ -320,17 +320,6 @@ func GetMasterKey() string {
return key
}
// GetDNSKey - gets the configured dns key of server
func GetDNSKey() string {
key := ""
if os.Getenv("DNS_KEY") != "" {
key = os.Getenv("DNS_KEY")
} else if config.Config.Server.DNSKey != "" {
key = config.Config.Server.DNSKey
}
return key
}
// GetAllowedOrigin - get the allowed origin
func GetAllowedOrigin() string {
allowedorigin := "*"

View file

@ -8,7 +8,7 @@ import (
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/acls"
"github.com/gravitl/netmaker/logic/acls/nodeacls"
"github.com/gravitl/netmaker/logic/pro"
"golang.org/x/exp/slog"
)
const (
@ -59,10 +59,6 @@ func setNetworkDefaults() error {
return err
}
for _, network := range networks {
if err = pro.InitializeNetworkUsers(network.NetID); err != nil {
logger.Log(0, "could not initialize NetworkUsers on network", network.NetID)
}
pro.AddProNetDefaults(&network)
update := false
newNet := network
if strings.Contains(network.NetID, ".") {
@ -85,7 +81,7 @@ func setNetworkDefaults() error {
}
} else {
network.SetDefaults()
_, _, _, _, _, err = logic.UpdateNetwork(&network, &network)
_, _, _, err = logic.UpdateNetwork(&network, &network)
if err != nil {
logger.Log(0, "could not set defaults on network", network.NetID)
}
@ -102,13 +98,12 @@ func setUserDefaults() error {
for _, user := range users {
updateUser, err := logic.GetUser(user.UserName)
if err != nil {
logger.Log(0, "could not update user", updateUser.UserName)
slog.Error("could not get user", "user", updateUser.UserName, "error", err.Error())
}
logic.SetUserDefaults(updateUser)
copyUser := updateUser
copyUser.Password = ""
if _, err = logic.UpdateUser(copyUser, updateUser); err != nil {
logger.Log(0, "could not update user", updateUser.UserName)
err = logic.UpsertUser(*updateUser)
if err != nil {
slog.Error("could not update user", "user", updateUser.UserName, "error", err.Error())
}
}
return nil