| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- package acl
- import (
- "context"
- "strings"
- config "github.com/OliveTin/OliveTin/internal/config"
- log "github.com/sirupsen/logrus"
- "golang.org/x/exp/slices"
- "google.golang.org/grpc/metadata"
- )
- type PermissionBits int
- const (
- View PermissionBits = 1 << iota
- Exec
- Logs
- Kill
- )
- func (p PermissionBits) Has(permission PermissionBits) bool {
- return p&permission != 0
- }
- // User respresents a person.
- type AuthenticatedUser struct {
- Username string
- UsergroupLine string
- Provider string
- SID string
- Acls []string
- EffectivePolicy *config.ConfigurationPolicy
- }
- func (u *AuthenticatedUser) IsGuest() bool {
- return u.Username == "guest" && u.Provider == "system"
- }
- func (u *AuthenticatedUser) parseUsergroupLine(sep string) []string {
- ret := []string{}
- if sep != "" {
- for _, v := range strings.Split(u.UsergroupLine, sep) {
- trimmed := strings.TrimSpace(v)
- if trimmed != "" {
- ret = append(ret, trimmed)
- }
- }
- } else {
- ret = strings.Fields(u.UsergroupLine)
- }
- return ret
- }
- func (u *AuthenticatedUser) matchesUsergroupAcl(matchUsergroups []string, sep string) bool {
- groupList := u.parseUsergroupLine(sep)
- for _, group := range groupList {
- if slices.Contains(matchUsergroups, group) {
- log.Debugf("Usergroup %v found in %+v (len: %v)", group, groupList, len(groupList))
- return true
- }
- }
- return false
- }
- type UnauthenticatedUser struct {
- Username string
- Usergroup string
- Provider string
- Sid string
- }
- func logAclNotMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
- if cfg.LogDebugOptions.AclNotMatched {
- log.WithFields(log.Fields{
- "User": user.Username,
- "Action": action.Title,
- "ACL": acl.Name,
- }).Debugf("%v - ACL Not Matched", aclFunction)
- }
- }
- func logAclMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
- actionTitle := "N/A"
- if action != nil {
- actionTitle = action.Title
- }
- if cfg.LogDebugOptions.AclMatched {
- log.WithFields(log.Fields{
- "User": user.Username,
- "Action": actionTitle,
- "ACL": acl.Name,
- }).Debugf("%v - Matched ACL", aclFunction)
- }
- }
- func logAclNoneMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, defaultPermission bool) {
- if cfg.LogDebugOptions.AclNoneMatched {
- log.WithFields(log.Fields{
- "User": user.Username,
- "Action": action.Title,
- "Default": defaultPermission,
- }).Debugf("%v - No ACLs Matched, returning default permission", aclFunction)
- }
- }
- func permissionsConfigToBits(permissions config.PermissionsList) PermissionBits {
- type permPair struct {
- enabled bool
- bit PermissionBits
- }
- permMap := []permPair{
- {permissions.View, View},
- {permissions.Exec, Exec},
- {permissions.Logs, Logs},
- {permissions.Kill, Kill},
- }
- var ret PermissionBits
- for _, perm := range permMap {
- if perm.enabled {
- ret |= perm.bit
- }
- }
- return ret
- }
- func aclCheck(requiredPermission PermissionBits, defaultValue bool, cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action) bool {
- relevantAcls := getRelevantAcls(cfg, action.Acls, user)
- if cfg.LogDebugOptions.AclCheckStarted {
- log.WithFields(log.Fields{
- "actionTitle": action.Title,
- "username": user.Username,
- "usergroupLine": user.UsergroupLine,
- "relevantAcls": len(relevantAcls),
- "requiredPermission": requiredPermission,
- }).Debugf("ACL check - %v", aclFunction)
- }
- for _, acl := range relevantAcls {
- permissionBits := permissionsConfigToBits(acl.Permissions)
- if permissionBits.Has(requiredPermission) {
- logAclMatched(cfg, aclFunction, user, action, acl)
- return true
- } else {
- logAclNotMatched(cfg, aclFunction, user, action, acl)
- }
- }
- logAclNoneMatched(cfg, aclFunction, user, action, cfg.DefaultPermissions.Logs)
- return defaultValue
- }
- // IsAllowedLogs checks if a AuthenticatedUser is allowed to view an action's logs
- func IsAllowedLogs(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
- return aclCheck(Logs, cfg.DefaultPermissions.Logs, cfg, "isAllowedLogs", user, action)
- }
- // IsAllowedExec checks if a AuthenticatedUser is allowed to execute an Action
- func IsAllowedExec(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
- return aclCheck(Exec, cfg.DefaultPermissions.Exec, cfg, "isAllowedExec", user, action)
- }
- // IsAllowedView checks if a User is allowed to view an Action
- func IsAllowedView(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
- if action.Hidden {
- return false
- }
- return aclCheck(View, cfg.DefaultPermissions.View, cfg, "isAllowedView", user, action)
- }
- func IsAllowedKill(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
- return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action)
- }
- func getMetadataKeyOrEmpty(md metadata.MD, key string) string {
- mdValues := md.Get(key)
- if len(mdValues) > 0 {
- return mdValues[0]
- }
- return ""
- }
- func UserFromUnauthenticatedUser(cfg *config.Config, unauthenticatedUser UnauthenticatedUser) *AuthenticatedUser {
- ret := &AuthenticatedUser{}
- ret.Username = unauthenticatedUser.Username
- ret.UsergroupLine = unauthenticatedUser.Usergroup
- ret.Provider = unauthenticatedUser.Provider
- ret.SID = unauthenticatedUser.Sid
- if ret.Username == "" {
- ret = UserGuest(cfg)
- }
- buildUserAcls(cfg, ret)
- log.WithFields(log.Fields{
- "username": ret.Username,
- "usergroupLine": ret.UsergroupLine,
- "provider": ret.Provider,
- "acls": ret.Acls,
- }).Debugf("UserFromUnauthenticatedUser")
- return ret
- }
- // UserFromContext tries to find a user from a grpc context
- func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
- var ret *AuthenticatedUser
- md, ok := metadata.FromIncomingContext(ctx)
- if ok {
- ret = &AuthenticatedUser{}
- ret.Username = getMetadataKeyOrEmpty(md, "username")
- ret.UsergroupLine = getMetadataKeyOrEmpty(md, "usergroup")
- ret.Provider = getMetadataKeyOrEmpty(md, "provider")
- buildUserAcls(cfg, ret)
- }
- if !ok || ret.Username == "" {
- ret = UserGuest(cfg)
- }
- log.WithFields(log.Fields{
- "username": ret.Username,
- "usergroupLine": ret.UsergroupLine,
- "provider": ret.Provider,
- "acls": ret.Acls,
- }).Debugf("UserFromContext")
- return ret
- }
- func UserGuest(cfg *config.Config) *AuthenticatedUser {
- ret := &AuthenticatedUser{}
- ret.Username = "guest"
- ret.UsergroupLine = "guest"
- ret.Provider = "system"
- buildUserAcls(cfg, ret)
- return ret
- }
- func UserFromSystem(cfg *config.Config, username string) *AuthenticatedUser {
- ret := &AuthenticatedUser{
- Username: username,
- UsergroupLine: "system",
- Provider: "system",
- }
- buildUserAcls(cfg, ret)
- return ret
- }
- func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) {
- for _, acl := range cfg.AccessControlLists {
- if slices.Contains(acl.MatchUsernames, user.Username) {
- user.Acls = append(user.Acls, acl.Name)
- continue
- }
- if user.matchesUsergroupAcl(acl.MatchUsergroups, cfg.AuthHttpHeaderUserGroupSep) {
- user.Acls = append(user.Acls, acl.Name)
- continue
- }
- }
- user.EffectivePolicy = getEffectivePolicy(cfg, user)
- }
- func isACLRelevantToAction(cfg *config.Config, actionAcls []string, acl *config.AccessControlList, user *AuthenticatedUser) bool {
- if !slices.Contains(user.Acls, acl.Name) {
- // If the user does not have this ACL, then it is not relevant
- return false
- }
- if acl.AddToEveryAction {
- return true
- }
- if slices.Contains(actionAcls, acl.Name) {
- return true
- }
- return false
- }
- func getRelevantAcls(cfg *config.Config, actionAcls []string, user *AuthenticatedUser) []*config.AccessControlList {
- var ret []*config.AccessControlList
- for _, acl := range cfg.AccessControlLists {
- if isACLRelevantToAction(cfg, actionAcls, acl, user) {
- ret = append(ret, acl)
- }
- }
- return ret
- }
- func getEffectivePolicy(cfg *config.Config, user *AuthenticatedUser) *config.ConfigurationPolicy {
- ret := &config.ConfigurationPolicy{
- ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics,
- ShowLogList: cfg.DefaultPolicy.ShowLogList,
- }
- for _, acl := range cfg.AccessControlLists {
- if slices.Contains(user.Acls, acl.Name) {
- logAclMatched(cfg, "GetEffectivePolicy", user, nil, acl)
- ret = buildConfigurationPolicy(ret, acl.Policy)
- }
- }
- return ret
- }
- func buildConfigurationPolicy(ret *config.ConfigurationPolicy, policy config.ConfigurationPolicy) *config.ConfigurationPolicy {
- if policy.ShowDiagnostics {
- ret.ShowDiagnostics = policy.ShowDiagnostics
- }
- if policy.ShowLogList {
- ret.ShowLogList = policy.ShowLogList
- }
- return ret
- }
|