netmaker/migrate/migrate.go
Abhishek K 2e8d95e80e
NET-1227: User Mgmt V2 (#3055)
* user mgmt models

* define user roles

* define models for new user mgmt and groups

* oauth debug log

* initialize user role after db conn

* print oauth token in debug log

* user roles CRUD apis

* user groups CRUD Apis

* additional api checks

* add additional scopes

* add additional scopes url

* add additional scopes url

* rm additional scopes url

* setup middlleware permission checks

* integrate permission check into middleware

* integrate permission check into middleware

* check for headers for subjects

* refactor user role models

* refactor user groups models

* add new user to pending user via RAC login

* untracked

* allow multiple groups for an user

* change json tag

* add debug headers

* refer network controls form roles, add debug headers

* refer network controls form roles, add debug headers

* replace auth checks, add network id to role model

* nodes handler

* migration funcs

* invoke sync users migration func

* add debug logs

* comment middleware

* fix get all nodes api

* add debug logs

* fix middleware error nil check

* add new func to get username from jwt

* fix jwt parsing

* abort on error

* allow multiple network roles

* allow multiple network roles

* add migration func

* return err if jwt parsing fails

* set global check to true when accessing user apis

* set netid for acls api calls

* set netid for acls api calls

* update role and groups routes

* add validation checks

* add invite flow apis and magic links

* add invited user via oauth signup automatically

* create invited user on oauth signup, with groups in the invite

* add group validation for user invite

* update create user handler with new role mgmt

* add validation checks

* create user invites tables

* add error logging for email invite

* fix invite singup url

* debug log

* get query params from url

* get query params from url

* add query escape

* debug log

* debug log

* fix user signup via invite api

* set admin field for backward compatbility

* use new role id for user apis

* deprecate use of old admin fields

* deprecate usage of old user fields

* add user role as service user if empty

* setup email sender

* delete invite after user singup

* add plaform user role

* redirect on invite verification link

* fix invite redirect

* temporary redirect

* fix invite redirect

* point invite link to frontend

* fix query params lookup

* add resend support, configure email interface types

* fix groups and user creation

* validate user groups, add check for metrics api in middleware

* add invite url to invite model

* migrate rac apis to new user mgmt

* handle network nodes

* add platform user to default role

* fix user role migration

* add default on rag creation and cleanup after deletion

* fix rac apis

* change to invite code param

* filter nodes and hosts based on user network access

* extend create user group req to accomodate users

* filter network based on user access

* format oauth error

* move user roles and groups

* fix get user v1 api

* move user mgmt func to pro

* add user auth type to user model

* fix roles init

* remove platform role from group object

* list only platform roles

* add network roles to invite req

* create default groups and roles

* fix middleware for global access

* create default role

* fix nodes filter with global network roles

* block selfupdate of groups and network roles

* delete netID if net roles are empty

* validate user roles nd groups on update

* set extclient permission scope when rag vpn access is set

* allow deletion of roles and groups

* replace _ with - in role naming convention

* fix failover middleware mgmt

* format oauth templates

* fetch route temaplate

* return err if user wrong login type

* check user groups on rac apis

* fix rac apis

* fix resp msg

* add validation checks for admin invite

* return oauth type

* format group err msg

* fix html tag

* clean up default groups

* create default rag role

* add UI name to roles

* remove default net group from user when deleted

* reorder migration funcs

* fix duplicacy of hosts

* check old field for migration

* from pro to ce make all secondary users admins

* from pro to ce make all secondary users admins

* revert: from pro to ce make all secondary users admins

* make sure downgrades work

* fix pending users approval

* fix duplicate hosts

* fix duplicate hosts entries

* fix cache reference issue

* feat: configure FRONTEND_URL during installation

* disable user vpn access when network roles are modified

* rm vpn acces when roles or groups are deleted

* add http to frontend url

* revert crypto version

* downgrade crytpo version

* add platform id check on user invites

---------

Co-authored-by: the_aceix <aceixsmartx@gmail.com>
2024-08-20 17:08:56 +05:30

420 lines
11 KiB
Go

package migrate
import (
"encoding/json"
"fmt"
"log"
"time"
"golang.org/x/exp/slog"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/logic/acls"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/mq"
"github.com/gravitl/netmaker/servercfg"
)
// Run - runs all migrations
func Run() {
updateEnrollmentKeys()
assignSuperAdmin()
syncUsers()
updateHosts()
updateNodes()
updateAcls()
}
func assignSuperAdmin() {
users, err := logic.GetUsers()
if err != nil || len(users) == 0 {
return
}
if ok, _ := logic.HasSuperAdmin(); ok {
return
}
createdSuperAdmin := false
owner := servercfg.GetOwnerEmail()
if owner != "" {
user, err := logic.GetUser(owner)
if err != nil {
log.Fatal("error getting user", "user", owner, "error", err.Error())
}
user.PlatformRoleID = models.SuperAdminRole
err = logic.UpsertUser(*user)
if err != nil {
log.Fatal(
"error updating user to superadmin",
"user",
user.UserName,
"error",
err.Error(),
)
}
return
}
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.PlatformRoleID = models.SuperAdminRole
user.IsSuperAdmin = true
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() {
rows, err := database.FetchRecords(database.ENROLLMENT_KEYS_TABLE_NAME)
if err != nil {
return
}
for _, row := range rows {
var key models.EnrollmentKey
if err = json.Unmarshal([]byte(row), &key); err != nil {
continue
}
if key.Type != models.Undefined {
logger.Log(2, "migration: enrollment key type already set")
continue
} else {
logger.Log(2, "migration: updating enrollment key type")
if key.Unlimited {
key.Type = models.Unlimited
} else if key.UsesRemaining > 0 {
key.Type = models.Uses
} else if !key.Expiration.IsZero() {
key.Type = models.TimeExpiration
}
}
data, err := json.Marshal(key)
if err != nil {
logger.Log(0, "migration: marshalling enrollment key: "+err.Error())
continue
}
if err = database.Insert(key.Value, string(data), database.ENROLLMENT_KEYS_TABLE_NAME); err != nil {
logger.Log(0, "migration: inserting enrollment key: "+err.Error())
continue
}
}
}
func updateHosts() {
rows, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
if err != nil {
logger.Log(0, "failed to fetch database records for hosts")
}
for _, row := range rows {
var host models.Host
if err := json.Unmarshal([]byte(row), &host); err != nil {
logger.Log(0, "failed to unmarshal database row to host", "row", row)
continue
}
if host.PersistentKeepalive == 0 {
host.PersistentKeepalive = models.DefaultPersistentKeepAlive
if err := logic.UpsertHost(&host); err != nil {
logger.Log(0, "failed to upsert host", host.ID.String())
continue
}
}
}
}
func updateNodes() {
nodes, err := logic.GetAllNodes()
if err != nil {
slog.Error("migration failed for nodes", "error", err)
return
}
for _, node := range nodes {
if node.IsEgressGateway {
egressRanges, update := removeInterGw(node.EgressGatewayRanges)
if update {
node.EgressGatewayRequest.Ranges = egressRanges
node.EgressGatewayRanges = egressRanges
logic.UpsertNode(&node)
}
}
}
}
func removeInterGw(egressRanges []string) ([]string, bool) {
update := false
for i := len(egressRanges) - 1; i >= 0; i-- {
if egressRanges[i] == "0.0.0.0/0" || egressRanges[i] == "::/0" {
update = true
egressRanges = append(egressRanges[:i], egressRanges[i+1:]...)
}
}
return egressRanges, update
}
func updateAcls() {
// get all networks
networks, err := logic.GetNetworks()
if err != nil && !database.IsEmptyRecord(err) {
slog.Error("acls migration failed. error getting networks", "error", err)
return
}
// get current acls per network
for _, network := range networks {
var networkAcl acls.ACLContainer
networkAcl, err := networkAcl.Get(acls.ContainerID(network.NetID))
if err != nil {
if database.IsEmptyRecord(err) {
continue
}
slog.Error(fmt.Sprintf("error during acls migration. error getting acls for network: %s", network.NetID), "error", err)
continue
}
// convert old acls to new acls with clients
// TODO: optimise O(n^2) operation
clients, err := logic.GetNetworkExtClients(network.NetID)
if err != nil {
slog.Error(fmt.Sprintf("error during acls migration. error getting clients for network: %s", network.NetID), "error", err)
continue
}
clientsIdMap := make(map[string]struct{})
for _, client := range clients {
clientsIdMap[client.ClientID] = struct{}{}
}
nodeIdsMap := make(map[string]struct{})
for nodeId := range networkAcl {
nodeIdsMap[string(nodeId)] = struct{}{}
}
/*
initially, networkACL has only node acls so we add client acls to it
final shape:
{
"node1": {
"node2": 2,
"client1": 2,
"client2": 1,
},
"node2": {
"node1": 2,
"client1": 2,
"client2": 1,
},
"client1": {
"node1": 2,
"node2": 2,
"client2": 1,
},
"client2": {
"node1": 1,
"node2": 1,
"client1": 1,
},
}
*/
for _, client := range clients {
networkAcl[acls.AclID(client.ClientID)] = acls.ACL{}
// add client values to node acls and create client acls with node values
for id, nodeAcl := range networkAcl {
// skip if not a node
if _, ok := nodeIdsMap[string(id)]; !ok {
continue
}
if nodeAcl == nil {
slog.Warn("acls migration bad data: nil node acl", "node", id, "network", network.NetID)
continue
}
nodeAcl[acls.AclID(client.ClientID)] = acls.Allowed
networkAcl[acls.AclID(client.ClientID)][id] = acls.Allowed
if client.DeniedACLs == nil {
continue
} else if _, ok := client.DeniedACLs[string(id)]; ok {
nodeAcl[acls.AclID(client.ClientID)] = acls.NotAllowed
networkAcl[acls.AclID(client.ClientID)][id] = acls.NotAllowed
}
}
// add clients to client acls response
for _, c := range clients {
if c.ClientID == client.ClientID {
continue
}
networkAcl[acls.AclID(client.ClientID)][acls.AclID(c.ClientID)] = acls.Allowed
if client.DeniedACLs == nil {
continue
} else if _, ok := client.DeniedACLs[c.ClientID]; ok {
networkAcl[acls.AclID(client.ClientID)][acls.AclID(c.ClientID)] = acls.NotAllowed
}
}
// delete oneself from its own acl
delete(networkAcl[acls.AclID(client.ClientID)], acls.AclID(client.ClientID))
}
// remove non-existent client and node acls
for objId := range networkAcl {
if _, ok := nodeIdsMap[string(objId)]; ok {
continue
}
if _, ok := clientsIdMap[string(objId)]; ok {
continue
}
// remove all occurances of objId from all acls
for objId2 := range networkAcl {
delete(networkAcl[objId2], objId)
}
delete(networkAcl, objId)
}
// save new acls
slog.Debug(fmt.Sprintf("(migration) saving new acls for network: %s", network.NetID), "networkAcl", networkAcl)
if _, err := networkAcl.Save(acls.ContainerID(network.NetID)); err != nil {
slog.Error(fmt.Sprintf("error during acls migration. error saving new acls for network: %s", network.NetID), "error", err)
continue
}
slog.Info(fmt.Sprintf("(migration) successfully saved new acls for network: %s", network.NetID))
}
}
func MigrateEmqx() {
err := mq.SendPullSYN()
if err != nil {
logger.Log(0, "failed to send pull syn to clients", "error", err.Error())
}
time.Sleep(time.Second * 3)
slog.Info("proceeding to kicking out clients from emqx")
err = mq.KickOutClients()
if err != nil {
logger.Log(2, "failed to migrate emqx: ", "kickout-error", err.Error())
}
}
func syncUsers() {
// create default network user roles for existing networks
if servercfg.IsPro {
networks, _ := logic.GetNetworks()
nodes, err := logic.GetAllNodes()
if err == nil {
for _, netI := range networks {
networkNodes := logic.GetNetworkNodesMemory(nodes, netI.NetID)
for _, networkNodeI := range networkNodes {
if networkNodeI.IsIngressGateway {
h, err := logic.GetHost(networkNodeI.HostID.String())
if err == nil {
logic.CreateRole(models.UserRolePermissionTemplate{
ID: models.GetRAGRoleID(networkNodeI.Network, h.ID.String()),
UiName: models.GetRAGRoleName(networkNodeI.Network, h.Name),
NetworkID: models.NetworkID(netI.NetID),
NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{
models.RemoteAccessGwRsrc: {
models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{
Read: true,
VPNaccess: true,
},
},
models.ExtClientsRsrc: {
models.AllExtClientsRsrcID: models.RsrcPermissionScope{
Read: true,
Create: true,
Update: true,
Delete: true,
SelfOnly: true,
},
},
},
})
}
}
}
}
}
}
users, err := logic.GetUsersDB()
if err == nil {
for _, user := range users {
user := user
if user.PlatformRoleID == models.AdminRole && !user.IsAdmin {
user.IsAdmin = true
logic.UpsertUser(user)
}
if user.PlatformRoleID == models.SuperAdminRole && !user.IsSuperAdmin {
user.IsSuperAdmin = true
logic.UpsertUser(user)
}
if user.PlatformRoleID.String() != "" {
continue
}
user.AuthType = models.BasicAuth
if logic.IsOauthUser(&user) == nil {
user.AuthType = models.OAuth
}
if len(user.NetworkRoles) == 0 {
user.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
}
if len(user.UserGroups) == 0 {
user.UserGroups = make(map[models.UserGroupID]struct{})
}
if user.IsSuperAdmin {
user.PlatformRoleID = models.SuperAdminRole
} else if user.IsAdmin {
user.PlatformRoleID = models.AdminRole
} else {
user.PlatformRoleID = models.ServiceUser
}
logic.UpsertUser(user)
if len(user.RemoteGwIDs) > 0 {
// define user roles for network
// assign relevant network role to user
for remoteGwID := range user.RemoteGwIDs {
gwNode, err := logic.GetNodeByID(remoteGwID)
if err != nil {
continue
}
h, err := logic.GetHost(gwNode.HostID.String())
if err != nil {
continue
}
r, err := logic.GetRole(models.GetRAGRoleID(gwNode.Network, h.ID.String()))
if err != nil {
continue
}
if netRoles, ok := user.NetworkRoles[models.NetworkID(gwNode.Network)]; ok {
netRoles[r.ID] = struct{}{}
} else {
user.NetworkRoles[models.NetworkID(gwNode.Network)] = map[models.UserRoleID]struct{}{
r.ID: {},
}
}
}
logic.UpsertUser(user)
}
}
}
}