fix(apiserver): enforce self_ug job scopes and accept project filters (bug-1934, bug-1935)
bug-1934: every job-permission check read a single scope and compared it only with "self", so a job:view or job:control grant scoped to self_ug acted like "all" (list, get by ID, control), and self + self_ug collapsed to self. userJobScope() now collects all of a user's grants of a job permission before deduplication and adds them up (all; self; self_ug = the user and everyone sharing a user group). The job list appends a submit_user filter for narrower scopes, get-by-ID keeps covered rows in a new slice (removing from the ranged slice skipped rows) and answers 403, both control paths and job output check the owner against the scope. User groups now reach the apiserver: the config push converts cluster_config_t.ugroups and OnUserGroupsUpdated() replaces the new store (nested groups, all, exclusions). The default basic_user now also sees its user-group mates' jobs, as its configuration says. bug-1935: project_name and license_project are accepted job filters, so xcecli jobs list -P works. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6114f16dac
commit
4a38bb3ca9
18 changed files with 750 additions and 79 deletions
|
|
@ -157,13 +157,18 @@ func (j JobController) GetJobsInfo(ctx *gin.Context) {
|
|||
}
|
||||
logger.Debug("get job info accessQueues", zap.Any("accessQueues", accessQueues),
|
||||
zap.Any("permission", permission))
|
||||
// check job:view permission
|
||||
permission, err = GetRequireResourcePermission(submitUser, "job:view")
|
||||
logger.Debug("get job info for job:view permission:", zap.Any("permission", permission))
|
||||
// check job:view permission: every grant counts (bug-1934)
|
||||
scope, err := userJobScope(submitUser, "job:view")
|
||||
logger.Debug("get job info for job:view scope:", zap.Any("scope", scope))
|
||||
if err != nil || len(accessQueues) == 0 {
|
||||
ReturnError(ctx, common.ERROR_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
ownerFilter, needOwnerFilter, anyOwner := scope.ownerFilter()
|
||||
if !anyOwner {
|
||||
ReturnError(ctx, common.ERROR_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// if submit_user is not specified, use query request submission user
|
||||
var specify_user bool = false
|
||||
|
|
@ -175,16 +180,17 @@ func (j JobController) GetJobsInfo(ctx *gin.Context) {
|
|||
if filter.Value == models.DomainTypeAll {
|
||||
delindex = i
|
||||
}
|
||||
if permission.DomainType != models.DomainTypeSelf {
|
||||
submitUser = filter.Value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if delindex != -1 {
|
||||
filters = append(filters[:delindex], filters[delindex+1:]...)
|
||||
}
|
||||
if !specify_user || permission.DomainType == models.DomainTypeSelf {
|
||||
// a narrower scope keeps only the jobs of the owners it covers
|
||||
if needOwnerFilter {
|
||||
filters = append(filters, ownerFilter)
|
||||
}
|
||||
if !specify_user {
|
||||
logger.Debug("submit_user is not specified in query filter, add submit_user into filter: filter=[{submit_user:eq:" + submitUser)
|
||||
filters = append(filters, utils.FilterCondition{
|
||||
Field: "submit_user",
|
||||
|
|
@ -743,27 +749,29 @@ func (j JobController) GetJobByIDs(ctx *gin.Context) {
|
|||
} // end for jobid
|
||||
logger.Debug("the specific jobid query reply after merge:", zap.Any("jqueryReply", mergeJqueryReply))
|
||||
|
||||
// check rbac
|
||||
// check rbac: every job:view grant counts (bug-1934); rows are kept in
|
||||
// a new slice, since removing them from the one being ranged over
|
||||
// skipped the row after each removal
|
||||
removeFlag := false
|
||||
for i, row := range mergeJqueryReply {
|
||||
jobPermission, err := GetRequireResourcePermission(logonUser, "job:view")
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
queuePermission, err := GetRequireResourcePermission(logonUser, "queue:view")
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
jobScope, err := userJobScope(logonUser, "job:view")
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
queuePermission, err := GetRequireResourcePermission(logonUser, "queue:view")
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
visible := mergeJqueryReply[:0:0]
|
||||
for _, row := range mergeJqueryReply {
|
||||
if job_owner, exist := row.Data["submit_user"]; exist {
|
||||
if jobPermission.DomainType == models.DomainTypeSelf {
|
||||
if logonUser != job_owner.(string) {
|
||||
removeFlag = true
|
||||
mergeJqueryReply = append(mergeJqueryReply[:i], mergeJqueryReply[i+1:]...)
|
||||
}
|
||||
owner, _ := job_owner.(string)
|
||||
if !jobScope.covers(owner) {
|
||||
removeFlag = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if typeValue != "array_stats" {
|
||||
|
|
@ -773,15 +781,16 @@ func (j JobController) GetJobByIDs(ctx *gin.Context) {
|
|||
permissionQueueValues := queuePermission.DomainValues
|
||||
if !utils.ContainsField(permissionQueueValues, queueName) {
|
||||
removeFlag = true
|
||||
mergeJqueryReply = append(mergeJqueryReply[:i], mergeJqueryReply[i+1:]...)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visible = append(visible, row)
|
||||
}
|
||||
mergeJqueryReply = visible
|
||||
if removeFlag && len(mergeJqueryReply) == 0 {
|
||||
ReturnError(ctx, common.ERROR_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -2181,18 +2190,17 @@ func (j JobController) ControlJobByID(ctx *gin.Context) {
|
|||
|
||||
// check rbac
|
||||
if job_owner, exist := jobInfo.Data["submit_user"]; exist {
|
||||
jobPermission, err := GetRequireResourcePermission(submitter, "job:control")
|
||||
jobScope, err := userJobScope(submitter, "job:control")
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
if jobPermission.DomainType == models.DomainTypeSelf {
|
||||
if job_owner.(string) != submitter {
|
||||
logger.Error("Permission denied", zap.String("perm", "job:control"), zap.String("logonUser", submitter))
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
// bug-1934: every job:control grant counts
|
||||
if owner, _ := job_owner.(string); !jobScope.covers(owner) {
|
||||
logger.Error("Permission denied", zap.String("perm", "job:control"), zap.String("logonUser", submitter))
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2448,18 +2456,17 @@ func (j JobController) ControlJob(ctx *gin.Context) {
|
|||
|
||||
// check rbac begin
|
||||
if job_owner, exist := job.Data["submit_user"]; exist {
|
||||
jobPermission, err := GetRequireResourcePermission(submitter, "job:control")
|
||||
jobScope, err := userJobScope(submitter, "job:control")
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
if jobPermission.DomainType == models.DomainTypeSelf {
|
||||
if job_owner.(string) != submitter {
|
||||
logger.Error("Permission denied", zap.String("perm", "job:control"), zap.String("logonUser", submitter))
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
// bug-1934: every job:control grant counts
|
||||
if owner, _ := job_owner.(string); !jobScope.covers(owner) {
|
||||
logger.Error("Permission denied", zap.String("perm", "job:control"), zap.String("logonUser", submitter))
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue