Skip to content

Commit

Permalink
Argo CD CLI Admin Setting RBAC Can AppProject Roles Support
Browse files Browse the repository at this point in the history
Signed-off-by: Brian Dunnigan <[email protected]>
  • Loading branch information
bdun1013 committed Dec 16, 2024
1 parent 433b317 commit 728a198
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 118 deletions.
106 changes: 83 additions & 23 deletions cmd/argocd/commands/admin/settings_rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ import (
"sigs.k8s.io/yaml"

"github.com/argoproj/argo-cd/v2/common"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
appclient "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/typed/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/glob"
"github.com/argoproj/argo-cd/v2/util/rbac"
)

Expand Down Expand Up @@ -140,6 +143,7 @@ func NewRBACCanCommand(cmdCtx commandContext) *cobra.Command {
useBuiltin bool
strict bool
quiet bool
project string
subject string
action string
resource string
Expand All @@ -162,9 +166,10 @@ argocd admin settings rbac can some:role create application 'default/app' --poli
# i.e. 'policy.csv' and (optionally) 'policy.default'
argocd admin settings rbac can some:role create application 'default/app' --policy-file argocd-rbac-cm.yaml
# If --policy-file is not given, the ConfigMap 'argocd-rbac-cm' from K8s is
# used. You need to specify the argocd namespace, and make sure that your
# current Kubernetes context is pointing to the cluster Argo CD is running in
# If --policy-file is not given, the ConfigMap 'argocd-rbac-cm' and
# applicable AppProject roles from K8s are used. You need to specify
# the argocd namespace, and make sure that your current Kubernetes context
# is pointing to the cluster Argo CD is running in
argocd admin settings rbac can some:role create application 'default/app' --namespace argocd
# You can override a possibly configured default role
Expand All @@ -180,11 +185,25 @@ argocd admin settings rbac can someuser create application 'default/app' --defau
}
subject = args[0]
action = args[1]
resource = args[2]
// User could have used a mutation of the resource name (i.e. 'cert' for
// 'certificate') - let's resolve it to the valid resource.
resource = resolveRBACResourceName(args[2])
if len(args) > 3 {
subResource = args[3]
// Some project scoped resources have a special notation - for simplicity's sake,
// if user gives no sub-resource (or specifies simple '*'), we construct
// the required notation by setting subresource to '*/*'.
if projectScoped[resource] {
if subResource == "*" || subResource == "" {
subResource = "*/*"
}
}
}

userPolicy := ""
builtinPolicy := ""
projectPolicies := ""

namespace, nsOverride, err := clientConfig.Namespace()
if err != nil {
log.Fatalf("could not create k8s client: %v", err)
Expand All @@ -205,10 +224,11 @@ argocd admin settings rbac can someuser create application 'default/app' --defau
log.Fatalf("could not create k8s client: %v", err)
}

appclients := appclientset.NewForConfigOrDie(restConfig)

userPolicy, newDefaultRole, matchMode := getPolicy(ctx, policyFile, realClientset, namespace)

