pkg/common/security/acl.go (99 lines of code) (raw):
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package security
import (
"fmt"
"regexp"
"strings"
"go.uber.org/zap"
"github.com/apache/yunikorn-core/pkg/common"
"github.com/apache/yunikorn-core/pkg/log"
)
// User and group regexp, must allow at least what we allow in the config checks
// See configs.UserNameRegExp and configs.GroupRegExp in the config validator.
var userNameRegExp = regexp.MustCompile("^[_a-zA-Z][a-zA-Z0-9_.@-]*[$]?$")
var groupRegExp = regexp.MustCompile("^[_a-zA-Z][a-zA-Z0-9_-]*$")
type ACL struct {
users map[string]bool
groups map[string]bool
allAllowed bool
}
// the ACL allows all access, set the flag
func (a *ACL) setAllAllowed(part string) {
part = strings.TrimSpace(part)
a.allAllowed = part == common.Wildcard
}
// set the user list in the ACL, invalid user names are ignored.
// If the silence flag is set to true, the function will not log when setting the users.
func (a *ACL) setUsers(userList []string, silence bool) {
a.users = make(map[string]bool)
// special case if the user list is just the wildcard
if len(userList) == 1 && userList[0] == common.Wildcard {
if !silence {
log.Log(log.Security).Info("user list is wildcard, allowing all access")
}
a.allAllowed = true
return
}
// add all users to the map
for _, user := range userList {
// skip an empty user (happens if ACL is just groups)
if user == "" {
continue
}
// check the users validity
if userNameRegExp.MatchString(user) {
a.users[user] = true
} else if !silence {
log.Log(log.Security).Info("ignoring user in ACL definition",
zap.String("user", user))
}
}
}
// set the group list in the ACL, invalid group names are ignored
// If the silence flag is set to true, the function will not log when setting the groups.
func (a *ACL) setGroups(groupList []string, silence bool) {
a.groups = make(map[string]bool)
// special case if the wildcard was already set
if a.allAllowed {
if !silence {
log.Log(log.Security).Info("ignoring group list in ACL: wildcard set")
}
return
}
if len(groupList) == 1 && groupList[0] == common.Wildcard {
if !silence {
log.Log(log.Security).Info("group list is wildcard, allowing all access")
}
a.users = make(map[string]bool)
a.allAllowed = true
return
}
// add all groups to the map
for _, group := range groupList {
// skip an empty group (happens if ACL is just users and ends in space)
if group == "" {
continue
}
// check the group validity
if groupRegExp.MatchString(group) {
a.groups[group] = true
} else if !silence {
log.Log(log.Security).Info("ignoring group in ACL",
zap.String("group", group))
}
}
}
// create a new ACL from scratch
func NewACL(aclStr string, silence bool) (ACL, error) {
acl := ACL{}
if aclStr == "" {
return acl, nil
}
// before trimming check
// should have no more than two groups defined
fields := strings.Split(aclStr, common.Space)
if len(fields) > 2 {
return acl, fmt.Errorf("multiple spaces found in ACL: '%s'", aclStr)
}
// trim and check for wildcard
acl.setAllAllowed(aclStr)
// parse users and groups
acl.setUsers(strings.Split(fields[0], common.Separator), silence)
if len(fields) == 2 {
acl.setGroups(strings.Split(fields[1], common.Separator), silence)
}
return acl, nil
}
// Check if the user has access
func (a ACL) CheckAccess(userObj UserGroup) bool {
// shortcut allow all
if a.allAllowed {
return true
}
// if the ACL is not the wildcard we have non nil lists
// check user access
if a.users[userObj.User] {
return true
}
// get groups for the user and check them
for _, group := range userObj.Groups {
if a.groups[group] {
return true
}
}
return false
}