NM-61: User group ACL fixes (#3546)

* feat(go): create default acl only for networks that are part of the group;

* feat(go): update acls on user group update and delete;

* feat(go): add migration for existing acls.

* feat(go): check for network roles in migration.
This commit is contained in:
Vishal Dalwadi 2025-08-08 22:17:39 +05:30 committed by GitHub
parent 996410fc61
commit e4da84aa85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 186 additions and 6 deletions

View file

@ -1263,6 +1263,19 @@ func ValidateCreateAclReq(req models.Acl) error {
// if err != nil {
// return err
// }
for _, src := range req.Src {
if src.ID == models.UserGroupAclID {
userGroup, err := GetUserGroup(models.UserGroupID(src.Value))
if err != nil {
return err
}
_, ok := userGroup.NetworkRoles[req.NetworkID]
if !ok {
return fmt.Errorf("user group %s does not have access to network %s", src.Value, req.NetworkID)
}
}
}
return nil
}

View file

@ -95,6 +95,7 @@ var CreateDefaultUserPolicies = func(netID models.NetworkID) {
InsertAcl(defaultUserAcl)
}
}
var ListUserGroups = func() ([]models.UserGroup, error) { return nil, nil }
var GetUserGroupsInNetwork = func(netID models.NetworkID) (networkGrps map[models.UserGroupID]models.UserGroup) { return }
var GetUserGroup = func(groupId models.UserGroupID) (userGrps models.UserGroup, err error) { return }
var AddGlobalNetRolesToAdmins = func(u *models.User) {}

View file

