Skip to content

Commit f74c5c0

Browse files
committed
Tests for aggregated cluster roles
1 parent d27ff46 commit f74c5c0

File tree

7 files changed

+3398
-65
lines changed

7 files changed

+3398
-65
lines changed

actions/projects/projects.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func CreateNamespaceUsingWrangler(client *rancher.Client, clusterID, projectName
167167

168168
// WaitForProjectFinalizerToUpdate is a helper to wait for project finalizer to update to match the expected finalizer count
169169
func WaitForProjectFinalizerToUpdate(client *rancher.Client, projectName string, projectNamespace string, finalizerCount int) error {
170-
err := kwait.PollUntilContextTimeout(context.Background(), defaults.FiveSecondTimeout, defaults.TenSecondTimeout, false, func(ctx context.Context) (done bool, pollErr error) {
170+
err := kwait.PollUntilContextTimeout(context.Background(), defaults.FiveSecondTimeout, defaults.TenSecondTimeout, false, func(context.Context) (done bool, pollErr error) {
171171
project, pollErr := client.WranglerContext.Mgmt.Project().Get(projectNamespace, projectName, metav1.GetOptions{})
172172
if pollErr != nil {
173173
return false, pollErr
@@ -196,8 +196,13 @@ func WaitForProjectIDUpdate(client *rancher.Client, clusterID, projectName, name
196196
projectsapi.ProjectIDAnnotation: projectName,
197197
}
198198

199-
err := kwait.PollUntilContextTimeout(context.Background(), defaults.FiveSecondTimeout, defaults.OneMinuteTimeout, false, func(ctx context.Context) (done bool, pollErr error) {
200-
namespace, pollErr := namespaces.GetNamespaceByName(client, clusterID, namespaceName)
199+
downstreamContext, err := clusterapi.GetClusterWranglerContext(client, clusterID)
200+
if err != nil {
201+
return err
202+
}
203+
204+
err = kwait.PollUntilContextTimeout(context.Background(), defaults.FiveSecondTimeout, defaults.OneMinuteTimeout, false, func(context.Context) (done bool, pollErr error) {
205+
namespace, pollErr := downstreamContext.Core.Namespace().Get(namespaceName, metav1.GetOptions{})
201206
if pollErr != nil {
202207
return false, pollErr
203208
}

actions/rbac/rbac.go

Lines changed: 535 additions & 62 deletions
Large diffs are not rendered by default.

actions/rbac/verify.go

Lines changed: 220 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,220 @@ 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+
case "configmaps":
351+
return CheckConfigMapAccess(userContext, verb, namespaceName, resourceName)
352+
default:
353+
return false, fmt.Errorf("checks for resource type '%s' not added", resourceType)
354+
}
355+
}
356+
357+
// 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.
358+
func CheckProjectAccess(userContext *wrangler.Context, verb, clusterID, projectName string) (bool, error) {
359+
switch verb {
360+
case "get":
361+
_, err := userContext.Mgmt.Project().Get(clusterID, projectName, metav1.GetOptions{})
362+
return err == nil, err
363+
case "list":
364+
_, err := userContext.Mgmt.Project().List(clusterID, metav1.ListOptions{})
365+
return err == nil, err
366+
case "create":
367+
projectTemplate := projectsapi.NewProjectTemplate(clusterID)
368+
_, err := userContext.Mgmt.Project().Create(projectTemplate)
369+
return err == nil, err
370+
case "delete":
371+
err := userContext.Mgmt.Project().Delete(clusterID, projectName, &metav1.DeleteOptions{})
372+
return err == nil, err
373+
case "update":
374+
project, err := userContext.Mgmt.Project().Get(clusterID, projectName, metav1.GetOptions{})
375+
if err != nil {
376+
return false, err
377+
}
378+
if project.Labels == nil {
379+
project.Labels = make(map[string]string)
380+
}
381+
project.Labels["hello"] = "world"
382+
_, err = userContext.Mgmt.Project().Update(project)
383+
return err == nil, err
384+
case "patch":
385+
patchData := []byte(`{"metadata":{"annotations":{"patched":"true"}}}`)
386+
_, err := userContext.Mgmt.Project().Patch(clusterID, projectName, types.MergePatchType, patchData)
387+
return err == nil, err
388+
default:
389+
return false, fmt.Errorf("verb '%s' not available in checks for projects", verb)
390+
}
391+
}
392+
393+
// 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.
394+
func CheckNamespaceAccess(userContext *wrangler.Context, verb, namespaceName string) (bool, error) {
395+
switch verb {
396+
case "get":
397+
_, err := userContext.Core.Namespace().Get(namespaceName, metav1.GetOptions{})
398+
return err == nil, err
399+
case "list":
400+
_, err := userContext.Core.Namespace().List(metav1.ListOptions{})
401+
return err == nil, err
402+
case "delete":
403+
err := userContext.Core.Namespace().Delete(namespaceName, &metav1.DeleteOptions{})
404+
return err == nil, err
405+
default:
406+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'namespaces'", verb)
407+
}
408+
}
409+
410+
// 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.
411+
func CheckPodAccess(userContext *wrangler.Context, verb, namespaceName, podName string) (bool, error) {
412+
switch verb {
413+
case "get":
414+
_, err := userContext.Core.Pod().Get(namespaceName, podName, metav1.GetOptions{})
415+
return err == nil, err
416+
case "list":
417+
_, err := userContext.Core.Pod().List(namespaceName, metav1.ListOptions{})
418+
return err == nil, err
419+
case "delete":
420+
err := userContext.Core.Pod().Delete(namespaceName, podName, &metav1.DeleteOptions{})
421+
return err == nil, err
422+
default:
423+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'pods'", verb)
424+
}
425+
}
426+
427+
// 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.
428+
func CheckDeploymentAccess(userContext *wrangler.Context, verb, namespaceName, deploymentName string) (bool, error) {
429+
switch verb {
430+
case "get":
431+
_, err := userContext.Apps.Deployment().Get(namespaceName, deploymentName, metav1.GetOptions{})
432+
return err == nil, err
433+
case "list":
434+
_, err := userContext.Apps.Deployment().List(namespaceName, metav1.ListOptions{})
435+
return err == nil, err
436+
case "delete":
437+
err := userContext.Apps.Deployment().Delete(namespaceName, deploymentName, &metav1.DeleteOptions{})
438+
return err == nil, err
439+
default:
440+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'deployments'", verb)
441+
}
442+
}
443+
444+
// 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.
445+
func CheckSecretAccess(userContext *wrangler.Context, verb, namespaceName, secretName string) (bool, error) {
446+
switch verb {
447+
case "get":
448+
_, err := userContext.Core.Secret().Get(namespaceName, secretName, metav1.GetOptions{})
449+
return err == nil, err
450+
case "list":
451+
_, err := userContext.Core.Secret().List(namespaceName, metav1.ListOptions{})
452+
return err == nil, err
453+
case "delete":
454+
err := userContext.Core.Secret().Delete(namespaceName, secretName, &metav1.DeleteOptions{})
455+
return err == nil, err
456+
default:
457+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'namespaces'", verb)
458+
}
459+
}
460+
461+
// 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.
462+
func CheckPrtbAccess(userContext *wrangler.Context, verb, prtbNamespace, prtbName string) (bool, error) {
463+
switch verb {
464+
case "get":
465+
_, err := userContext.Mgmt.ProjectRoleTemplateBinding().Get(prtbNamespace, prtbName, metav1.GetOptions{})
466+
return err == nil, err
467+
case "list":
468+
_, err := userContext.Mgmt.ProjectRoleTemplateBinding().List(prtbNamespace, metav1.ListOptions{})
469+
return err == nil, err
470+
case "delete":
471+
err := userContext.Mgmt.ProjectRoleTemplateBinding().Delete(prtbNamespace, prtbName, &metav1.DeleteOptions{})
472+
return err == nil, err
473+
case "update":
474+
prtb, err := userContext.Mgmt.ProjectRoleTemplateBinding().Get(prtbNamespace, prtbName, metav1.GetOptions{})
475+
if err != nil {
476+
return false, err
477+
}
478+
if prtb.Labels == nil {
479+
prtb.Labels = make(map[string]string)
480+
}
481+
prtb.Labels["hello"] = "world"
482+
_, err = userContext.Mgmt.ProjectRoleTemplateBinding().Update(prtb)
483+
return err == nil, err
484+
case "patch":
485+
patchData := []byte(`{"metadata":{"annotations":{"patched":"true"}}}`)
486+
_, err := userContext.Mgmt.ProjectRoleTemplateBinding().Patch(prtbNamespace, prtbName, types.MergePatchType, patchData)
487+
return err == nil, err
488+
default:
489+
return false, fmt.Errorf("verb '%s' not available in checks for prtbs", verb)
490+
}
491+
}
492+
493+
// CheckConfigMapAccess checks if a user has the specified access to a ConfigMap in a namespace. It returns true if the user has access, false otherwise.
494+
func CheckConfigMapAccess(userContext *wrangler.Context, verb, namespaceName, configMapName string) (bool, error) {
495+
switch verb {
496+
case "get":
497+
_, err := userContext.Core.ConfigMap().Get(namespaceName, configMapName, metav1.GetOptions{})
498+
return err == nil, err
499+
case "list":
500+
_, err := userContext.Core.ConfigMap().List(namespaceName, metav1.ListOptions{})
501+
return err == nil, err
502+
case "delete":
503+
err := userContext.Core.ConfigMap().Delete(namespaceName, configMapName, &metav1.DeleteOptions{})
504+
return err == nil, err
505+
default:
506+
return false, fmt.Errorf("verb '%s' not available in checks for resource 'configmaps'", verb)
507+
}
508+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Aggregated Cluster Roles
2+
3+
## Pre-requisites
4+
5+
- Ensure you have an existing cluster that the user has access to. If you do not have a downstream cluster in Rancher, create one first before running this test.
6+
7+
## Test Setup
8+
9+
Your GO suite should be set to `-run ^Test<TestSuite>$`
10+
11+
- To run the CRTB tests in aggregated_cluster_roles_crtb_test.go, set the GO suite to `-run ^TestAggregatedClusterRolesCrtbTestSuite$`
12+
- To run the PRTB tests in aggregated_cluster_roles_prtb_test.go, set the GO suite to `-run ^TestAggregatedClusterRolesPrtbTestSuite$`
13+
- To run the CRTB tests in aggregated_cluster_roles_cleanup_test.go, set the GO suite to `-run ^TestAggregatedClusterRolesCleanupTestSuite$`
14+
15+
In your config file, set the following:
16+
17+
```yaml
18+
rancher:
19+
host: "rancher_server_address"
20+
adminToken: "rancher_admin_token"
21+
insecure: True #optional
22+
cleanup: True #optional
23+
clusterName: "cluster_name"
24+
```

0 commit comments

Comments
 (0)