// Use built-in policy as augmentation if requested
builtinPolicy := ""
if useBuiltin {
builtinPolicy = assets.BuiltinPolicyCSV
}
Expand All @@ -226,7 +246,7 @@ argocd admin settings rbac can someuser create application 'default/app' --defau
// even if there is no explicit RBAC allow, or if there is an explicit RBAC deny)
var isLogRbacEnforced func() bool
if nsOverride && policyFile == "" {
if resolveRBACResourceName(resource) == rbacpolicy.ResourceLogs {
if resource == rbacpolicy.ResourceLogs {
isLogRbacEnforced = func() bool {
if opts, ok := cmdCtx.(*settingsOpts); ok {
opts.loadClusterSettings = true
Expand All @@ -241,8 +261,22 @@ argocd admin settings rbac can someuser create application 'default/app' --defau
}
}
}
res := checkPolicy(subject, action, resource, subResource, builtinPolicy, userPolicy, defaultRole, matchMode, strict, isLogRbacEnforced)

if nsOverride && policyFile == "" {
projIf := appclients.ArgoprojV1alpha1().AppProjects(namespace)
if resource == rbacpolicy.ResourceProjects {
if subResource == "" {
project = "*"
} else {
project = subResource
}
} else if subResource != "" && strings.Contains(subResource, "/") {
project = strings.Split(subResource, "/")[0]
}
projectPolicies = getProjectPolicies(ctx, projIf, project)
}

res := checkPolicy(project, subject, action, resource, subResource, builtinPolicy, userPolicy, projectPolicies, defaultRole, matchMode, strict, isLogRbacEnforced)
if res {
if !quiet {
fmt.Println("Yes")
Expand Down Expand Up @@ -409,9 +443,36 @@ func getPolicyConfigMap(ctx context.Context, client kubernetes.Interface, namesp
return cm, nil
}

// getProjectPolicies fetches the RBAC AppProject Roles from K8s cluster for the project
func getProjectPolicies(ctx context.Context, projIf appclient.AppProjectInterface, project string) (projectPolicies string) {
var projectPoliciesBuilder strings.Builder

if project != "" {
if strings.Contains(project, "*") {
projs, err := projIf.List(ctx, v1.ListOptions{})
errors.CheckError(err)
for _, proj := range projs.Items {
if glob.Match(project, proj.Name) {
projectPoliciesBuilder.WriteString(proj.ProjectPoliciesString())
projectPoliciesBuilder.WriteString("\n")
}
}
} else {
proj, err := projIf.Get(ctx, project, v1.GetOptions{})
errors.CheckError(err)
projectPoliciesBuilder.WriteString(proj.ProjectPoliciesString())
projectPoliciesBuilder.WriteString("\n")
}
}

projectPolicies = projectPoliciesBuilder.String()

return projectPolicies
}

// checkPolicy checks whether given subject is allowed to execute specified
// action against specified resource
func checkPolicy(subject, action, resource, subResource, builtinPolicy, userPolicy, defaultRole, matchMode string, strict bool, isLogRbacEnforced func() bool) bool {
func checkPolicy(project, subject, action, resource, subResource, builtinPolicy, userPolicy, projectPolicies, defaultRole, matchMode string, strict bool, isLogRbacEnforced func() bool) bool {
enf := rbac.NewEnforcer(nil, "argocd", "argocd-rbac-cm", nil)
enf.SetDefaultRole(defaultRole)
enf.SetMatchMode(matchMode)
Expand All @@ -431,34 +492,33 @@ func checkPolicy(subject, action, resource, subResource, builtinPolicy, userPoli
return false
}
}

// User could have used a mutation of the resource name (i.e. 'cert' for
// 'certificate') - let's resolve it to the valid resource.
realResource := resolveRBACResourceName(resource)
if project != "" && projectPolicies != "" {
if err := rbac.ValidatePolicy(projectPolicies); err != nil {
log.Fatalf("invalid project policy: %v", err)
return false
}
}

// If in strict mode, validate that given RBAC resource and action are
// actually valid tokens.
if strict {
if err := validateRBACResourceAction(realResource, action); err != nil {
if err := validateRBACResourceAction(resource, action); err != nil {
log.Fatalf("error in RBAC request: %v", err)
return false
}
}

// Some project scoped resources have a special notation - for simplicity's sake,
// if user gives no sub-resource (or specifies simple '*'), we construct
// the required notation by setting subresource to '*/*'.
if projectScoped[realResource] {
if subResource == "*" || subResource == "" {
subResource = "*/*"
}
}
if realResource == rbacpolicy.ResourceLogs {
if resource == rbacpolicy.ResourceLogs {
if isLogRbacEnforced != nil && !isLogRbacEnforced() {
return true
}
}
return enf.Enforce(subject, realResource, action, subResource)

if project != "" && projectPolicies != "" {
return enf.EnforceRuntimePolicy(project, projectPolicies, subject, resource, action, subResource)
} else {
return enf.Enforce(subject, resource, action, subResource)
}
}

// resolveRBACResourceName resolves a user supplied value to a valid RBAC
Expand Down
Loading

0 comments on commit 728a198

Please sign in to comment.