@ -35,6 +35,7 @@ func Run() {
updateHosts()
updateNodes()
updateAcls()
updateNewAcls()
logic.MigrateToGws()
migrateToEgressV1()
resync()
@ -441,6 +442,48 @@ func updateAcls() {
}
}
func updateNewAcls() {
if servercfg.IsPro {
userGroups, _ := logic.ListUserGroups()
userGroupMap := make(map[models.UserGroupID]models.UserGroup)
for _, userGroup := range userGroups {
userGroupMap[userGroup.ID] = userGroup
}
acls := logic.ListAcls()
for _, acl := range acls {
aclSrc := make([]models.AclPolicyTag, 0)
for _, src := range acl.Src {
if src.ID == models.UserGroupAclID {
userGroup, ok := userGroupMap[models.UserGroupID(src.Value)]
if !ok {
// if the group doesn't exist, don't add it to the acl's src.
continue
} else {
_, ok := userGroup.NetworkRoles[acl.NetworkID]
if !ok {
// if the group doesn't have permissions for the acl's
// network, don't add it to the acl's src.
continue
}
}
}
aclSrc = append(aclSrc, src)
}
if len(aclSrc) == 0 {
// if there are no acl sources, delete the acl.
_ = logic.DeleteAcl(acl)
} else if len(aclSrc) != len(acl.Src) {
// if some user groups were removed from the acl source,
// update the acl.
acl.Src = aclSrc
_ = logic.UpsertAcl(acl)
}
}
}
}
func MigrateEmqx() {
err := mq.SendPullSYN()

View file

@ -470,12 +470,14 @@ func createUserGroup(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
networks, err := logic.GetNetworks()
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, network := range networks {
for networkID := range userGroupReq.Group.NetworkRoles {
network, err := logic.GetNetwork(networkID.String())
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
acl := models.Acl{
ID: uuid.New().String(),
Name: fmt.Sprintf("%s group", userGroupReq.Group.Name),
@ -599,6 +601,93 @@ func updateUserGroup(w http.ResponseWriter, r *http.Request) {
},
Origin: models.Dashboard,
})
go func() {
networksAdded := make([]models.NetworkID, 0)
networksRemoved := make([]models.NetworkID, 0)
for networkID := range userGroup.NetworkRoles {
if _, ok := currUserG.NetworkRoles[networkID]; !ok {
networksAdded = append(networksAdded, networkID)
}
}
for networkID := range currUserG.NetworkRoles {
if _, ok := userGroup.NetworkRoles[networkID]; !ok {
networksRemoved = append(networksRemoved, networkID)
}
}
for _, networkID := range networksAdded {
// ensure the network exists.
network, err := logic.GetNetwork(networkID.String())
if err != nil {
continue
}
// insert acl if the network is added to the group.
acl := models.Acl{
ID: uuid.New().String(),
Name: fmt.Sprintf("%s group", userGroup.Name),
MetaData: "This Policy allows user group to communicate with all gateways",
Default: false,
ServiceType: models.Any,
NetworkID: models.NetworkID(network.NetID),
Proto: models.ALL,
RuleType: models.UserPolicy,
Src: []models.AclPolicyTag{
{
ID: models.UserGroupAclID,
Value: userGroup.ID.String(),
},
},
Dst: []models.AclPolicyTag{
{
ID: models.NodeTagID,
Value: fmt.Sprintf("%s.%s", models.NetworkID(network.NetID), models.GwTagName),
}},
AllowedDirection: models.TrafficDirectionUni,
Enabled: true,
CreatedBy: "auto",
CreatedAt: time.Now().UTC(),
}
_ = logic.InsertAcl(acl)
}
// since this group doesn't have a role for this network,
// there is no point in having this group as src in any
// of the network's acls.
for _, networkID := range networksRemoved {
acls, err := logic.ListAclsByNetwork(networkID)
if err != nil {
continue
}
for _, acl := range acls {
var hasGroupSrc bool
newAclSrc := make([]models.AclPolicyTag, 0)
for _, src := range acl.Src {
if src.ID == models.UserGroupAclID && src.Value == userGroup.ID.String() {
hasGroupSrc = true
} else {
newAclSrc = append(newAclSrc, src)
}
}
if hasGroupSrc {
if len(newAclSrc) == 0 {
// no other src exists, delete acl.
_ = logic.DeleteAcl(acl)
} else {
// other sources exist, update acl.
acl.Src = newAclSrc
_ = logic.UpsertAcl(acl)
}
}
}
}
}()
// reset configs for service user
go proLogic.UpdatesUserGwAccessOnGrpUpdates(currUserG.NetworkRoles, userGroup.NetworkRoles)
logic.ReturnSuccessResponseWithJson(w, r, userGroup, "updated user group")
@ -658,6 +747,39 @@ func deleteUserGroup(w http.ResponseWriter, r *http.Request) {
},
Origin: models.Dashboard,
})
go func() {
for networkID := range userG.NetworkRoles {
acls, err := logic.ListAclsByNetwork(networkID)
if err != nil {
continue
}
for _, acl := range acls {
var hasGroupSrc bool
newAclSrc := make([]models.AclPolicyTag, 0)
for _, src := range acl.Src {
if src.ID == models.UserGroupAclID && src.Value == userG.ID.String() {
hasGroupSrc = true
} else {
newAclSrc = append(newAclSrc, src)
}
}
if hasGroupSrc {
if len(newAclSrc) == 0 {
// no other src exists, delete acl.
_ = logic.DeleteAcl(acl)
} else {
// other sources exist, update acl.
acl.Src = newAclSrc
_ = logic.UpsertAcl(acl)
}
}
}
}
}()
go proLogic.UpdatesUserGwAccessOnGrpUpdates(userG.NetworkRoles, make(map[models.NetworkID]map[models.UserRoleID]struct{}))
logic.ReturnSuccessResponseWithJson(w, r, nil, "deleted user group")
}

View file

@ -132,6 +132,7 @@ func InitPro() {
logic.MigrateToUUIDs = proLogic.MigrateToUUIDs
logic.IntialiseGroups = proLogic.UserGroupsInit
logic.AddGlobalNetRolesToAdmins = proLogic.AddGlobalNetRolesToAdmins
logic.ListUserGroups = proLogic.ListUserGroups
logic.GetUserGroupsInNetwork = proLogic.GetUserGroupsInNetwork
logic.GetUserGroup = proLogic.GetUserGroup
logic.GetNodeStatus = proLogic.GetNodeStatus