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  }