diff --git a/go.mod b/go.mod index a03a3cf7a24..e7326570e7e 100644 --- a/go.mod +++ b/go.mod @@ -144,6 +144,7 @@ require ( github.com/urfave/cli v1.22.14 github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vmware/govmomi v0.30.6 + go.uber.org/mock v0.4.0 golang.org/x/crypto v0.22.0 golang.org/x/mod v0.14.0 golang.org/x/net v0.24.0 diff --git a/pkg/controllers/management/auth/crtb_handler.go b/pkg/controllers/management/auth/crtb_handler.go index 5bffed68eeb..04cced43765 100644 --- a/pkg/controllers/management/auth/crtb_handler.go +++ b/pkg/controllers/management/auth/crtb_handler.go @@ -8,7 +8,9 @@ import ( "github.com/pkg/errors" "github.com/rancher/rancher/pkg/controllers/management/authprovisioningv2" v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + typesrbacv1 "github.com/rancher/rancher/pkg/generated/norman/rbac.authorization.k8s.io/v1" pkgrbac "github.com/rancher/rancher/pkg/rbac" + "github.com/rancher/rancher/pkg/user" "github.com/sirupsen/logrus" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -36,7 +38,7 @@ const ( RtbCrbRbLabelsUpdated = "auth.management.cattle.io/crb-rb-labels-updated" ) -var clusterManagmentPlaneResources = map[string]string{ +var clusterManagementPlaneResources = map[string]string{ "clusterscans": "management.cattle.io", "catalogtemplates": "management.cattle.io", "catalogtemplateversions": "management.cattle.io", @@ -57,8 +59,16 @@ var clusterManagmentPlaneResources = map[string]string{ } type crtbLifecycle struct { - mgr *manager + mgr managerInterface clusterLister v3.ClusterLister + userMGR user.Manager + userLister v3.UserLister + projectLister v3.ProjectLister + rbLister typesrbacv1.RoleBindingLister + rbClient typesrbacv1.RoleBindingInterface + crbLister typesrbacv1.ClusterRoleBindingLister + crbClient typesrbacv1.ClusterRoleBindingInterface + crtbClient v3.ClusterRoleTemplateBindingInterface } func (c *crtbLifecycle) Create(obj *v3.ClusterRoleTemplateBinding) (runtime.Object, error) { @@ -102,7 +112,7 @@ func (c *crtbLifecycle) reconcileSubject(binding *v3.ClusterRoleTemplateBinding) if binding.UserPrincipalName != "" && binding.UserName == "" { displayName := binding.Annotations["auth.cattle.io/principal-display-name"] - user, err := c.mgr.userMGR.EnsureUser(binding.UserPrincipalName, displayName) + user, err := c.userMGR.EnsureUser(binding.UserPrincipalName, displayName) if err != nil { return binding, err } @@ -112,7 +122,7 @@ func (c *crtbLifecycle) reconcileSubject(binding *v3.ClusterRoleTemplateBinding) } if binding.UserPrincipalName == "" && binding.UserName != "" { - u, err := c.mgr.userLister.Get("", binding.UserName) + u, err := c.userLister.Get("", binding.UserName) if err != nil { return binding, err } @@ -166,12 +176,12 @@ func (c *crtbLifecycle) reconcileBindings(binding *v3.ClusterRoleTemplateBinding return err } - err = c.mgr.grantManagementPlanePrivileges(binding.RoleTemplateName, clusterManagmentPlaneResources, subject, binding) + err = c.mgr.grantManagementPlanePrivileges(binding.RoleTemplateName, clusterManagementPlaneResources, subject, binding) if err != nil { return err } - projects, err := c.mgr.projectLister.List(binding.Namespace, labels.Everything()) + projects, err := c.projectLister.List(binding.Namespace, labels.Everything()) if err != nil { return err } @@ -180,7 +190,7 @@ func (c *crtbLifecycle) reconcileBindings(binding *v3.ClusterRoleTemplateBinding logrus.Warnf("Project %v is being deleted, not creating membership bindings", p.Name) continue } - if err := c.mgr.grantManagementClusterScopedPrivilegesInProjectNamespace(binding.RoleTemplateName, p.Name, projectManagmentPlaneResources, subject, binding); err != nil { + if err := c.mgr.grantManagementClusterScopedPrivilegesInProjectNamespace(binding.RoleTemplateName, p.Name, projectManagementPlaneResources, subject, binding); err != nil { return err } } @@ -188,20 +198,20 @@ func (c *crtbLifecycle) reconcileBindings(binding *v3.ClusterRoleTemplateBinding } func (c *crtbLifecycle) removeMGMTClusterScopedPrivilegesInProjectNamespace(binding *v3.ClusterRoleTemplateBinding) error { - projects, err := c.mgr.projectLister.List(binding.Namespace, labels.Everything()) + projects, err := c.projectLister.List(binding.Namespace, labels.Everything()) if err != nil { return err } bindingKey := pkgrbac.GetRTBLabel(binding.ObjectMeta) for _, p := range projects { set := labels.Set(map[string]string{bindingKey: CrtbInProjectBindingOwner}) - rbs, err := c.mgr.rbLister.List(p.Name, set.AsSelector()) + rbs, err := c.rbLister.List(p.Name, set.AsSelector()) if err != nil { return err } for _, rb := range rbs { logrus.Infof("[%v] Deleting rolebinding %v in namespace %v for crtb %v", ctrbMGMTController, rb.Name, p.Name, binding.Name) - if err := c.mgr.mgmt.RBAC.RoleBindings(p.Name).Delete(rb.Name, &v1.DeleteOptions{}); err != nil { + if err := c.rbClient.DeleteNamespaced(p.Name, rb.Name, &v1.DeleteOptions{}); err != nil { return err } } @@ -226,14 +236,14 @@ func (c *crtbLifecycle) reconcileLabels(binding *v3.ClusterRoleTemplateBinding) } set := labels.Set(map[string]string{string(binding.UID): MembershipBindingOwnerLegacy}) - crbs, err := c.mgr.crbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) + crbs, err := c.crbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) if err != nil { return err } bindingKey := pkgrbac.GetRTBLabel(binding.ObjectMeta) for _, crb := range crbs { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - crbToUpdate, updateErr := c.mgr.crbClient.Get(crb.Name, v1.GetOptions{}) + crbToUpdate, updateErr := c.crbClient.Get(crb.Name, v1.GetOptions{}) if updateErr != nil { return updateErr } @@ -242,7 +252,7 @@ func (c *crtbLifecycle) reconcileLabels(binding *v3.ClusterRoleTemplateBinding) } crbToUpdate.Labels[bindingKey] = MembershipBindingOwner crbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := c.mgr.crbClient.Update(crbToUpdate) + _, err := c.crbClient.Update(crbToUpdate) return err }) if retryErr != nil { @@ -251,14 +261,14 @@ func (c *crtbLifecycle) reconcileLabels(binding *v3.ClusterRoleTemplateBinding) } set = map[string]string{string(binding.UID): CrtbInProjectBindingOwner} - rbs, err := c.mgr.rbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) + rbs, err := c.rbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) if err != nil { return err } for _, rb := range rbs { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - rbToUpdate, updateErr := c.mgr.rbClient.GetNamespaced(rb.Namespace, rb.Name, v1.GetOptions{}) + rbToUpdate, updateErr := c.rbClient.GetNamespaced(rb.Namespace, rb.Name, v1.GetOptions{}) if updateErr != nil { return updateErr } @@ -267,7 +277,7 @@ func (c *crtbLifecycle) reconcileLabels(binding *v3.ClusterRoleTemplateBinding) } rbToUpdate.Labels[bindingKey] = CrtbInProjectBindingOwner rbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := c.mgr.rbClient.Update(rbToUpdate) + _, err := c.rbClient.Update(rbToUpdate) return err }) if retryErr != nil { @@ -279,7 +289,7 @@ func (c *crtbLifecycle) reconcileLabels(binding *v3.ClusterRoleTemplateBinding) } retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - crtbToUpdate, updateErr := c.mgr.crtbs.GetNamespaced(binding.Namespace, binding.Name, v1.GetOptions{}) + crtbToUpdate, updateErr := c.crtbClient.GetNamespaced(binding.Namespace, binding.Name, v1.GetOptions{}) if updateErr != nil { return updateErr } @@ -287,7 +297,7 @@ func (c *crtbLifecycle) reconcileLabels(binding *v3.ClusterRoleTemplateBinding) crtbToUpdate.Labels = make(map[string]string) } crtbToUpdate.Labels[RtbCrbRbLabelsUpdated] = "true" - _, err := c.mgr.crtbs.Update(crtbToUpdate) + _, err := c.crtbClient.Update(crtbToUpdate) return err }) return retryErr diff --git a/pkg/controllers/management/auth/crtb_handler_test.go b/pkg/controllers/management/auth/crtb_handler_test.go new file mode 100644 index 00000000000..5e653e2e093 --- /dev/null +++ b/pkg/controllers/management/auth/crtb_handler_test.go @@ -0,0 +1,304 @@ +package auth + +import ( + "fmt" + "testing" + "time" + + v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3/fakes" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +var ( + e = fmt.Errorf("error") + defaultCRTB = v3.ClusterRoleTemplateBinding{ + UserName: "test", + GroupName: "", + GroupPrincipalName: "", + ClusterName: "clusterName", + RoleTemplateName: "roleTemplate", + } + noUserCRTB = v3.ClusterRoleTemplateBinding{ + UserName: "", + GroupName: "", + GroupPrincipalName: "", + } + defaultCluster = v3.Cluster{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-cluster", + }, + } + defaultProject = v3.Project{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-project", + }, + } + deletingProject = v3.Project{ + ObjectMeta: v1.ObjectMeta{ + Name: "deleting-project", + DeletionTimestamp: &v1.Time{Time: time.Now()}, + }, + } +) + +type crtbTestState struct { + clusterListerMock *fakes.ClusterListerMock + projectListerMock *fakes.ProjectListerMock + managerMock *MockmanagerInterface +} + +func TestReconcileBindings(t *testing.T) { + tests := []struct { + name string + stateSetup func(crtbTestState) + wantError bool + crtb *v3.ClusterRoleTemplateBinding + }{ + { + name: "reconcile crtb with no subject", + crtb: noUserCRTB.DeepCopy(), + }, + { + name: "error getting cluster", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + return nil, e + } + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "cluster not found", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + return nil, nil + } + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "error in checkReferencedRoles", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(true, e) + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "error in ensureClusterMembershipBinding", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(true, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clusterowner", gomock.Any(), gomock.Any(), true, gomock.Any()). + Return(e) + + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "error in grantManagementPlanePrivileges", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(true, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clusterowner", gomock.Any(), gomock.Any(), true, gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementPlanePrivileges("roleTemplate", gomock.Any(), gomock.Any(), gomock.Any()). + Return(e) + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "error listing projects", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(true, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clusterowner", gomock.Any(), gomock.Any(), true, gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementPlanePrivileges("roleTemplate", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + cts.projectListerMock.ListFunc = func(namespace string, selector labels.Selector) ([]*v3.Project, error) { + return nil, e + } + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "error listing projects", + stateSetup: func(cts crtbTestState) { + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(true, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clusterowner", gomock.Any(), gomock.Any(), true, gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementPlanePrivileges("roleTemplate", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + cts.projectListerMock.ListFunc = func(namespace string, selector labels.Selector) ([]*v3.Project, error) { + p := defaultProject.DeepCopy() + return []*v3.Project{p}, nil + } + cts.managerMock.EXPECT(). + grantManagementClusterScopedPrivilegesInProjectNamespace("roleTemplate", "test-project", gomock.Any(), gomock.Any(), gomock.Any()). + Return(e) + + }, + wantError: true, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "successfully reconcile clusterowner", + stateSetup: func(cts crtbTestState) { + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(true, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clusterowner", gomock.Any(), gomock.Any(), true, gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementPlanePrivileges("roleTemplate", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementClusterScopedPrivilegesInProjectNamespace("roleTemplate", "test-project", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.projectListerMock.ListFunc = func(namespace string, selector labels.Selector) ([]*v3.Project, error) { + p := defaultProject.DeepCopy() + return []*v3.Project{p}, nil + } + }, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "successfully reconcile clustermember", + stateSetup: func(cts crtbTestState) { + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(false, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clustermember", gomock.Any(), gomock.Any(), false, gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementPlanePrivileges("roleTemplate", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementClusterScopedPrivilegesInProjectNamespace("roleTemplate", "test-project", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.projectListerMock.ListFunc = func(namespace string, selector labels.Selector) ([]*v3.Project, error) { + p := defaultProject.DeepCopy() + return []*v3.Project{p}, nil + } + }, + crtb: defaultCRTB.DeepCopy(), + }, + { + name: "skip projects that are deleting", + stateSetup: func(cts crtbTestState) { + cts.managerMock.EXPECT(). + checkReferencedRoles("roleTemplate", "cluster", gomock.Any()). + Return(false, nil) + cts.managerMock.EXPECT(). + ensureClusterMembershipBinding("clustername-clustermember", gomock.Any(), gomock.Any(), false, gomock.Any()). + Return(nil) + cts.managerMock.EXPECT(). + grantManagementPlanePrivileges("roleTemplate", gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + // This should not be called + cts.managerMock.EXPECT(). + grantManagementClusterScopedPrivilegesInProjectNamespace("roleTemplate", "deleting-project", gomock.Any(), gomock.Any(), gomock.Any()). + Return(e).AnyTimes() + cts.clusterListerMock.GetFunc = func(namespace, name string) (*v3.Cluster, error) { + c := defaultCluster.DeepCopy() + return c, nil + } + cts.projectListerMock.ListFunc = func(namespace string, selector labels.Selector) ([]*v3.Project, error) { + p := deletingProject.DeepCopy() + return []*v3.Project{p}, nil + } + }, + crtb: defaultCRTB.DeepCopy(), + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + crtbLifecycle := crtbLifecycle{} + state := setupTest(t) + if test.stateSetup != nil { + test.stateSetup(state) + } + crtbLifecycle.clusterLister = state.clusterListerMock + crtbLifecycle.projectLister = state.projectListerMock + crtbLifecycle.mgr = state.managerMock + + err := crtbLifecycle.reconcileBindings(test.crtb) + + if test.wantError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func setupTest(t *testing.T) crtbTestState { + ctrl := gomock.NewController(t) + fakeManager := NewMockmanagerInterface(ctrl) + projectListerMock := fakes.ProjectListerMock{} + clusterListerMock := fakes.ClusterListerMock{} + + state := crtbTestState{ + managerMock: fakeManager, + clusterListerMock: &clusterListerMock, + projectListerMock: &projectListerMock, + } + return state +} diff --git a/pkg/controllers/management/auth/manager.go b/pkg/controllers/management/auth/manager.go index 2b66ef907b8..60555342836 100644 --- a/pkg/controllers/management/auth/manager.go +++ b/pkg/controllers/management/auth/manager.go @@ -9,14 +9,12 @@ import ( "github.com/pkg/errors" "github.com/rancher/norman/objectclient" "github.com/rancher/norman/types/slice" - "github.com/rancher/rancher/pkg/clustermanager" "github.com/rancher/rancher/pkg/controllers/managementuser/rbac" v13 "github.com/rancher/rancher/pkg/generated/norman/core/v1" v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" typesrbacv1 "github.com/rancher/rancher/pkg/generated/norman/rbac.authorization.k8s.io/v1" pkgrbac "github.com/rancher/rancher/pkg/rbac" "github.com/rancher/rancher/pkg/types/config" - "github.com/rancher/rancher/pkg/user" "github.com/rancher/wrangler/v2/pkg/apply" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" @@ -50,72 +48,79 @@ func newRTBLifecycles(management *config.ManagementContext) (*prtbLifecycle, *cr prtb := &prtbLifecycle{ mgr: &manager{ - mgmt: management, - projectLister: management.Management.Projects("").Controller().Lister(), - crbLister: management.RBAC.ClusterRoleBindings("").Controller().Lister(), - crbClient: management.RBAC.ClusterRoleBindings(""), - crLister: management.RBAC.ClusterRoles("").Controller().Lister(), - nsLister: management.Core.Namespaces("").Controller().Lister(), - rLister: management.RBAC.Roles("").Controller().Lister(), - rClient: management.RBAC.Roles(""), - rbLister: management.RBAC.RoleBindings("").Controller().Lister(), - rbClient: management.RBAC.RoleBindings(""), - rtLister: management.Management.RoleTemplates("").Controller().Lister(), - userLister: management.Management.Users("").Controller().Lister(), - rbIndexer: rbInformer.GetIndexer(), - crbIndexer: crbInformer.GetIndexer(), - userMGR: management.UserManager, - controller: ptrbMGMTController, - prtbs: management.Management.ProjectRoleTemplateBindings(""), + mgmt: management, + crLister: management.RBAC.ClusterRoles("").Controller().Lister(), + nsLister: management.Core.Namespaces("").Controller().Lister(), + rLister: management.RBAC.Roles("").Controller().Lister(), + rClient: management.RBAC.Roles(""), + rbLister: management.RBAC.RoleBindings("").Controller().Lister(), + rbClient: management.RBAC.RoleBindings(""), + rtLister: management.Management.RoleTemplates("").Controller().Lister(), + rbIndexer: rbInformer.GetIndexer(), + crbIndexer: crbInformer.GetIndexer(), + controller: ptrbMGMTController, }, projectLister: management.Management.Projects("").Controller().Lister(), clusterLister: management.Management.Clusters("").Controller().Lister(), + userMGR: management.UserManager, + userLister: management.Management.Users("").Controller().Lister(), + rbLister: management.RBAC.RoleBindings("").Controller().Lister(), + rbClient: management.RBAC.RoleBindings(""), + crbLister: management.RBAC.ClusterRoleBindings("").Controller().Lister(), + crbClient: management.RBAC.ClusterRoleBindings(""), + prtbClient: management.Management.ProjectRoleTemplateBindings(""), } crtb := &crtbLifecycle{ mgr: &manager{ - mgmt: management, - projectLister: management.Management.Projects("").Controller().Lister(), - crbLister: management.RBAC.ClusterRoleBindings("").Controller().Lister(), - crbClient: management.RBAC.ClusterRoleBindings(""), - crLister: management.RBAC.ClusterRoles("").Controller().Lister(), - nsLister: management.Core.Namespaces("").Controller().Lister(), - rLister: management.RBAC.Roles("").Controller().Lister(), - rClient: management.RBAC.Roles(""), - rbLister: management.RBAC.RoleBindings("").Controller().Lister(), - rbClient: management.RBAC.RoleBindings(""), - rtLister: management.Management.RoleTemplates("").Controller().Lister(), - userLister: management.Management.Users("").Controller().Lister(), - rbIndexer: rbInformer.GetIndexer(), - crbIndexer: crbInformer.GetIndexer(), - userMGR: management.UserManager, - controller: ctrbMGMTController, - crtbs: management.Management.ClusterRoleTemplateBindings(""), + mgmt: management, + crLister: management.RBAC.ClusterRoles("").Controller().Lister(), + nsLister: management.Core.Namespaces("").Controller().Lister(), + rLister: management.RBAC.Roles("").Controller().Lister(), + rClient: management.RBAC.Roles(""), + rbLister: management.RBAC.RoleBindings("").Controller().Lister(), + rbClient: management.RBAC.RoleBindings(""), + rtLister: management.Management.RoleTemplates("").Controller().Lister(), + rbIndexer: rbInformer.GetIndexer(), + crbIndexer: crbInformer.GetIndexer(), + controller: ctrbMGMTController, }, clusterLister: management.Management.Clusters("").Controller().Lister(), + userMGR: management.UserManager, + userLister: management.Management.Users("").Controller().Lister(), + projectLister: management.Management.Projects("").Controller().Lister(), + rbLister: management.RBAC.RoleBindings("").Controller().Lister(), + rbClient: management.RBAC.RoleBindings(""), + crbLister: management.RBAC.ClusterRoleBindings("").Controller().Lister(), + crbClient: management.RBAC.ClusterRoleBindings(""), + crtbClient: management.Management.ClusterRoleTemplateBindings(""), } return prtb, crtb } +type managerInterface interface { + reconcileClusterMembershipBindingForDelete(string, string) error + reconcileProjectMembershipBindingForDelete(string, string, string) error + removeAuthV2Permissions(string, runtime.Object) error + checkReferencedRoles(string, string, int) (bool, error) + ensureClusterMembershipBinding(string, string, *v3.Cluster, bool, v1.Subject) error + ensureProjectMembershipBinding(string, string, string, *v3.Project, bool, v1.Subject) error + grantManagementPlanePrivileges(string, map[string]string, v1.Subject, interface{}) error + grantManagementClusterScopedPrivilegesInProjectNamespace(string, string, map[string]string, v1.Subject, *v3.ClusterRoleTemplateBinding) error + grantManagementProjectScopedPrivilegesInClusterNamespace(string, string, map[string]string, v1.Subject, *v3.ProjectRoleTemplateBinding) error +} + type manager struct { - projectLister v3.ProjectLister - crLister typesrbacv1.ClusterRoleLister - rLister typesrbacv1.RoleLister - rClient typesrbacv1.RoleInterface - rbLister typesrbacv1.RoleBindingLister - rbClient typesrbacv1.RoleBindingInterface - crbLister typesrbacv1.ClusterRoleBindingLister - crbClient typesrbacv1.ClusterRoleBindingInterface - rtLister v3.RoleTemplateLister - nsLister v13.NamespaceLister - userLister v3.UserLister - rbIndexer cache.Indexer - crbIndexer cache.Indexer - mgmt *config.ManagementContext - userMGR user.Manager - controller string - clusterManager *clustermanager.Manager - crtbs v3.ClusterRoleTemplateBindingInterface - prtbs v3.ProjectRoleTemplateBindingInterface + crLister typesrbacv1.ClusterRoleLister + rLister typesrbacv1.RoleLister + rClient typesrbacv1.RoleInterface + rbLister typesrbacv1.RoleBindingLister + rbClient typesrbacv1.RoleBindingInterface + rtLister v3.RoleTemplateLister + nsLister v13.NamespaceLister + rbIndexer cache.Indexer + crbIndexer cache.Indexer + mgmt *config.ManagementContext + controller string } // When a CRTB is created that gives a subject some permissions in a project or cluster, we need to create a "membership" binding diff --git a/pkg/controllers/management/auth/prtb_handler.go b/pkg/controllers/management/auth/prtb_handler.go index c574090ff8c..ebc94ab8867 100644 --- a/pkg/controllers/management/auth/prtb_handler.go +++ b/pkg/controllers/management/auth/prtb_handler.go @@ -8,7 +8,9 @@ import ( "github.com/pkg/errors" "github.com/rancher/rancher/pkg/controllers/management/authprovisioningv2" v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + typesrbacv1 "github.com/rancher/rancher/pkg/generated/norman/rbac.authorization.k8s.io/v1" pkgrbac "github.com/rancher/rancher/pkg/rbac" + "github.com/rancher/rancher/pkg/user" "github.com/sirupsen/logrus" rbacv1 "k8s.io/api/rbac/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -22,7 +24,7 @@ const ( ptrbMGMTController = "mgmt-auth-prtb-controller" ) -var projectManagmentPlaneResources = map[string]string{ +var projectManagementPlaneResources = map[string]string{ "apps": "project.cattle.io", "apprevisions": "project.cattle.io", "catalogtemplates": "management.cattle.io", @@ -44,9 +46,16 @@ var prtbClusterManagmentPlaneResources = map[string]string{ } type prtbLifecycle struct { - mgr *manager + mgr managerInterface projectLister v3.ProjectLister clusterLister v3.ClusterLister + userMGR user.Manager + userLister v3.UserLister + rbLister typesrbacv1.RoleBindingLister + rbClient typesrbacv1.RoleBindingInterface + crbLister typesrbacv1.ClusterRoleBindingLister + crbClient typesrbacv1.ClusterRoleBindingInterface + prtbClient v3.ProjectRoleTemplateBindingInterface } func (p *prtbLifecycle) Create(obj *v3.ProjectRoleTemplateBinding) (runtime.Object, error) { @@ -107,7 +116,7 @@ func (p *prtbLifecycle) reconcileSubject(binding *v3.ProjectRoleTemplateBinding) if binding.UserPrincipalName != "" && binding.UserName == "" { displayName := binding.Annotations["auth.cattle.io/principal-display-name"] - user, err := p.mgr.userMGR.EnsureUser(binding.UserPrincipalName, displayName) + user, err := p.userMGR.EnsureUser(binding.UserPrincipalName, displayName) if err != nil { return binding, err } @@ -117,7 +126,7 @@ func (p *prtbLifecycle) reconcileSubject(binding *v3.ProjectRoleTemplateBinding) } if binding.UserPrincipalName == "" && binding.UserName != "" { - u, err := p.mgr.userLister.Get("", binding.UserName) + u, err := p.userLister.Get("", binding.UserName) if err != nil { return binding, err } @@ -193,14 +202,14 @@ func (p *prtbLifecycle) reconcileBindings(binding *v3.ProjectRoleTemplateBinding if err := p.mgr.grantManagementProjectScopedPrivilegesInClusterNamespace(binding.RoleTemplateName, proj.Namespace, prtbClusterManagmentPlaneResources, subject, binding); err != nil { return err } - return p.mgr.grantManagementPlanePrivileges(binding.RoleTemplateName, projectManagmentPlaneResources, subject, binding) + return p.mgr.grantManagementPlanePrivileges(binding.RoleTemplateName, projectManagementPlaneResources, subject, binding) } // removeMGMTProjectScopedPrivilegesInClusterNamespace revokes access that project roles were granted to certain cluster scoped resources like // catalogtemplates, when the prtb is deleted, by deleting the rolebinding created for this prtb in the cluster's namespace func (p *prtbLifecycle) removeMGMTProjectScopedPrivilegesInClusterNamespace(binding *v3.ProjectRoleTemplateBinding, clusterName string) error { set := labels.Set(map[string]string{pkgrbac.GetRTBLabel(binding.ObjectMeta): PrtbInClusterBindingOwner}) - rbs, err := p.mgr.rbLister.List(clusterName, set.AsSelector()) + rbs, err := p.rbLister.List(clusterName, set.AsSelector()) if err != nil { return err } @@ -218,7 +227,7 @@ func (p *prtbLifecycle) removeMGMTProjectScopedPrivilegesInClusterNamespace(bind } if removeBinding { logrus.Infof("[%v] Deleting rolebinding %v in namespace %v for prtb %v", ptrbMGMTController, rb.Name, clusterName, binding.Name) - if err := p.mgr.mgmt.RBAC.RoleBindings(clusterName).Delete(rb.Name, &v1.DeleteOptions{}); err != nil { + if err := p.rbClient.DeleteNamespaced(clusterName, rb.Name, &v1.DeleteOptions{}); err != nil { return err } } @@ -243,13 +252,13 @@ func (p *prtbLifecycle) reconcileLabels(binding *v3.ProjectRoleTemplateBinding) } bindingKey := pkgrbac.GetRTBLabel(binding.ObjectMeta) set := labels.Set(map[string]string{string(binding.UID): MembershipBindingOwnerLegacy}) - crbs, err := p.mgr.crbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) + crbs, err := p.crbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) if err != nil { return err } for _, crb := range crbs { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - crbToUpdate, updateErr := p.mgr.crbClient.Get(crb.Name, v1.GetOptions{}) + crbToUpdate, updateErr := p.crbClient.Get(crb.Name, v1.GetOptions{}) if updateErr != nil { return updateErr } @@ -258,7 +267,7 @@ func (p *prtbLifecycle) reconcileLabels(binding *v3.ProjectRoleTemplateBinding) } crbToUpdate.Labels[bindingKey] = MembershipBindingOwner crbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := p.mgr.crbClient.Update(crbToUpdate) + _, err := p.crbClient.Update(crbToUpdate) return err }) if retryErr != nil { @@ -268,13 +277,13 @@ func (p *prtbLifecycle) reconcileLabels(binding *v3.ProjectRoleTemplateBinding) for _, prtbLabel := range []string{MembershipBindingOwner, PrtbInClusterBindingOwner} { set = map[string]string{string(binding.UID): prtbLabel} - rbs, err := p.mgr.rbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) + rbs, err := p.rbLister.List(v1.NamespaceAll, set.AsSelector().Add(requirements...)) if err != nil { return err } for _, rb := range rbs { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - rbToUpdate, updateErr := p.mgr.rbClient.GetNamespaced(rb.Namespace, rb.Name, v1.GetOptions{}) + rbToUpdate, updateErr := p.rbClient.GetNamespaced(rb.Namespace, rb.Name, v1.GetOptions{}) if updateErr != nil { return updateErr } @@ -283,7 +292,7 @@ func (p *prtbLifecycle) reconcileLabels(binding *v3.ProjectRoleTemplateBinding) } rbToUpdate.Labels[bindingKey] = prtbLabel rbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := p.mgr.rbClient.Update(rbToUpdate) + _, err := p.rbClient.Update(rbToUpdate) return err }) if retryErr != nil { @@ -296,7 +305,7 @@ func (p *prtbLifecycle) reconcileLabels(binding *v3.ProjectRoleTemplateBinding) } retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - prtbToUpdate, updateErr := p.mgr.prtbs.GetNamespaced(binding.Namespace, binding.Name, v1.GetOptions{}) + prtbToUpdate, updateErr := p.prtbClient.GetNamespaced(binding.Namespace, binding.Name, v1.GetOptions{}) if updateErr != nil { return updateErr } @@ -304,7 +313,7 @@ func (p *prtbLifecycle) reconcileLabels(binding *v3.ProjectRoleTemplateBinding) prtbToUpdate.Labels = make(map[string]string) } prtbToUpdate.Labels[RtbCrbRbLabelsUpdated] = "true" - _, err := p.mgr.prtbs.Update(prtbToUpdate) + _, err := p.prtbClient.Update(prtbToUpdate) return err }) return retryErr diff --git a/pkg/controllers/management/auth/zz_manager_fakes.go b/pkg/controllers/management/auth/zz_manager_fakes.go new file mode 100644 index 00000000000..54457870f00 --- /dev/null +++ b/pkg/controllers/management/auth/zz_manager_fakes.go @@ -0,0 +1,169 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: manager.go +// +// Generated by this command: +// +// mockgen -source=manager.go -destination=zz_manager_fakes.go -package=auth +// + +// Package auth is a generated GoMock package. +package auth + +import ( + reflect "reflect" + + v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/rbac/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// MockmanagerInterface is a mock of managerInterface interface. +type MockmanagerInterface struct { + ctrl *gomock.Controller + recorder *MockmanagerInterfaceMockRecorder +} + +// MockmanagerInterfaceMockRecorder is the mock recorder for MockmanagerInterface. +type MockmanagerInterfaceMockRecorder struct { + mock *MockmanagerInterface +} + +// NewMockmanagerInterface creates a new mock instance. +func NewMockmanagerInterface(ctrl *gomock.Controller) *MockmanagerInterface { + mock := &MockmanagerInterface{ctrl: ctrl} + mock.recorder = &MockmanagerInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockmanagerInterface) EXPECT() *MockmanagerInterfaceMockRecorder { + return m.recorder +} + +// checkReferencedRoles mocks base method. +func (m *MockmanagerInterface) checkReferencedRoles(arg0, arg1 string, arg2 int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "checkReferencedRoles", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// checkReferencedRoles indicates an expected call of checkReferencedRoles. +func (mr *MockmanagerInterfaceMockRecorder) checkReferencedRoles(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "checkReferencedRoles", reflect.TypeOf((*MockmanagerInterface)(nil).checkReferencedRoles), arg0, arg1, arg2) +} + +// ensureClusterMembershipBinding mocks base method. +func (m *MockmanagerInterface) ensureClusterMembershipBinding(arg0, arg1 string, arg2 *v3.Cluster, arg3 bool, arg4 v1.Subject) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureClusterMembershipBinding", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// ensureClusterMembershipBinding indicates an expected call of ensureClusterMembershipBinding. +func (mr *MockmanagerInterfaceMockRecorder) ensureClusterMembershipBinding(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureClusterMembershipBinding", reflect.TypeOf((*MockmanagerInterface)(nil).ensureClusterMembershipBinding), arg0, arg1, arg2, arg3, arg4) +} + +// ensureProjectMembershipBinding mocks base method. +func (m *MockmanagerInterface) ensureProjectMembershipBinding(arg0, arg1, arg2 string, arg3 *v3.Project, arg4 bool, arg5 v1.Subject) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureProjectMembershipBinding", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(error) + return ret0 +} + +// ensureProjectMembershipBinding indicates an expected call of ensureProjectMembershipBinding. +func (mr *MockmanagerInterfaceMockRecorder) ensureProjectMembershipBinding(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureProjectMembershipBinding", reflect.TypeOf((*MockmanagerInterface)(nil).ensureProjectMembershipBinding), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// grantManagementClusterScopedPrivilegesInProjectNamespace mocks base method. +func (m *MockmanagerInterface) grantManagementClusterScopedPrivilegesInProjectNamespace(arg0, arg1 string, arg2 map[string]string, arg3 v1.Subject, arg4 *v3.ClusterRoleTemplateBinding) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "grantManagementClusterScopedPrivilegesInProjectNamespace", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// grantManagementClusterScopedPrivilegesInProjectNamespace indicates an expected call of grantManagementClusterScopedPrivilegesInProjectNamespace. +func (mr *MockmanagerInterfaceMockRecorder) grantManagementClusterScopedPrivilegesInProjectNamespace(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "grantManagementClusterScopedPrivilegesInProjectNamespace", reflect.TypeOf((*MockmanagerInterface)(nil).grantManagementClusterScopedPrivilegesInProjectNamespace), arg0, arg1, arg2, arg3, arg4) +} + +// grantManagementPlanePrivileges mocks base method. +func (m *MockmanagerInterface) grantManagementPlanePrivileges(arg0 string, arg1 map[string]string, arg2 v1.Subject, arg3 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "grantManagementPlanePrivileges", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// grantManagementPlanePrivileges indicates an expected call of grantManagementPlanePrivileges. +func (mr *MockmanagerInterfaceMockRecorder) grantManagementPlanePrivileges(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "grantManagementPlanePrivileges", reflect.TypeOf((*MockmanagerInterface)(nil).grantManagementPlanePrivileges), arg0, arg1, arg2, arg3) +} + +// grantManagementProjectScopedPrivilegesInClusterNamespace mocks base method. +func (m *MockmanagerInterface) grantManagementProjectScopedPrivilegesInClusterNamespace(arg0, arg1 string, arg2 map[string]string, arg3 v1.Subject, arg4 *v3.ProjectRoleTemplateBinding) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "grantManagementProjectScopedPrivilegesInClusterNamespace", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// grantManagementProjectScopedPrivilegesInClusterNamespace indicates an expected call of grantManagementProjectScopedPrivilegesInClusterNamespace. +func (mr *MockmanagerInterfaceMockRecorder) grantManagementProjectScopedPrivilegesInClusterNamespace(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "grantManagementProjectScopedPrivilegesInClusterNamespace", reflect.TypeOf((*MockmanagerInterface)(nil).grantManagementProjectScopedPrivilegesInClusterNamespace), arg0, arg1, arg2, arg3, arg4) +} + +// reconcileClusterMembershipBindingForDelete mocks base method. +func (m *MockmanagerInterface) reconcileClusterMembershipBindingForDelete(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "reconcileClusterMembershipBindingForDelete", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// reconcileClusterMembershipBindingForDelete indicates an expected call of reconcileClusterMembershipBindingForDelete. +func (mr *MockmanagerInterfaceMockRecorder) reconcileClusterMembershipBindingForDelete(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "reconcileClusterMembershipBindingForDelete", reflect.TypeOf((*MockmanagerInterface)(nil).reconcileClusterMembershipBindingForDelete), arg0, arg1) +} + +// reconcileProjectMembershipBindingForDelete mocks base method. +func (m *MockmanagerInterface) reconcileProjectMembershipBindingForDelete(arg0, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "reconcileProjectMembershipBindingForDelete", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// reconcileProjectMembershipBindingForDelete indicates an expected call of reconcileProjectMembershipBindingForDelete. +func (mr *MockmanagerInterfaceMockRecorder) reconcileProjectMembershipBindingForDelete(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "reconcileProjectMembershipBindingForDelete", reflect.TypeOf((*MockmanagerInterface)(nil).reconcileProjectMembershipBindingForDelete), arg0, arg1, arg2) +} + +// removeAuthV2Permissions mocks base method. +func (m *MockmanagerInterface) removeAuthV2Permissions(arg0 string, arg1 runtime.Object) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "removeAuthV2Permissions", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// removeAuthV2Permissions indicates an expected call of removeAuthV2Permissions. +func (mr *MockmanagerInterfaceMockRecorder) removeAuthV2Permissions(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "removeAuthV2Permissions", reflect.TypeOf((*MockmanagerInterface)(nil).removeAuthV2Permissions), arg0, arg1) +} diff --git a/pkg/controllers/managementuser/rbac/crtb_handler.go b/pkg/controllers/managementuser/rbac/crtb_handler.go index 2fb5b0e6f7a..d2ada186ae1 100644 --- a/pkg/controllers/managementuser/rbac/crtb_handler.go +++ b/pkg/controllers/managementuser/rbac/crtb_handler.go @@ -4,7 +4,9 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + typesrbacv1 "github.com/rancher/rancher/pkg/generated/norman/rbac.authorization.k8s.io/v1" pkgrbac "github.com/rancher/rancher/pkg/rbac" + "github.com/rancher/rancher/pkg/types/config" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -14,12 +16,22 @@ import ( "k8s.io/client-go/util/retry" ) -func newCRTBLifecycle(m *manager) *crtbLifecycle { - return &crtbLifecycle{m: m} +func newCRTBLifecycle(m *manager, management *config.ManagementContext) *crtbLifecycle { + return &crtbLifecycle{ + m: m, + rtLister: management.Management.RoleTemplates("").Controller().Lister(), + crbLister: m.workload.RBAC.ClusterRoleBindings("").Controller().Lister(), + crbClient: m.workload.RBAC.ClusterRoleBindings(""), + crtbClient: management.Management.ClusterRoleTemplateBindings(""), + } } type crtbLifecycle struct { - m *manager + m managerInterface + rtLister v3.RoleTemplateLister + crbLister typesrbacv1.ClusterRoleBindingLister + crbClient typesrbacv1.ClusterRoleBindingInterface + crtbClient v3.ClusterRoleTemplateBindingInterface } func (c *crtbLifecycle) Create(obj *v3.ClusterRoleTemplateBinding) (runtime.Object, error) { @@ -50,7 +62,7 @@ func (c *crtbLifecycle) syncCRTB(binding *v3.ClusterRoleTemplateBinding) error { return nil } - rt, err := c.m.rtLister.Get("", binding.RoleTemplateName) + rt, err := c.rtLister.Get("", binding.RoleTemplateName) if err != nil { return errors.Wrapf(err, "couldn't get role template %v", binding.RoleTemplateName) } @@ -79,14 +91,13 @@ func (c *crtbLifecycle) syncCRTB(binding *v3.ClusterRoleTemplateBinding) error { func (c *crtbLifecycle) ensureCRTBDelete(binding *v3.ClusterRoleTemplateBinding) error { set := labels.Set(map[string]string{rtbOwnerLabel: pkgrbac.GetRTBLabel(binding.ObjectMeta)}) - bindingCli := c.m.workload.RBAC.ClusterRoleBindings("") - rbs, err := c.m.crbLister.List("", set.AsSelector()) + rbs, err := c.crbLister.List("", set.AsSelector()) if err != nil { return errors.Wrapf(err, "couldn't list clusterrolebindings with selector %s", set.AsSelector()) } for _, rb := range rbs { - if err := bindingCli.Delete(rb.Name, &metav1.DeleteOptions{}); err != nil { + if err := c.crbClient.Delete(rb.Name, &metav1.DeleteOptions{}); err != nil { if !apierrors.IsNotFound(err) { return errors.Wrapf(err, "error deleting clusterrolebinding %v", rb.Name) } @@ -120,14 +131,14 @@ func (c *crtbLifecycle) reconcileCRTBUserClusterLabels(binding *v3.ClusterRoleTe return err } set.AsSelector().Add(*reqUpdatedLabel, *reqNsAndNameLabel) - userCRBs, err := c.m.clusterRoleBindings.List(metav1.ListOptions{LabelSelector: set.AsSelector().Add(*reqUpdatedLabel, *reqNsAndNameLabel).String()}) + userCRBs, err := c.crbClient.List(metav1.ListOptions{LabelSelector: set.AsSelector().Add(*reqUpdatedLabel, *reqNsAndNameLabel).String()}) if err != nil { return err } bindingValue := pkgrbac.GetRTBLabel(binding.ObjectMeta) for _, crb := range userCRBs.Items { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - crbToUpdate, updateErr := c.m.clusterRoleBindings.Get(crb.Name, metav1.GetOptions{}) + crbToUpdate, updateErr := c.crbClient.Get(crb.Name, metav1.GetOptions{}) if updateErr != nil { return updateErr } @@ -136,7 +147,7 @@ func (c *crtbLifecycle) reconcileCRTBUserClusterLabels(binding *v3.ClusterRoleTe } crbToUpdate.Labels[rtbOwnerLabel] = bindingValue crbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := c.m.clusterRoleBindings.Update(crbToUpdate) + _, err := c.crbClient.Update(crbToUpdate) return err }) if retryErr != nil { @@ -148,7 +159,7 @@ func (c *crtbLifecycle) reconcileCRTBUserClusterLabels(binding *v3.ClusterRoleTe } retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - crtbToUpdate, updateErr := c.m.crtbs.GetNamespaced(binding.Namespace, binding.Name, metav1.GetOptions{}) + crtbToUpdate, updateErr := c.crtbClient.GetNamespaced(binding.Namespace, binding.Name, metav1.GetOptions{}) if updateErr != nil { return updateErr } @@ -156,7 +167,7 @@ func (c *crtbLifecycle) reconcileCRTBUserClusterLabels(binding *v3.ClusterRoleTe crtbToUpdate.Labels = make(map[string]string) } crtbToUpdate.Labels[rtbCrbRbLabelsUpdated] = "true" - _, err := c.m.crtbs.Update(crtbToUpdate) + _, err := c.crtbClient.Update(crtbToUpdate) return err }) return retryErr diff --git a/pkg/controllers/managementuser/rbac/crtb_handler_test.go b/pkg/controllers/managementuser/rbac/crtb_handler_test.go new file mode 100644 index 00000000000..f2b6e0b44c8 --- /dev/null +++ b/pkg/controllers/managementuser/rbac/crtb_handler_test.go @@ -0,0 +1,159 @@ +package rbac + +import ( + "fmt" + "testing" + + v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3/fakes" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +var ( + e = fmt.Errorf("error") + defaultCRTB = v3.ClusterRoleTemplateBinding{ + UserName: "crtb-name", + RoleTemplateName: "rt-name", + } + noRoleTemplateCRTB = v3.ClusterRoleTemplateBinding{ + UserName: "crtb-name", + RoleTemplateName: "", + } + noSubjectCRTB = v3.ClusterRoleTemplateBinding{ + UserName: "", + GroupName: "", + GroupPrincipalName: "", + RoleTemplateName: "rt-name", + } +) + +type crtbTestState struct { + managerMock *MockmanagerInterface + rtListerMock *fakes.RoleTemplateListerMock +} + +func TestSyncCRTB(t *testing.T) { + t.Parallel() + tests := []struct { + name string + stateSetup func(crtbTestState) + crtb *v3.ClusterRoleTemplateBinding + wantError bool + }{ + { + name: "crtb with no role template", + crtb: noRoleTemplateCRTB.DeepCopy(), + }, + { + name: "crtb with no subject", + crtb: noSubjectCRTB.DeepCopy(), + }, + { + name: "error getting roletemplate", + stateSetup: func(cts crtbTestState) { + cts.rtListerMock.GetFunc = func(namespace, name string) (*v3.RoleTemplate, error) { + return nil, e + } + }, + crtb: defaultCRTB.DeepCopy(), + wantError: true, + }, + { + name: "error gathering roles", + stateSetup: func(cts crtbTestState) { + cts.rtListerMock.GetFunc = func(namespace, name string) (*v3.RoleTemplate, error) { + return nil, nil + } + cts.managerMock.EXPECT().gatherRoles(gomock.Any(), gomock.Any(), gomock.Any()).Return(e) + }, + crtb: defaultCRTB.DeepCopy(), + wantError: true, + }, + { + name: "error ensuring roles", + stateSetup: func(cts crtbTestState) { + cts.rtListerMock.GetFunc = func(namespace, name string) (*v3.RoleTemplate, error) { + return nil, nil + } + cts.managerMock.EXPECT().gatherRoles(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureRoles(gomock.Any()).Return(e) + }, + crtb: defaultCRTB.DeepCopy(), + wantError: true, + }, + { + name: "error ensuring cluster bindings", + stateSetup: func(cts crtbTestState) { + cts.rtListerMock.GetFunc = func(namespace, name string) (*v3.RoleTemplate, error) { + return nil, nil + } + cts.managerMock.EXPECT().gatherRoles(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureRoles(gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureClusterBindings(gomock.Any(), gomock.Any()).Return(e) + }, + crtb: defaultCRTB.DeepCopy(), + wantError: true, + }, + { + name: "error ensuring service account impersonator", + stateSetup: func(cts crtbTestState) { + cts.rtListerMock.GetFunc = func(namespace, name string) (*v3.RoleTemplate, error) { + return nil, nil + } + cts.managerMock.EXPECT().gatherRoles(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureRoles(gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureClusterBindings(gomock.Any(), gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureServiceAccountImpersonator(gomock.Any()).Return(e) + }, + crtb: defaultCRTB.DeepCopy(), + wantError: true, + }, + { + name: "success", + stateSetup: func(cts crtbTestState) { + cts.rtListerMock.GetFunc = func(namespace, name string) (*v3.RoleTemplate, error) { + return nil, nil + } + cts.managerMock.EXPECT().gatherRoles(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureRoles(gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureClusterBindings(gomock.Any(), gomock.Any()).Return(nil) + cts.managerMock.EXPECT().ensureServiceAccountImpersonator(gomock.Any()).Return(nil) + }, + crtb: defaultCRTB.DeepCopy(), + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + crtbLifecycle := crtbLifecycle{} + state := setupCRTBTest(t) + if test.stateSetup != nil { + test.stateSetup(state) + } + crtbLifecycle.rtLister = state.rtListerMock + crtbLifecycle.m = state.managerMock + + err := crtbLifecycle.syncCRTB(test.crtb) + + if test.wantError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func setupCRTBTest(t *testing.T) crtbTestState { + ctrl := gomock.NewController(t) + fakeManager := NewMockmanagerInterface(ctrl) + rtListerMock := fakes.RoleTemplateListerMock{} + state := crtbTestState{ + managerMock: fakeManager, + rtListerMock: &rtListerMock, + } + return state +} diff --git a/pkg/controllers/managementuser/rbac/handler_base.go b/pkg/controllers/managementuser/rbac/handler_base.go index 901a6eee652..ab148b1054b 100644 --- a/pkg/controllers/managementuser/rbac/handler_base.go +++ b/pkg/controllers/managementuser/rbac/handler_base.go @@ -102,22 +102,19 @@ func Register(ctx context.Context, workload *config.UserContext) { crLister: workload.RBAC.ClusterRoles("").Controller().Lister(), clusterRoles: workload.RBAC.ClusterRoles(""), clusterRoleBindings: workload.RBAC.ClusterRoleBindings(""), - roleBindings: workload.RBAC.RoleBindings(""), nsLister: workload.Core.Namespaces("").Controller().Lister(), nsController: workload.Core.Namespaces("").Controller(), clusterLister: management.Management.Clusters("").Controller().Lister(), projectLister: management.Management.Projects(workload.ClusterName).Controller().Lister(), userLister: management.Management.Users("").Controller().Lister(), userAttributeLister: management.Management.UserAttributes("").Controller().Lister(), - crtbs: management.Management.ClusterRoleTemplateBindings(""), - prtbs: management.Management.ProjectRoleTemplateBindings(""), clusterName: workload.ClusterName, } management.Management.Projects(workload.ClusterName).AddClusterScopedLifecycle(ctx, "project-namespace-auth", workload.ClusterName, newProjectLifecycle(r)) - management.Management.ProjectRoleTemplateBindings("").AddClusterScopedLifecycle(ctx, "cluster-prtb-sync", workload.ClusterName, newPRTBLifecycle(r)) + management.Management.ProjectRoleTemplateBindings("").AddClusterScopedLifecycle(ctx, "cluster-prtb-sync", workload.ClusterName, newPRTBLifecycle(r, management, nsInformer)) workload.RBAC.ClusterRoles("").AddHandler(ctx, "cluster-clusterrole-sync", newClusterRoleHandler(r).sync) workload.RBAC.ClusterRoleBindings("").AddHandler(ctx, "legacy-crb-cleaner-sync", newLegacyCRBCleaner(r).sync) - management.Management.ClusterRoleTemplateBindings("").AddClusterScopedLifecycle(ctx, "cluster-crtb-sync", workload.ClusterName, newCRTBLifecycle(r)) + management.Management.ClusterRoleTemplateBindings("").AddClusterScopedLifecycle(ctx, "cluster-crtb-sync", workload.ClusterName, newCRTBLifecycle(r, management)) management.Management.Clusters("").AddHandler(ctx, "global-admin-cluster-sync", newClusterHandler(workload)) management.Management.GlobalRoleBindings("").AddHandler(ctx, grbHandlerName, newGlobalRoleBindingHandler(workload)) @@ -137,6 +134,18 @@ func Register(ctx context.Context, workload *config.UserContext) { management.Wrangler.Mgmt.RoleTemplate(), management.Wrangler.Mgmt.RoleTemplate()) } +type managerInterface interface { + gatherRoles(*v3.RoleTemplate, map[string]*v3.RoleTemplate, int) error + ensureRoles(map[string]*v3.RoleTemplate) error + ensureClusterBindings(map[string]*v3.RoleTemplate, *v3.ClusterRoleTemplateBinding) error + ensureProjectRoleBindings(string, map[string]*v3.RoleTemplate, *v3.ProjectRoleTemplateBinding) error + ensureServiceAccountImpersonator(string) error + deleteServiceAccountImpersonator(string) error + ensureGlobalResourcesRolesForPRTB(string, map[string]*v3.RoleTemplate) ([]string, error) + reconcileProjectAccessToGlobalResources(*v3.ProjectRoleTemplateBinding, []string) (map[string]bool, error) + noRemainingOwnerLabels(*rbacv1.ClusterRoleBinding) (bool, error) +} + type manager struct { workload *config.UserContext rtLister v3.RoleTemplateLister @@ -150,7 +159,6 @@ type manager struct { crbLister typesrbacv1.ClusterRoleBindingLister clusterRoleBindings typesrbacv1.ClusterRoleBindingInterface rbLister typesrbacv1.RoleBindingLister - roleBindings typesrbacv1.RoleBindingInterface rLister typesrbacv1.RoleLister roles typesrbacv1.RoleInterface nsLister typescorev1.NamespaceLister @@ -159,8 +167,6 @@ type manager struct { projectLister v3.ProjectLister userLister v3.UserLister userAttributeLister v3.UserAttributeLister - crtbs v3.ClusterRoleTemplateBindingInterface - prtbs v3.ProjectRoleTemplateBindingInterface clusterName string } diff --git a/pkg/controllers/managementuser/rbac/prtb_handler.go b/pkg/controllers/managementuser/rbac/prtb_handler.go index 1d611dc23d2..c216d774d8a 100644 --- a/pkg/controllers/managementuser/rbac/prtb_handler.go +++ b/pkg/controllers/managementuser/rbac/prtb_handler.go @@ -10,7 +10,9 @@ import ( "github.com/rancher/norman/types/convert" "github.com/rancher/norman/types/slice" v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + typesrbacv1 "github.com/rancher/rancher/pkg/generated/norman/rbac.authorization.k8s.io/v1" pkgrbac "github.com/rancher/rancher/pkg/rbac" + "github.com/rancher/rancher/pkg/types/config" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -20,6 +22,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/retry" ) @@ -57,12 +60,28 @@ var globalResourceRulesNeededInProjects = map[string]rbacv1.PolicyRule{ }, } -func newPRTBLifecycle(m *manager) *prtbLifecycle { - return &prtbLifecycle{m: m} +func newPRTBLifecycle(m *manager, management *config.ManagementContext, nsInformer cache.SharedIndexInformer) *prtbLifecycle { + return &prtbLifecycle{ + m: m, + rtLister: management.Management.RoleTemplates("").Controller().Lister(), + nsIndexer: nsInformer.GetIndexer(), + rbLister: m.workload.RBAC.RoleBindings("").Controller().Lister(), + rbClient: m.workload.RBAC.RoleBindings(""), + crbLister: m.workload.RBAC.ClusterRoleBindings("").Controller().Lister(), + crbClient: m.workload.RBAC.ClusterRoleBindings(""), + prtbClient: management.Management.ProjectRoleTemplateBindings(""), + } } type prtbLifecycle struct { - m *manager + m managerInterface + rtLister v3.RoleTemplateLister + nsIndexer cache.Indexer + rbLister typesrbacv1.RoleBindingLister + rbClient typesrbacv1.RoleBindingInterface + crbLister typesrbacv1.ClusterRoleBindingLister + crbClient typesrbacv1.ClusterRoleBindingInterface + prtbClient v3.ProjectRoleTemplateBindingInterface } func (p *prtbLifecycle) Create(obj *v3.ProjectRoleTemplateBinding) (runtime.Object, error) { @@ -92,13 +111,13 @@ func (p *prtbLifecycle) syncPRTB(binding *v3.ProjectRoleTemplateBinding) error { return nil } - rt, err := p.m.rtLister.Get("", binding.RoleTemplateName) + rt, err := p.rtLister.Get("", binding.RoleTemplateName) if err != nil { return fmt.Errorf("couldn't get role template %v: %w", binding.RoleTemplateName, err) } // Get namespaces belonging to project - namespaces, err := p.m.nsIndexer.ByIndex(nsByProjectIndex, binding.ProjectName) + namespaces, err := p.nsIndexer.ByIndex(nsByProjectIndex, binding.ProjectName) if err != nil { return fmt.Errorf("couldn't list namespaces with project ID %v: %w", binding.ProjectName, err) } @@ -132,7 +151,7 @@ func (p *prtbLifecycle) syncPRTB(binding *v3.ProjectRoleTemplateBinding) error { func (p *prtbLifecycle) ensurePRTBDelete(binding *v3.ProjectRoleTemplateBinding) error { // Get namespaces belonging to project - namespaces, err := p.m.nsIndexer.ByIndex(nsByProjectIndex, binding.ProjectName) + namespaces, err := p.nsIndexer.ByIndex(nsByProjectIndex, binding.ProjectName) if err != nil { return fmt.Errorf("couldn't list namespaces with project ID %v: %w", binding.ProjectName, err) } @@ -140,14 +159,13 @@ func (p *prtbLifecycle) ensurePRTBDelete(binding *v3.ProjectRoleTemplateBinding) set := labels.Set(map[string]string{rtbOwnerLabel: pkgrbac.GetRTBLabel(binding.ObjectMeta)}) for _, n := range namespaces { ns := n.(*v1.Namespace) - bindingCli := p.m.workload.RBAC.RoleBindings(ns.Name) - rbs, err := p.m.rbLister.List(ns.Name, set.AsSelector()) + rbs, err := p.rbLister.List(ns.Name, set.AsSelector()) if err != nil { return fmt.Errorf("couldn't list rolebindings with selector %s: %w", set.AsSelector(), err) } for _, rb := range rbs { - if err := bindingCli.Delete(rb.Name, &metav1.DeleteOptions{}); err != nil { + if err := p.rbClient.DeleteNamespaced(ns.Name, rb.Name, &metav1.DeleteOptions{}); err != nil { if !apierrors.IsNotFound(err) { return fmt.Errorf("error deleting rolebinding %v: %w", rb.Name, err) } @@ -175,10 +193,9 @@ func (p *prtbLifecycle) reconcileProjectAccessToGlobalResources(binding *v3.Proj } func (p *prtbLifecycle) reconcileProjectAccessToGlobalResourcesForDelete(binding *v3.ProjectRoleTemplateBinding) error { - bindingCli := p.m.workload.RBAC.ClusterRoleBindings("") rtbNsAndName := pkgrbac.GetRTBLabel(binding.ObjectMeta) set := labels.Set(map[string]string{rtbNsAndName: owner}) - crbs, err := p.m.crbLister.List("", set.AsSelector()) + crbs, err := p.crbLister.List("", set.AsSelector()) if err != nil { return err } @@ -197,14 +214,14 @@ func (p *prtbLifecycle) reconcileProjectAccessToGlobalResourcesForDelete(binding } if eligibleForDeletion { - if err := bindingCli.Delete(crb.Name, &metav1.DeleteOptions{}); err != nil { + if err := p.crbClient.Delete(crb.Name, &metav1.DeleteOptions{}); err != nil { if apierrors.IsNotFound(err) { continue } return err } } else { - if _, err := bindingCli.Update(crb); err != nil { + if _, err := p.crbClient.Update(crb); err != nil { return err } } @@ -424,7 +441,7 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe return err } set := labels.Set(map[string]string{string(binding.UID): owner}) - userCRBs, err := p.m.clusterRoleBindings.List(metav1.ListOptions{LabelSelector: set.AsSelector().Add(*reqUpdatedLabel, *reqNsAndNameLabel).String()}) + userCRBs, err := p.crbClient.List(metav1.ListOptions{LabelSelector: set.AsSelector().Add(*reqUpdatedLabel, *reqNsAndNameLabel).String()}) if err != nil { return err } @@ -432,7 +449,7 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe for _, crb := range userCRBs.Items { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - crbToUpdate, updateErr := p.m.clusterRoleBindings.Get(crb.Name, metav1.GetOptions{}) + crbToUpdate, updateErr := p.crbClient.Get(crb.Name, metav1.GetOptions{}) if updateErr != nil { return updateErr } @@ -441,7 +458,7 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe } crbToUpdate.Labels[bindingLabel] = owner crbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := p.m.clusterRoleBindings.Update(crbToUpdate) + _, err := p.crbClient.Update(crbToUpdate) return err }) if retryErr != nil { @@ -454,13 +471,13 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe return err } set = map[string]string{rtbOwnerLabelLegacy: string(binding.UID)} - rbs, err := p.m.rbLister.List(v1.NamespaceAll, set.AsSelector().Add(*reqUpdatedLabel, *reqUpdatedOwnerLabel)) + rbs, err := p.rbLister.List(v1.NamespaceAll, set.AsSelector().Add(*reqUpdatedLabel, *reqUpdatedOwnerLabel)) if err != nil { return err } for _, rb := range rbs { retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - rbToUpdate, updateErr := p.m.roleBindings.GetNamespaced(rb.Namespace, rb.Name, metav1.GetOptions{}) + rbToUpdate, updateErr := p.rbClient.GetNamespaced(rb.Namespace, rb.Name, metav1.GetOptions{}) if updateErr != nil { return updateErr } @@ -469,7 +486,7 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe } rbToUpdate.Labels[rtbOwnerLabel] = bindingLabel rbToUpdate.Labels[rtbLabelUpdated] = "true" - _, err := p.m.roleBindings.Update(rbToUpdate) + _, err := p.rbClient.Update(rbToUpdate) return err }) if retryErr != nil { @@ -482,7 +499,7 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe } retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - prtbToUpdate, updateErr := p.m.prtbs.GetNamespaced(binding.Namespace, binding.Name, metav1.GetOptions{}) + prtbToUpdate, updateErr := p.prtbClient.GetNamespaced(binding.Namespace, binding.Name, metav1.GetOptions{}) if updateErr != nil { return updateErr } @@ -490,7 +507,7 @@ func (p *prtbLifecycle) reconcilePRTBUserClusterLabels(binding *v3.ProjectRoleTe prtbToUpdate.Labels = make(map[string]string) } prtbToUpdate.Labels[rtbCrbRbLabelsUpdated] = "true" - _, err := p.m.prtbs.Update(prtbToUpdate) + _, err := p.prtbClient.Update(prtbToUpdate) return err }) return retryErr diff --git a/pkg/controllers/managementuser/rbac/prtb_handler_test.go b/pkg/controllers/managementuser/rbac/prtb_handler_test.go index e000e67fc3a..208f51bb3b6 100644 --- a/pkg/controllers/managementuser/rbac/prtb_handler_test.go +++ b/pkg/controllers/managementuser/rbac/prtb_handler_test.go @@ -10,12 +10,92 @@ import ( typesrbacv1fakes "github.com/rancher/rancher/pkg/generated/norman/rbac.authorization.k8s.io/v1/fakes" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" rbacv1 "k8s.io/api/rbac/v1" v1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" ) +type prtbTestState struct { + managerMock *MockmanagerInterface +} + +func TestReconcileProjectAccessToGlobalResources(t *testing.T) { + t.Parallel() + + defaultPRTB := v3.ProjectRoleTemplateBinding{ + ProjectName: "default", + } + + tests := []struct { + name string + stateSetup func(prtbTestState) + prtb *v3.ProjectRoleTemplateBinding + rts map[string]*v3.RoleTemplate + wantError bool + }{ + { + name: "error ensuring global resource roles", + stateSetup: func(pts prtbTestState) { + pts.managerMock.EXPECT().ensureGlobalResourcesRolesForPRTB(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")) + }, + prtb: defaultPRTB.DeepCopy(), + rts: nil, + wantError: true, + }, + { + name: "error reconciling access to global resources", + stateSetup: func(pts prtbTestState) { + pts.managerMock.EXPECT().ensureGlobalResourcesRolesForPRTB(gomock.Any(), gomock.Any()).Return(nil, nil) + pts.managerMock.EXPECT().reconcileProjectAccessToGlobalResources(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")) + }, + prtb: defaultPRTB.DeepCopy(), + rts: nil, + wantError: true, + }, + { + name: "success", + stateSetup: func(pts prtbTestState) { + pts.managerMock.EXPECT().ensureGlobalResourcesRolesForPRTB(gomock.Any(), gomock.Any()).Return(nil, nil) + pts.managerMock.EXPECT().reconcileProjectAccessToGlobalResources(gomock.Any(), gomock.Any()).Return(nil, nil) + }, + prtb: defaultPRTB.DeepCopy(), + rts: nil, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + prtbLifecycle := prtbLifecycle{} + state := setupPRTBTest(t) + if test.stateSetup != nil { + test.stateSetup(state) + } + prtbLifecycle.m = state.managerMock + + err := prtbLifecycle.reconcileProjectAccessToGlobalResources(test.prtb, test.rts) + + if test.wantError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func setupPRTBTest(t *testing.T) prtbTestState { + ctrl := gomock.NewController(t) + managerMock := NewMockmanagerInterface(ctrl) + state := prtbTestState{ + managerMock: managerMock, + } + return state +} + func Test_manager_checkForGlobalResourceRules(t *testing.T) { type tests struct { name string diff --git a/pkg/controllers/managementuser/rbac/zz_manager_fakes.go b/pkg/controllers/managementuser/rbac/zz_manager_fakes.go new file mode 100644 index 00000000000..af4003ae91c --- /dev/null +++ b/pkg/controllers/managementuser/rbac/zz_manager_fakes.go @@ -0,0 +1,170 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: handler_base.go +// +// Generated by this command: +// +// mockgen -source handler_base.go -destination=zz_manager_fakes.go -package=rbac +// + +// Package rbac is a generated GoMock package. +package rbac + +import ( + reflect "reflect" + + v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/rbac/v1" +) + +// MockmanagerInterface is a mock of managerInterface interface. +type MockmanagerInterface struct { + ctrl *gomock.Controller + recorder *MockmanagerInterfaceMockRecorder +} + +// MockmanagerInterfaceMockRecorder is the mock recorder for MockmanagerInterface. +type MockmanagerInterfaceMockRecorder struct { + mock *MockmanagerInterface +} + +// NewMockmanagerInterface creates a new mock instance. +func NewMockmanagerInterface(ctrl *gomock.Controller) *MockmanagerInterface { + mock := &MockmanagerInterface{ctrl: ctrl} + mock.recorder = &MockmanagerInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockmanagerInterface) EXPECT() *MockmanagerInterfaceMockRecorder { + return m.recorder +} + +// deleteServiceAccountImpersonator mocks base method. +func (m *MockmanagerInterface) deleteServiceAccountImpersonator(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "deleteServiceAccountImpersonator", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// deleteServiceAccountImpersonator indicates an expected call of deleteServiceAccountImpersonator. +func (mr *MockmanagerInterfaceMockRecorder) deleteServiceAccountImpersonator(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "deleteServiceAccountImpersonator", reflect.TypeOf((*MockmanagerInterface)(nil).deleteServiceAccountImpersonator), arg0) +} + +// ensureClusterBindings mocks base method. +func (m *MockmanagerInterface) ensureClusterBindings(arg0 map[string]*v3.RoleTemplate, arg1 *v3.ClusterRoleTemplateBinding) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureClusterBindings", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ensureClusterBindings indicates an expected call of ensureClusterBindings. +func (mr *MockmanagerInterfaceMockRecorder) ensureClusterBindings(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureClusterBindings", reflect.TypeOf((*MockmanagerInterface)(nil).ensureClusterBindings), arg0, arg1) +} + +// ensureGlobalResourcesRolesForPRTB mocks base method. +func (m *MockmanagerInterface) ensureGlobalResourcesRolesForPRTB(arg0 string, arg1 map[string]*v3.RoleTemplate) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureGlobalResourcesRolesForPRTB", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ensureGlobalResourcesRolesForPRTB indicates an expected call of ensureGlobalResourcesRolesForPRTB. +func (mr *MockmanagerInterfaceMockRecorder) ensureGlobalResourcesRolesForPRTB(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureGlobalResourcesRolesForPRTB", reflect.TypeOf((*MockmanagerInterface)(nil).ensureGlobalResourcesRolesForPRTB), arg0, arg1) +} + +// ensureProjectRoleBindings mocks base method. +func (m *MockmanagerInterface) ensureProjectRoleBindings(arg0 string, arg1 map[string]*v3.RoleTemplate, arg2 *v3.ProjectRoleTemplateBinding) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureProjectRoleBindings", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// ensureProjectRoleBindings indicates an expected call of ensureProjectRoleBindings. +func (mr *MockmanagerInterfaceMockRecorder) ensureProjectRoleBindings(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureProjectRoleBindings", reflect.TypeOf((*MockmanagerInterface)(nil).ensureProjectRoleBindings), arg0, arg1, arg2) +} + +// ensureRoles mocks base method. +func (m *MockmanagerInterface) ensureRoles(arg0 map[string]*v3.RoleTemplate) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureRoles", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ensureRoles indicates an expected call of ensureRoles. +func (mr *MockmanagerInterfaceMockRecorder) ensureRoles(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureRoles", reflect.TypeOf((*MockmanagerInterface)(nil).ensureRoles), arg0) +} + +// ensureServiceAccountImpersonator mocks base method. +func (m *MockmanagerInterface) ensureServiceAccountImpersonator(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ensureServiceAccountImpersonator", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ensureServiceAccountImpersonator indicates an expected call of ensureServiceAccountImpersonator. +func (mr *MockmanagerInterfaceMockRecorder) ensureServiceAccountImpersonator(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ensureServiceAccountImpersonator", reflect.TypeOf((*MockmanagerInterface)(nil).ensureServiceAccountImpersonator), arg0) +} + +// gatherRoles mocks base method. +func (m *MockmanagerInterface) gatherRoles(arg0 *v3.RoleTemplate, arg1 map[string]*v3.RoleTemplate, arg2 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "gatherRoles", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// gatherRoles indicates an expected call of gatherRoles. +func (mr *MockmanagerInterfaceMockRecorder) gatherRoles(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "gatherRoles", reflect.TypeOf((*MockmanagerInterface)(nil).gatherRoles), arg0, arg1, arg2) +} + +// noRemainingOwnerLabels mocks base method. +func (m *MockmanagerInterface) noRemainingOwnerLabels(arg0 *v1.ClusterRoleBinding) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "noRemainingOwnerLabels", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// noRemainingOwnerLabels indicates an expected call of noRemainingOwnerLabels. +func (mr *MockmanagerInterfaceMockRecorder) noRemainingOwnerLabels(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "noRemainingOwnerLabels", reflect.TypeOf((*MockmanagerInterface)(nil).noRemainingOwnerLabels), arg0) +} + +// reconcileProjectAccessToGlobalResources mocks base method. +func (m *MockmanagerInterface) reconcileProjectAccessToGlobalResources(arg0 *v3.ProjectRoleTemplateBinding, arg1 []string) (map[string]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "reconcileProjectAccessToGlobalResources", arg0, arg1) + ret0, _ := ret[0].(map[string]bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// reconcileProjectAccessToGlobalResources indicates an expected call of reconcileProjectAccessToGlobalResources. +func (mr *MockmanagerInterfaceMockRecorder) reconcileProjectAccessToGlobalResources(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "reconcileProjectAccessToGlobalResources", reflect.TypeOf((*MockmanagerInterface)(nil).reconcileProjectAccessToGlobalResources), arg0, arg1) +}