github.com/argoproj/argo-cd/v3@v3.2.1/server/rbacpolicy/rbacpolicy.go (about) 1 package rbacpolicy 2 3 import ( 4 "strings" 5 6 "github.com/golang-jwt/jwt/v5" 7 log "github.com/sirupsen/logrus" 8 9 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 10 applister "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1" 11 jwtutil "github.com/argoproj/argo-cd/v3/util/jwt" 12 "github.com/argoproj/argo-cd/v3/util/rbac" 13 ) 14 15 // RBACPolicyEnforcer provides an RBAC Claims Enforcer which additionally consults AppProject 16 // roles, jwt tokens, and groups. It is backed by a AppProject informer/lister cache and does not 17 // make any API calls during enforcement. 18 type RBACPolicyEnforcer struct { 19 enf *rbac.Enforcer 20 projLister applister.AppProjectNamespaceLister 21 scopes []string 22 } 23 24 // NewRBACPolicyEnforcer returns a new RBAC Enforcer for the Argo CD API Server 25 func NewRBACPolicyEnforcer(enf *rbac.Enforcer, projLister applister.AppProjectNamespaceLister) *RBACPolicyEnforcer { 26 return &RBACPolicyEnforcer{ 27 enf: enf, 28 projLister: projLister, 29 scopes: nil, 30 } 31 } 32 33 func (p *RBACPolicyEnforcer) SetScopes(scopes []string) { 34 p.scopes = scopes 35 } 36 37 func (p *RBACPolicyEnforcer) GetScopes() []string { 38 scopes := p.scopes 39 if scopes == nil { 40 scopes = rbac.DefaultScopes 41 } 42 return scopes 43 } 44 45 func IsProjectSubject(subject string) bool { 46 _, _, ok := GetProjectRoleFromSubject(subject) 47 return ok 48 } 49 50 func GetProjectRoleFromSubject(subject string) (string, string, bool) { 51 parts := strings.Split(subject, ":") 52 if len(parts) == 3 && parts[0] == "proj" { 53 return parts[1], parts[2], true 54 } 55 return "", "", false 56 } 57 58 // EnforceClaims is an RBAC claims enforcer specific to the Argo CD API server 59 func (p *RBACPolicyEnforcer) EnforceClaims(claims jwt.Claims, rvals ...any) bool { 60 mapClaims, err := jwtutil.MapClaims(claims) 61 if err != nil { 62 return false 63 } 64 65 subject := jwtutil.GetUserIdentifier(mapClaims) 66 // Check if the request is for an application resource. We have special enforcement which takes 67 // into consideration the project's token and group bindings 68 var runtimePolicy string 69 var projName string 70 proj := p.getProjectFromRequest(rvals...) 71 if proj != nil { 72 if IsProjectSubject(subject) { 73 return p.enforceProjectToken(subject, proj, rvals...) 74 } 75 runtimePolicy = proj.ProjectPoliciesString() 76 projName = proj.Name 77 } 78 79 // NOTE: This calls prevent multiple creation of the wrapped enforcer 80 enforcer := p.enf.CreateEnforcerWithRuntimePolicy(projName, runtimePolicy) 81 82 // Check the subject. This is typically the 'admin' case. 83 // NOTE: the call to EnforceWithCustomEnforcer will also consider the default role 84 vals := append([]any{subject}, rvals[1:]...) 85 if p.enf.EnforceWithCustomEnforcer(enforcer, vals...) { 86 return true 87 } 88 89 scopes := p.scopes 90 if scopes == nil { 91 scopes = rbac.DefaultScopes 92 } 93 // Finally check if any of the user's groups grant them permissions 94 groups := jwtutil.GetScopeValues(mapClaims, scopes) 95 96 // Get groups to reduce the amount to checking groups 97 groupingPolicies, err := enforcer.GetGroupingPolicy() 98 if err != nil { 99 log.WithError(err).Error("failed to get grouping policy") 100 return false 101 } 102 for gidx := range groups { 103 for gpidx := range groupingPolicies { 104 // Prefilter user groups by groups defined in the model 105 if groupingPolicies[gpidx][0] == groups[gidx] { 106 vals := append([]any{groups[gidx]}, rvals[1:]...) 107 if p.enf.EnforceWithCustomEnforcer(enforcer, vals...) { 108 return true 109 } 110 break 111 } 112 } 113 } 114 logCtx := log.WithFields(log.Fields{"claims": claims, "rval": rvals, "subject": subject, "groups": groups, "project": projName, "scopes": scopes}) 115 logCtx.Debug("enforce failed") 116 return false 117 } 118 119 // getProjectFromRequest parses the project name from the RBAC request and returns the associated 120 // project (if it exists) 121 func (p *RBACPolicyEnforcer) getProjectFromRequest(rvals ...any) *v1alpha1.AppProject { 122 if len(rvals) != 4 { 123 return nil 124 } 125 getProjectByName := func(projName string) *v1alpha1.AppProject { 126 proj, err := p.projLister.Get(projName) 127 if err != nil { 128 return nil 129 } 130 return proj 131 } 132 if res, ok := rvals[1].(string); ok { 133 if obj, ok := rvals[3].(string); ok { 134 switch res { 135 case rbac.ResourceApplicationSets, rbac.ResourceApplications, rbac.ResourceRepositories, rbac.ResourceClusters, rbac.ResourceLogs, rbac.ResourceExec: 136 if objSplit := strings.Split(obj, "/"); len(objSplit) >= 2 { 137 return getProjectByName(objSplit[0]) 138 } 139 case rbac.ResourceProjects: 140 // we also automatically give project tokens and groups 'get' access to the project 141 return getProjectByName(obj) 142 } 143 } 144 } 145 return nil 146 } 147 148 // enforceProjectToken will check to see the valid token has not yet been revoked in the project 149 func (p *RBACPolicyEnforcer) enforceProjectToken(subject string, proj *v1alpha1.AppProject, rvals ...any) bool { 150 subjectSplit := strings.Split(subject, ":") 151 if len(subjectSplit) != 3 { 152 return false 153 } 154 projName, _ := subjectSplit[1], subjectSplit[2] 155 if projName != proj.Name { 156 // this should never happen (we generated a project token for a different project) 157 return false 158 } 159 160 vals := append([]any{subject}, rvals[1:]...) 161 return p.enf.EnforceRuntimePolicy(proj.Name, proj.ProjectPoliciesString(), vals...) 162 }