github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/pkg/auth/rbac.go (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 17 package auth 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "os" 24 "strings" 25 26 "github.com/freiheit-com/kuberpult/pkg/valid" 27 28 "google.golang.org/grpc/codes" 29 "google.golang.org/grpc/status" 30 ) 31 32 const ( 33 PermissionCreateLock = "CreateLock" 34 PermissionDeleteLock = "DeleteLock" 35 PermissionCreateRelease = "CreateRelease" 36 PermissionDeployRelease = "DeployRelease" 37 PermissionCreateUndeploy = "CreateUndeploy" 38 PermissionDeployUndeploy = "DeployUndeploy" 39 PermissionCreateEnvironment = "CreateEnvironment" 40 PermissionDeleteEnvironmentApplication = "DeleteEnvironmentApplication" 41 PermissionDeployReleaseTrain = "DeployReleaseTrain" 42 // The default permission template. 43 PermissionTemplate = "%s,%s,%s:%s,%s,allow" 44 ) 45 46 // All static rbac information that is required to check authentication of a given user. 47 type RBACConfig struct { 48 // Indicates if Dex is enabled. 49 DexEnabled bool 50 // The RBAC policy. A key is a permission, for example: "Developer, CreateLock, development:development, *, allow" 51 Policy map[string]*Permission 52 } 53 54 // Inits the RBAC Config struct 55 func initPolicyConfig() policyConfig { 56 return policyConfig{ 57 // List of allowed actions on the RBAC policy. 58 allowedActions: []string{ 59 PermissionCreateLock, 60 PermissionDeleteLock, 61 PermissionCreateRelease, 62 PermissionDeployRelease, 63 PermissionCreateUndeploy, 64 PermissionDeployUndeploy, 65 PermissionCreateEnvironment, 66 PermissionDeleteEnvironmentApplication, 67 PermissionDeployReleaseTrain}, 68 } 69 } 70 71 // Stores the RBAC Policy allowed Applications and Actions. 72 // Only used for policy validation. 73 type policyConfig struct { 74 allowedActions []string 75 } 76 77 func (c *policyConfig) validateAction(action string) error { 78 for _, a := range c.allowedActions { 79 if a == action { 80 return nil 81 } 82 } 83 return fmt.Errorf("invalid action %s", action) 84 } 85 86 func (c *policyConfig) validateEnvs(envs, action string) error { 87 e := strings.Split(envs, ":") 88 if len(e) != 2 || envs == "" { 89 return fmt.Errorf("invalid environment %s", envs) 90 } 91 // The environment follows the format <ENVIRONMENT_GROUP:ENVIRONMENT> 92 groupName := e[0] 93 envName := e[1] 94 // Validate environment group 95 if !valid.EnvironmentName(groupName) && groupName != "*" { 96 return fmt.Errorf("invalid environment group %s", envs) 97 } 98 // Actions that are environment independent need to follow the format <ENVIRONMENT_GROUP:*>. 99 if isEnvironmentIndependent(action) { 100 if envName == "*" { 101 return nil 102 } 103 return fmt.Errorf("the action %s requires the environment * and got %s", action, envs) 104 } 105 // Validate environment 106 if !valid.EnvironmentName(envName) && envName != "*" { 107 return fmt.Errorf("invalid environment %s", envs) 108 } 109 return nil 110 } 111 112 func (c *policyConfig) validateApplication(app string) error { 113 if app == "*" { 114 return nil 115 } 116 if !valid.ApplicationName(app) { 117 return fmt.Errorf("invalid application %s", app) 118 } 119 return nil 120 } 121 122 // Helper function to indicate is the format if the specified action 123 // is independent from the environment. If so, the following format needs to be 124 // followed <ENVIRONMENT_GROUP:*>. 125 func isEnvironmentIndependent(action string) bool { 126 switch action { 127 case PermissionCreateUndeploy, PermissionDeployUndeploy, PermissionCreateRelease: 128 return true 129 } 130 return false 131 } 132 133 // Struct to store an RBAC permission. 134 type Permission struct { 135 Role string 136 Application string 137 Environment string 138 Action string 139 } 140 141 func ValidateRbacPermission(line string) (p *Permission, err error) { 142 cfg := initPolicyConfig() 143 // Verifies if all fields are specified 144 c := strings.Split(line, ",") 145 if len(c) != 5 { 146 return nil, fmt.Errorf("5 fields are expected but only %d were specified", len(c)) 147 } 148 // Permission role 149 role := c[0] 150 // Validates the permission action 151 action := c[1] 152 err = cfg.validateAction(action) 153 if err != nil { 154 return nil, err 155 } 156 // Validate the permission environment 157 environment := c[2] 158 err = cfg.validateEnvs(environment, action) 159 if err != nil { 160 return nil, err 161 } 162 // Validate the environment names 163 application := c[3] 164 err = cfg.validateApplication(application) 165 if err != nil { 166 return nil, err 167 } 168 return &Permission{ 169 Role: role, 170 Action: action, 171 Environment: environment, 172 Application: application, 173 }, nil 174 } 175 176 func ReadRbacPolicy(dexEnabled bool, DexRbacPolicyPath string) (policy map[string]*Permission, err error) { 177 if !dexEnabled { 178 return nil, nil 179 } 180 181 file, err := os.Open(DexRbacPolicyPath) 182 if err != nil { 183 return nil, err 184 } 185 defer file.Close() 186 187 policy = map[string]*Permission{} 188 scanner := bufio.NewScanner(file) 189 for scanner.Scan() { 190 // Trim spaces from policy 191 line := strings.ReplaceAll(scanner.Text(), " ", "") 192 p, err := ValidateRbacPermission(line) 193 if err != nil { 194 return nil, err 195 } 196 policy[line] = p 197 } 198 if len(policy) == 0 { 199 return nil, errors.New("dex.policy.error: dexRbacPolicy is required when \"KUBERPULT_DEX_ENABLED\" is true") 200 } 201 return policy, nil 202 } 203 204 type PermissionError struct { 205 User string 206 Role string 207 Action string 208 Environment string 209 Team string 210 } 211 212 func (e PermissionError) Error() string { 213 var msg = fmt.Sprintf( 214 "%s The user '%s' with role '%s' is not allowed to perform the action '%s' on environment '%s'", 215 codes.PermissionDenied.String(), 216 e.User, 217 e.Role, 218 e.Action, 219 e.Environment, 220 ) 221 if e.Team != "" { 222 msg += fmt.Sprintf(" for team '%s'", e.Team) 223 } 224 return msg 225 } 226 227 func (e PermissionError) GRPCStatus() *status.Status { 228 return status.New(codes.PermissionDenied, e.Error()) 229 } 230 231 // Checks user permissions on the RBAC policy. 232 func CheckUserPermissions(rbacConfig RBACConfig, user *User, env, team, envGroup, application, action string) error { 233 // If the action is environment independent, the env format is <ENVIRONMENT_GROUP>:* 234 if isEnvironmentIndependent(action) { 235 env = "*" 236 } 237 // Check for all possible Wildcard combinations. Maximum of 8 combinations (2^3). 238 for _, pEnvGroup := range []string{envGroup, "*"} { 239 for _, pEnv := range []string{env, "*"} { 240 for _, pApplication := range []string{application, "*"} { 241 // Check if the permission exists on the policy. 242 permissionsWanted := fmt.Sprintf(PermissionTemplate, user.DexAuthContext.Role, action, pEnvGroup, pEnv, pApplication) 243 _, permissionsExist := rbacConfig.Policy[permissionsWanted] 244 if permissionsExist { 245 return nil 246 } 247 } 248 } 249 } 250 // The permission is not found. Return an error. 251 return PermissionError{ 252 User: user.Name, 253 Role: user.DexAuthContext.Role, 254 Action: action, 255 Environment: env, 256 Team: team, 257 } 258 } 259 260 // Helper function to parse the scopes 261 func ReadScopes(s string) (scopes []string) { 262 replacer := strings.NewReplacer(" ", "") 263 scopesTrim := replacer.Replace(s) 264 return strings.Split(scopesTrim, ",") 265 }