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 }