github.com/argoproj/argo-cd/v2@v2.10.9/server/rbacpolicy/rbacpolicy.go (about)

     1  package rbacpolicy
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/golang-jwt/jwt/v4"
     7  	log "github.com/sirupsen/logrus"
     8  
     9  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    10  	applister "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
    11  	jwtutil "github.com/argoproj/argo-cd/v2/util/jwt"
    12  	"github.com/argoproj/argo-cd/v2/util/rbac"
    13  )
    14  
    15  const (
    16  	// please add new items to Resources
    17  	ResourceClusters        = "clusters"
    18  	ResourceProjects        = "projects"
    19  	ResourceApplications    = "applications"
    20  	ResourceApplicationSets = "applicationsets"
    21  	ResourceRepositories    = "repositories"
    22  	ResourceCertificates    = "certificates"
    23  	ResourceAccounts        = "accounts"
    24  	ResourceGPGKeys         = "gpgkeys"
    25  	ResourceLogs            = "logs"
    26  	ResourceExec            = "exec"
    27  	ResourceExtensions      = "extensions"
    28  
    29  	// please add new items to Actions
    30  	ActionGet      = "get"
    31  	ActionCreate   = "create"
    32  	ActionUpdate   = "update"
    33  	ActionDelete   = "delete"
    34  	ActionSync     = "sync"
    35  	ActionOverride = "override"
    36  	ActionAction   = "action"
    37  	ActionInvoke   = "invoke"
    38  )
    39  
    40  var (
    41  	defaultScopes = []string{"groups"}
    42  	Resources     = []string{
    43  		ResourceClusters,
    44  		ResourceProjects,
    45  		ResourceApplications,
    46  		ResourceApplicationSets,
    47  		ResourceRepositories,
    48  		ResourceCertificates,
    49  		ResourceLogs,
    50  		ResourceExec,
    51  	}
    52  	Actions = []string{
    53  		ActionGet,
    54  		ActionCreate,
    55  		ActionUpdate,
    56  		ActionDelete,
    57  		ActionSync,
    58  		ActionOverride,
    59  	}
    60  )
    61  
    62  // RBACPolicyEnforcer provides an RBAC Claims Enforcer which additionally consults AppProject
    63  // roles, jwt tokens, and groups. It is backed by a AppProject informer/lister cache and does not
    64  // make any API calls during enforcement.
    65  type RBACPolicyEnforcer struct {
    66  	enf        *rbac.Enforcer
    67  	projLister applister.AppProjectNamespaceLister
    68  	scopes     []string
    69  }
    70  
    71  // NewRBACPolicyEnforcer returns a new RBAC Enforcer for the Argo CD API Server
    72  func NewRBACPolicyEnforcer(enf *rbac.Enforcer, projLister applister.AppProjectNamespaceLister) *RBACPolicyEnforcer {
    73  	return &RBACPolicyEnforcer{
    74  		enf:        enf,
    75  		projLister: projLister,
    76  		scopes:     nil,
    77  	}
    78  }
    79  
    80  func (p *RBACPolicyEnforcer) SetScopes(scopes []string) {
    81  	p.scopes = scopes
    82  }
    83  
    84  func (p *RBACPolicyEnforcer) GetScopes() []string {
    85  	scopes := p.scopes
    86  	if scopes == nil {
    87  		scopes = defaultScopes
    88  	}
    89  	return scopes
    90  }
    91  
    92  func IsProjectSubject(subject string) bool {
    93  	_, _, ok := GetProjectRoleFromSubject(subject)
    94  	return ok
    95  }
    96  
    97  func GetProjectRoleFromSubject(subject string) (string, string, bool) {
    98  	parts := strings.Split(subject, ":")
    99  	if len(parts) == 3 && parts[0] == "proj" {
   100  		return parts[1], parts[2], true
   101  	}
   102  	return "", "", false
   103  }
   104  
   105  // EnforceClaims is an RBAC claims enforcer specific to the Argo CD API server
   106  func (p *RBACPolicyEnforcer) EnforceClaims(claims jwt.Claims, rvals ...interface{}) bool {
   107  	mapClaims, err := jwtutil.MapClaims(claims)
   108  	if err != nil {
   109  		return false
   110  	}
   111  
   112  	subject := jwtutil.StringField(mapClaims, "sub")
   113  	// Check if the request is for an application resource. We have special enforcement which takes
   114  	// into consideration the project's token and group bindings
   115  	var runtimePolicy string
   116  	var projName string
   117  	proj := p.getProjectFromRequest(rvals...)
   118  	if proj != nil {
   119  		if IsProjectSubject(subject) {
   120  			return p.enforceProjectToken(subject, proj, rvals...)
   121  		}
   122  		runtimePolicy = proj.ProjectPoliciesString()
   123  		projName = proj.Name
   124  	}
   125  
   126  	// NOTE: This calls prevent multiple creation of the wrapped enforcer
   127  	enforcer := p.enf.CreateEnforcerWithRuntimePolicy(projName, runtimePolicy)
   128  
   129  	// Check the subject. This is typically the 'admin' case.
   130  	// NOTE: the call to EnforceWithCustomEnforcer will also consider the default role
   131  	vals := append([]interface{}{subject}, rvals[1:]...)
   132  	if p.enf.EnforceWithCustomEnforcer(enforcer, vals...) {
   133  		return true
   134  	}
   135  
   136  	scopes := p.scopes
   137  	if scopes == nil {
   138  		scopes = defaultScopes
   139  	}
   140  	// Finally check if any of the user's groups grant them permissions
   141  	groups := jwtutil.GetScopeValues(mapClaims, scopes)
   142  
   143  	// Get groups to reduce the amount to checking groups
   144  	groupingPolicies := enforcer.GetGroupingPolicy()
   145  	for gidx := range groups {
   146  		for gpidx := range groupingPolicies {
   147  			// Prefilter user groups by groups defined in the model
   148  			if groupingPolicies[gpidx][0] == groups[gidx] {
   149  				vals := append([]interface{}{groups[gidx]}, rvals[1:]...)
   150  				if p.enf.EnforceWithCustomEnforcer(enforcer, vals...) {
   151  					return true
   152  				}
   153  				break
   154  			}
   155  		}
   156  	}
   157  	logCtx := log.WithFields(log.Fields{"claims": claims, "rval": rvals, "subject": subject, "groups": groups, "project": projName, "scopes": scopes})
   158  	logCtx.Debug("enforce failed")
   159  	return false
   160  }
   161  
   162  // getProjectFromRequest parses the project name from the RBAC request and returns the associated
   163  // project (if it exists)
   164  func (p *RBACPolicyEnforcer) getProjectFromRequest(rvals ...interface{}) *v1alpha1.AppProject {
   165  	if len(rvals) != 4 {
   166  		return nil
   167  	}
   168  	getProjectByName := func(projName string) *v1alpha1.AppProject {
   169  		proj, err := p.projLister.Get(projName)
   170  		if err != nil {
   171  			return nil
   172  		}
   173  		return proj
   174  	}
   175  	if res, ok := rvals[1].(string); ok {
   176  		if obj, ok := rvals[3].(string); ok {
   177  			switch res {
   178  			case ResourceApplications, ResourceRepositories, ResourceClusters, ResourceLogs, ResourceExec:
   179  				if objSplit := strings.Split(obj, "/"); len(objSplit) >= 2 {
   180  					return getProjectByName(objSplit[0])
   181  				}
   182  			case ResourceProjects:
   183  				// we also automatically give project tokens and groups 'get' access to the project
   184  				return getProjectByName(obj)
   185  			}
   186  		}
   187  	}
   188  	return nil
   189  }
   190  
   191  // enforceProjectToken will check to see the valid token has not yet been revoked in the project
   192  func (p *RBACPolicyEnforcer) enforceProjectToken(subject string, proj *v1alpha1.AppProject, rvals ...interface{}) bool {
   193  	subjectSplit := strings.Split(subject, ":")
   194  	if len(subjectSplit) != 3 {
   195  		return false
   196  	}
   197  	projName, _ := subjectSplit[1], subjectSplit[2]
   198  	if projName != proj.Name {
   199  		// this should never happen (we generated a project token for a different project)
   200  		return false
   201  	}
   202  
   203  	vals := append([]interface{}{subject}, rvals[1:]...)
   204  	return p.enf.EnforceRuntimePolicy(proj.Name, proj.ProjectPoliciesString(), vals...)
   205  }