Skip to content

Commit 1792b25

Browse files
committed
Tests for aggregated cluster roles
1 parent d7ac386 commit 1792b25

File tree

8 files changed

+3094
-32
lines changed

8 files changed

+3094
-32
lines changed

.golangci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ linters-settings:
3131
run:
3232
tests: true
3333
relative-path-mode: wd
34-
build-tags: [validation,"2.11"]
34+
build-tags: [validation,"2.12"]
3535
timeout: 8m
3636
issues:
3737
max-issues-per-linter: 0

actions/rbac/rbac.go

Lines changed: 120 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,46 @@ import (
2424
type Role string
2525

2626
const (
27-
Admin Role = "admin"
28-
BaseUser Role = "user-base"
29-
StandardUser Role = "user"
30-
ClusterOwner Role = "cluster-owner"
31-
ClusterMember Role = "cluster-member"
32-
ProjectOwner Role = "project-owner"
33-
ProjectMember Role = "project-member"
34-
CreateNS Role = "create-ns"
35-
ReadOnly Role = "read-only"
36-
CustomManageProjectMember Role = "projectroletemplatebindings-manage"
37-
CrtbView Role = "clusterroletemplatebindings-view"
38-
PrtbView Role = "projectroletemplatebindings-view"
39-
ProjectsCreate Role = "projects-create"
40-
ProjectsView Role = "projects-view"
41-
ManageWorkloads Role = "workloads-manage"
42-
ActiveStatus = "active"
43-
ForbiddenError = "403 Forbidden"
44-
DefaultNamespace = "fleet-default"
45-
LocalCluster = "local"
46-
UserKind = "User"
47-
ImageName = "nginx"
48-
ManageUsersVerb = "manage-users"
49-
ManagementAPIGroup = "management.cattle.io"
50-
UsersResource = "users"
51-
UserAttributeResource = "userattribute"
52-
GroupsResource = "groups"
53-
GroupMembersResource = "groupmembers"
54-
PrtbResource = "projectroletemplatebindings"
55-
SecretsResource = "secrets"
56-
ClusterContext = "cluster"
57-
ProjectContext = "project"
27+
Admin Role = "admin"
28+
BaseUser Role = "user-base"
29+
StandardUser Role = "user"
30+
ClusterOwner Role = "cluster-owner"
31+
ClusterMember Role = "cluster-member"
32+
ProjectOwner Role = "project-owner"
33+
ProjectMember Role = "project-member"
34+
CreateNS Role = "create-ns"
35+
ReadOnly Role = "read-only"
36+
CustomManageProjectMember Role = "projectroletemplatebindings-manage"
37+
CrtbView Role = "clusterroletemplatebindings-view"
38+
PrtbView Role = "projectroletemplatebindings-view"
39+
ProjectsCreate Role = "projects-create"
40+
ProjectsView Role = "projects-view"
41+
ManageWorkloads Role = "workloads-manage"
42+
ActiveStatus = "active"
43+
ForbiddenError = "403 Forbidden"
44+
DefaultNamespace = "fleet-default"
45+
LocalCluster = "local"
46+
UserKind = "User"
47+
ImageName = "nginx"
48+
ManageUsersVerb = "manage-users"
49+
ManagementAPIGroup = "management.cattle.io"
50+
RkeCattleAPIGroup = "rke.cattle.io"
51+
ProjectCattleAPIGroup = "project.cattle.io"
52+
AppsAPIGroup = "apps"
53+
UsersResource = "users"
54+
UserAttributeResource = "userattribute"
55+
GroupsResource = "groups"
56+
GroupMembersResource = "groupmembers"
57+
PrtbResource = "projectroletemplatebindings"
58+
SecretsResource = "secrets"
59+
ClusterContext = "cluster"
60+
ProjectContext = "project"
61+
CrtbOwnerLabel = "authz.cluster.cattle.io/crtb-owner"
62+
PrtbOwnerLabel = "authz.cluster.cattle.io/prtb-owner"
63+
ClusterNameAnnotationKey = "cluster.cattle.io/name"
64+
RegularResourceAggregator = "-aggregator"
65+
ClusterMgmtResourceAggregator = "-cluster-mgmt-aggregator"
66+
ProjectMgmtResourceAggregator = "-project-mgmt-aggregator"
5867
)
5968

6069
func (r Role) String() string {
@@ -525,3 +534,83 @@ func GetRoleTemplateContext(client *rancher.Client, roleTemplateName string) (st
525534

526535
return roleTemplate.Context, nil
527536
}
537+
538+
// DeleteClusterRoleTemplateBinding deletes the cluster role template binding using wrangler context
539+
func DeleteClusterRoleTemplateBinding(client *rancher.Client, crtbNamespace, crtbName string) error {
540+
err := client.WranglerContext.Mgmt.ClusterRoleTemplateBinding().Delete(crtbNamespace, crtbName, &metav1.DeleteOptions{})
541+
if err != nil {
542+
return fmt.Errorf("failed to delete ClusterRoleTemplateBinding %s: %w", crtbName, err)
543+
}
544+
545+
err = kwait.PollUntilContextTimeout(context.TODO(), defaults.FiveHundredMillisecondTimeout, defaults.OneMinuteTimeout, false, func(ctx context.Context) (done bool, err error) {
546+
_, err = client.WranglerContext.Mgmt.ClusterRoleTemplateBinding().Get(crtbNamespace, crtbName, metav1.GetOptions{})
547+
548+
if apierrors.IsNotFound(err) {
549+
return true, nil
550+
}
551+
552+
if err != nil {
553+
return false, fmt.Errorf("error checking CRTB deletion status: %w", err)
554+
}
555+
556+
return false, nil
557+
})
558+
559+
if err != nil {
560+
return fmt.Errorf("timed out waiting for ClusterRoleTemplateBinding %s to be deleted: %w", crtbName, err)
561+
}
562+
563+
return nil
564+
}
565+
566+
// DeleteProjectRoleTemplateBinding deletes the project role template binding using wrangler context
567+
func DeleteProjectRoleTemplateBinding(client *rancher.Client, prtbNamespace, prtbName string) error {
568+
err := client.WranglerContext.Mgmt.ProjectRoleTemplateBinding().Delete(prtbNamespace, prtbName, &metav1.DeleteOptions{})
569+
if err != nil {
570+
return fmt.Errorf("failed to delete ProjectRoleTemplateBinding %s: %w", prtbName, err)
571+
}
572+
573+
err = kwait.PollUntilContextTimeout(context.TODO(), defaults.FiveHundredMillisecondTimeout, defaults.OneMinuteTimeout, false, func(ctx context.Context) (done bool, err error) {
574+
_, err = client.WranglerContext.Mgmt.ProjectRoleTemplateBinding().Get(prtbNamespace, prtbName, metav1.GetOptions{})
575+
576+
if apierrors.IsNotFound(err) {
577+
return true, nil
578+
}
579+
580+
if err != nil {
581+
return false, fmt.Errorf("error checking PRTB deletion status: %w", err)
582+
}
583+
584+
return false, nil
585+
})
586+
587+
if err != nil {
588+
return fmt.Errorf("timed out waiting for ProjectRoleTemplateBinding %s to be deleted: %w", prtbName, err)
589+
}
590+
591+
return nil
592+
}
593+
594+
// UpdateRoleTemplateInheritance updates the inheritance of a role template using wrangler context
595+
func UpdateRoleTemplateInheritance(client *rancher.Client, roleTemplateName string, inheritedRoles []*v3.RoleTemplate) (*v3.RoleTemplate, error) {
596+
var roleTemplateNames []string
597+
for _, inheritedRole := range inheritedRoles {
598+
if inheritedRole != nil {
599+
roleTemplateNames = append(roleTemplateNames, inheritedRole.Name)
600+
}
601+
}
602+
603+
existingRoleTemplate, err := GetRoleTemplateByName(client, roleTemplateName)
604+
if err != nil {
605+
return nil, fmt.Errorf("failed to get existing RoleTemplate: %w", err)
606+
}
607+
608+
existingRoleTemplate.RoleTemplateNames = roleTemplateNames
609+
610+
updatedRoleTemplate, err := client.WranglerContext.Mgmt.RoleTemplate().Update(existingRoleTemplate)
611+
if err != nil {
612+
return nil, fmt.Errorf("failed to update RoleTemplate inheritance: %w", err)
613+
}
614+
615+
return GetRoleTemplateByName(client, updatedRoleTemplate.Name)
616+
}

actions/rbac/verify.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/rancher/shepherd/extensions/clusters"
1515
"github.com/rancher/shepherd/extensions/users"
1616
namegen "github.com/rancher/shepherd/pkg/namegenerator"
17+
"github.com/rancher/shepherd/pkg/wrangler"
18+
clusterapi "github.com/rancher/tests/actions/kubeapi/clusters"
1719
namespacesapi "github.com/rancher/tests/actions/kubeapi/namespaces"
1820
projectsapi "github.com/rancher/tests/actions/kubeapi/projects"
1921
rbacapi "github.com/rancher/tests/actions/kubeapi/rbac"
@@ -24,6 +26,7 @@ import (
2426
coreV1 "k8s.io/api/core/v1"
2527
apierrors "k8s.io/apimachinery/pkg/api/errors"
2628
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/types"
2730
)
2831

2932
// VerifyGlobalRoleBindingsForUser validates that a global role bindings is created for a user when the user is created
@@ -286,3 +289,201 @@ func VerifyProjectRoleTemplateBindingForUser(client *rancher.Client, username st
286289

287290
return userPrtbs, nil
288291
}
292+
293+
// VerifyUserPermission validates that a user has the expected permissions for a given resource
294+
func VerifyUserPermission(client *rancher.Client, clusterID string, user *management.User, verb, resourceType, namespaceName, resourceName string, expected, isCRDInLocalCluster bool) error {
295+
allowed, err := CheckUserAccess(client, clusterID, user, verb, resourceType, namespaceName, resourceName, isCRDInLocalCluster)
296+
297+
if expected {
298+
if err != nil {
299+
if apierrors.IsForbidden(err) {
300+
return fmt.Errorf("user should have '%s' access to %s/%s/%s, but got forbidden error: %v", verb, resourceType, namespaceName, resourceName, err)
301+
}
302+
return fmt.Errorf("error verifying user access to %s/%s/%s: %v", resourceType, namespaceName, resourceName, err)
303+
}
304+
if !allowed {
305+
return fmt.Errorf("user should have '%s' access to %s/%s/%s, but access was denied", verb, resourceType, namespaceName, resourceName)
306+
}
307+
} else {
308+
if err == nil && allowed {
309+
return fmt.Errorf("expected '%s' access to %s/%s/%s to be denied, but access was granted", verb, resourceType, namespaceName, resourceName)
310+
}
311+
if err != nil && !apierrors.IsForbidden(err) {
312+
return fmt.Errorf("expected forbidden error for %s/%s/%s, but got: %v", resourceType, namespaceName, resourceName, err)
313+
}
314+
}
315+
316+
return nil
317+
}
318+
319+
// CheckUserAccess checks if a user has the specified access to a resource in a cluster. It returns true if the user has access, false otherwise.
320+
func CheckUserAccess(client *rancher.Client, clusterID string, user *management.User, verb, resourceType, namespaceName, resourceName string, isCRDInLocalCluster bool) (bool, error) {
321+
userClient, err := client.AsUser(user)
322+
if err != nil {
323+
return false, fmt.Errorf("failed to create user client: %w", err)
324+
}
325+
326+
var userContext *wrangler.Context
327+
if isCRDInLocalCluster {
328+
userContext, err = clusterapi.GetClusterWranglerContext(userClient, rbacapi.LocalCluster)
329+
} else {
330+
userContext, err = clusterapi.GetClusterWranglerContext(userClient, clusterID)
331+
}
332+
333+
if err != nil {
334+
return false, fmt.Errorf("failed to get user context: %w", err)
335+
}
336+
337+
switch resourceType {
338+
case "projects":
339+
return CheckProjectAccess(userContext, verb, clusterID, resourceName)
340+
case "namespaces":
341+
return CheckNamespaceAccess(userContext, verb, resourceName)
342+
case "deployments":
343+
return CheckDeploymentAccess(userContext, verb, namespaceName, resourceName)
344+
case "pods":
345+
return CheckPodAccess(userContext, verb, namespaceName, resourceName)
346+
case "secrets":
347+
return CheckSecretAccess(userContext, verb, namespaceName, resourceName)
348+
case "projectroletemplatebindings":
349+
return CheckPrtbAccess(userContext, verb, namespaceName, resourceName)
350+
default:
351+
return false, fmt.Errorf("checks for resource type '%s' not added", resourceType)
352+
}
353+
}
354+
355+
// CheckProjectAccess checks if a user has the specified access to a project in a cluster. It returns true if the user has access, false otherwise.
356+
func CheckProjectAccess(userContext *wrangler.Context, verb, clusterID, projectName string) (bool, error) {
357+
switch verb {
358+
case "get":
359+
_, err := userContext.Mgmt.Project().Get(clusterID, projectName, metav1.GetOptions{})
360+
return err == nil, err
361+
case "list":
362+
_, err := userContext.Mgmt.Project().List(clusterID, metav1.ListOptions{})
363+
return err == nil, err
364+
case "create":
365+
projectTemplate := projectsapi.NewProjectTemplate(clusterID)
366+
_, err := userContext.Mgmt.Project().Create(projectTemplate)
367+
return err == nil, err
368+
case "delete":
369+
err := userContext.Mgmt.Project().Delete(clusterID, projectName, &metav1.DeleteOptions{})
370+
return err == nil, err
371+
case "update":
372+
project, err := userContext.Mgmt.Project().Get(clusterID, projectName, metav1.GetOptions{})
373+
if err != nil {
374+
return false, err
375+
}
376+
if project.Labels == nil {
377+
project.Labels = make(map[string]string)
378+
}
379+
project.Labels["hello"] = "world"
380+
_, err = userContext.Mgmt.Project().Update(project)
381+
return err == nil, err
382+
case "patch":
383+
patchData := []byte(`{"metadata":{"annotations":{"patched":"true"}}}`)
384+
_, err := userContext.Mgmt.Project().Patch(clusterID, projectName, types.MergePatchType, patchData)
385+
return err == nil, err
386+
default:
387+
return false, fmt.Errorf("verb '%s' not available in checks for projects", verb)
388+
}
389+
}
390+
391+
// CheckNamespaceAccess checks if a user has the specified access to a namespace in a cluster. It returns true if the user has access, false otherwise.
392+
func CheckNamespaceAccess(userContext *wrangler.Context, verb, namespaceName string) (bool, error) {
393+
switch verb {
394+
case "get":
395+
_, err := userContext.Core.Namespace().Get(namespaceName, metav1.GetOptions{})
396+
return err == nil, err
397+
case "list":
398+
_, err := userContext.Core.Namespace().List(metav1.ListOptions{})
399+
return err == nil, err
400+
case "delete":
401+
err := userContext.Core.Namespace().Delete(namespaceName, &metav1.DeleteOptions{})
402+
return err == nil, err
403+
default:
404+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'namespaces'", verb)
405+
}
406+
}
407+
408+
// CheckPodAccess checks if a user has the specified access to a pod in a namespace. It returns true if the user has access, false otherwise.
409+
func CheckPodAccess(userContext *wrangler.Context, verb, namespaceName, podName string) (bool, error) {
410+
switch verb {
411+
case "get":
412+
_, err := userContext.Core.Pod().Get(namespaceName, podName, metav1.GetOptions{})
413+
return err == nil, err
414+
case "list":
415+
_, err := userContext.Core.Pod().List(namespaceName, metav1.ListOptions{})
416+
return err == nil, err
417+
case "delete":
418+
err := userContext.Core.Pod().Delete(namespaceName, podName, &metav1.DeleteOptions{})
419+
return err == nil, err
420+
default:
421+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'pods'", verb)
422+
}
423+
}
424+
425+
// CheckDeploymentAccess checks if a user has the specified access to a deployment in a namespace. It returns true if the user has access, false otherwise.
426+
func CheckDeploymentAccess(userContext *wrangler.Context, verb, namespaceName, deploymentName string) (bool, error) {
427+
switch verb {
428+
case "get":
429+
_, err := userContext.Apps.Deployment().Get(namespaceName, deploymentName, metav1.GetOptions{})
430+
return err == nil, err
431+
case "list":
432+
_, err := userContext.Apps.Deployment().List(namespaceName, metav1.ListOptions{})
433+
return err == nil, err
434+
case "delete":
435+
err := userContext.Apps.Deployment().Delete(namespaceName, deploymentName, &metav1.DeleteOptions{})
436+
return err == nil, err
437+
default:
438+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'deployments'", verb)
439+
}
440+
}
441+
442+
// CheckSecretAccess checks if a user has the specified access to a secret in a namespace. It returns true if the user has access, false otherwise.
443+
func CheckSecretAccess(userContext *wrangler.Context, verb, namespaceName, secretName string) (bool, error) {
444+
switch verb {
445+
case "get":
446+
_, err := userContext.Core.Secret().Get(namespaceName, secretName, metav1.GetOptions{})
447+
return err == nil, err
448+
case "list":
449+
_, err := userContext.Core.Secret().List(namespaceName, metav1.ListOptions{})
450+
return err == nil, err
451+
case "delete":
452+
err := userContext.Core.Secret().Delete(namespaceName, secretName, &metav1.DeleteOptions{})
453+
return err == nil, err
454+
default:
455+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'namespaces'", verb)
456+
}
457+
}
458+
459+
// CheckPrtbAccess checks if a user has the specified access to a project role template binding in a namespace. It returns true if the user has access, false otherwise.
460+
func CheckPrtbAccess(userContext *wrangler.Context, verb, prtbNamespace, prtbName string) (bool, error) {
461+
switch verb {
462+
case "get":
463+
_, err := userContext.Mgmt.ProjectRoleTemplateBinding().Get(prtbNamespace, prtbName, metav1.GetOptions{})
464+
return err == nil, err
465+
case "list":
466+
_, err := userContext.Mgmt.ProjectRoleTemplateBinding().List(prtbNamespace, metav1.ListOptions{})
467+
return err == nil, err
468+
case "delete":
469+
err := userContext.Mgmt.ProjectRoleTemplateBinding().Delete(prtbNamespace, prtbName, &metav1.DeleteOptions{})
470+
return err == nil, err
471+
case "update":
472+
prtb, err := userContext.Mgmt.ProjectRoleTemplateBinding().Get(prtbNamespace, prtbName, metav1.GetOptions{})
473+
if err != nil {
474+
return false, err
475+
}
476+
if prtb.Labels == nil {
477+
prtb.Labels = make(map[string]string)
478+
}
479+
prtb.Labels["hello"] = "world"
480+
_, err = userContext.Mgmt.ProjectRoleTemplateBinding().Update(prtb)
481+
return err == nil, err
482+
case "patch":
483+
patchData := []byte(`{"metadata":{"annotations":{"patched":"true"}}}`)
484+
_, err := userContext.Mgmt.ProjectRoleTemplateBinding().Patch(prtbNamespace, prtbName, types.MergePatchType, patchData)
485+
return err == nil, err
486+
default:
487+
return false, fmt.Errorf("verb '%s' not available in checks for prtbs", verb)
488+
}
489+
}

0 commit comments

Comments
 (0)