fmt: Rebuilt ACL code to have less boilerplate (#397)

This commit is contained in:
James Read
2024-08-30 21:30:44 +01:00
committed by GitHub
parent 2671983c43
commit defcf6d26e
3 changed files with 96 additions and 41 deletions

View File

@@ -9,6 +9,18 @@ import (
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
) )
type PermissionBits int
const (
View PermissionBits = 1 << iota
Exec
Logs
)
func (p PermissionBits) Has(permission PermissionBits) bool {
return p&permission != 0
}
// User respresents a person. // User respresents a person.
type AuthenticatedUser struct { type AuthenticatedUser struct {
Username string Username string
@@ -17,7 +29,7 @@ type AuthenticatedUser struct {
acls []string acls []string
} }
func logAclNotMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action) { func logAclNotMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
if cfg.LogDebugOptions.AclNotMatched { if cfg.LogDebugOptions.AclNotMatched {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"User": user.Username, "User": user.Username,
@@ -36,34 +48,70 @@ func logAclMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUs
} }
} }
// IsAllowedLogs checks if a AuthenticatedUser is allowed to view an action's logs func logAclNoneMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, defaultPermission bool) {
func IsAllowedLogs(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool { if cfg.LogDebugOptions.AclNoneMatched {
for _, acl := range getRelevantAcls(cfg, action.Acls, user) { log.WithFields(log.Fields{
if acl.Permissions.Logs { "User": user.Username,
logAclMatched(cfg, "isAllowedLogs", user, action, acl) "Action": action.Title,
"Default": defaultPermission,
}).Debugf("%v - No ACLs Matched, returning default permission", aclFunction)
}
}
func permissionsConfigToBits(permissions config.PermissionsList) PermissionBits {
var ret PermissionBits
if permissions.View {
ret |= View
}
if permissions.Exec {
ret |= Exec
}
if permissions.Logs {
ret |= Logs
}
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)
log.WithFields(log.Fields{
"actionTitle": action.Title,
"username": user.Username,
"usergroup": user.Usergroup,
"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 return true
} else {
logAclNotMatched(cfg, aclFunction, user, action, acl)
} }
} }
logAclNotMatched(cfg, "isAllowedLogs", user, action) logAclNoneMatched(cfg, aclFunction, user, action, cfg.DefaultPermissions.Logs)
return 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 // IsAllowedExec checks if a AuthenticatedUser is allowed to execute an Action
func IsAllowedExec(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool { func IsAllowedExec(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
for _, acl := range getRelevantAcls(cfg, action.Acls, user) { return aclCheck(Exec, cfg.DefaultPermissions.Exec, cfg, "isAllowedExec", user, action)
if acl.Permissions.Exec {
logAclMatched(cfg, "isAllowedExec", user, action, acl)
return true
}
}
logAclNotMatched(cfg, "isAllowedExec", user, action)
return cfg.DefaultPermissions.Exec
} }
// IsAllowedView checks if a User is allowed to view an Action // IsAllowedView checks if a User is allowed to view an Action
@@ -72,20 +120,10 @@ func IsAllowedView(cfg *config.Config, user *AuthenticatedUser, action *config.A
return false return false
} }
for _, acl := range getRelevantAcls(cfg, action.Acls, user) { return aclCheck(View, cfg.DefaultPermissions.View, cfg, "isAllowedView", user, action)
if acl.Permissions.View {
logAclMatched(cfg, "isAllowedView", user, action, acl)
return true
}
}
logAclNotMatched(cfg, "isAllowedView", user, action)
return cfg.DefaultPermissions.View
} }
func getMetdataKeyOrEmpty(md metadata.MD, key string) string { func getMetadataKeyOrEmpty(md metadata.MD, key string) string {
mdValues := md.Get(key) mdValues := md.Get(key)
if len(mdValues) > 0 { if len(mdValues) > 0 {
@@ -97,16 +135,21 @@ func getMetdataKeyOrEmpty(md metadata.MD, key string) string {
// UserFromContext tries to find a user from a grpc context // UserFromContext tries to find a user from a grpc context
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser { func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
ret := &AuthenticatedUser{}
md, ok := metadata.FromIncomingContext(ctx) md, ok := metadata.FromIncomingContext(ctx)
ret := &AuthenticatedUser{ if ok {
Username: "guest", ret.Username = getMetadataKeyOrEmpty(md, "username")
Usergroup: "guest", ret.Usergroup = getMetadataKeyOrEmpty(md, "usergroup")
} }
if ok { if ret.Username == "" {
ret.Username = getMetdataKeyOrEmpty(md, "username") ret.Username = "guest"
ret.Usergroup = getMetdataKeyOrEmpty(md, "usergroup") }
if ret.Usergroup == "" {
ret.Usergroup = "guest"
} }
buildUserAcls(cfg, ret) buildUserAcls(cfg, ret)
@@ -119,6 +162,17 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser
return ret return ret
} }
func UserFromSystem(cfg *config.Config, username string) *AuthenticatedUser {
ret := &AuthenticatedUser{
Username: username,
Usergroup: "system",
}
buildUserAcls(cfg, ret)
return ret
}
func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) { func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) {
for _, acl := range cfg.AccessControlLists { for _, acl := range cfg.AccessControlLists {
if slices.Contains(acl.MatchUsernames, user.Username) { if slices.Contains(acl.MatchUsernames, user.Username) {

View File

@@ -144,6 +144,7 @@ type LogDebugOptions struct {
SingleFrontendRequestHeaders bool SingleFrontendRequestHeaders bool
AclMatched bool AclMatched bool
AclNotMatched bool AclNotMatched bool
AclNoneMatched bool
} }
type DashboardComponent struct { type DashboardComponent struct {

View File

@@ -57,10 +57,10 @@ func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD {
username, usergroup = parseHttpHeaderForAuth(req) username, usergroup = parseHttpHeaderForAuth(req)
} }
md := metadata.Pairs( md := metadata.New(map[string]string {
"username", username, "username": username,
"usergroup", usergroup, "usergroup": usergroup,
) })
log.Tracef("api request metadata: %+v", md) log.Tracef("api request metadata: %+v", md)