diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index 6f01ae3c3..26848bb7d 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.39.2 -appVersion: 1.59.3 +version: 1.40.0 +appVersion: 1.60.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: diff --git a/pkg/apis/alert/alert_test.go b/pkg/apis/alert/alert_test.go index f0e0f0ca9..e6ddbfc4b 100644 --- a/pkg/apis/alert/alert_test.go +++ b/pkg/apis/alert/alert_test.go @@ -248,14 +248,15 @@ func (s *alertTestSuite) Test_OnSync_Rbac_UpdateWithOwnerReference() { func (s *alertTestSuite) Test_OnSync_Rbac_ConfiguredCorrectly() { namespace, appName := "any-ns", "any-app" - adminGroups, readerGroups := []string{"admin1", "admin2"}, []string{"reader1", "reader2"} + adminGroups, adminUsers := []string{"admin1", "admin2"}, []string{"adminUser1", "adminUser2"} + readerGroups, readerUsers := []string{"reader1", "reader2"}, []string{"readerUser1", "readerUser2"} alertName, alertUID := "alert", types.UID("alertuid") radixalert := &radixv1.RadixAlert{ ObjectMeta: metav1.ObjectMeta{Name: alertName, UID: alertUID, Labels: map[string]string{kube.RadixAppLabel: appName}}, Spec: radixv1.RadixAlertSpec{}, } radixalert, _ = s.radixClient.RadixV1().RadixAlerts(namespace).Create(context.Background(), radixalert, metav1.CreateOptions{}) - rr := &radixv1.RadixRegistration{ObjectMeta: metav1.ObjectMeta{Name: appName}, Spec: radixv1.RadixRegistrationSpec{AdGroups: adminGroups, ReaderAdGroups: readerGroups}} + rr := &radixv1.RadixRegistration{ObjectMeta: metav1.ObjectMeta{Name: appName}, Spec: radixv1.RadixRegistrationSpec{AdGroups: adminGroups, AdUsers: adminUsers, ReaderAdGroups: readerGroups, ReaderAdUsers: readerUsers}} _, err := s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) s.Require().NoError(err) @@ -272,7 +273,14 @@ func (s *alertTestSuite) Test_OnSync_Rbac_ConfiguredCorrectly() { actualAdminRoleBinding, _ := s.kubeClient.RbacV1().RoleBindings(namespace).Get(context.Background(), getAlertConfigSecretAdminRoleName(alertName), metav1.GetOptions{}) s.Equal(actualAdminRole.Name, actualAdminRoleBinding.RoleRef.Name, "rolebinding role reference not as expected") s.Equal("Role", actualAdminRoleBinding.RoleRef.Kind, "rolebinding role kind not as expected") - s.ElementsMatch([]rbacv1.Subject{{APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "admin1"}, {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "admin2"}}, actualAdminRoleBinding.Subjects) + s.ElementsMatch( + []rbacv1.Subject{ + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "admin1"}, + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "admin2"}, + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.UserKind, Name: "adminUser1"}, + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.UserKind, Name: "adminUser2"}, + }, + actualAdminRoleBinding.Subjects) actualReaderRole, _ := s.kubeClient.RbacV1().Roles(namespace).Get(context.Background(), getAlertConfigSecretReaderRoleName(alertName), metav1.GetOptions{}) s.Len(actualReaderRole.Rules, 1, "role rules not as expected") @@ -283,7 +291,14 @@ func (s *alertTestSuite) Test_OnSync_Rbac_ConfiguredCorrectly() { actualReaderRoleBinding, _ := s.kubeClient.RbacV1().RoleBindings(namespace).Get(context.Background(), getAlertConfigSecretReaderRoleName(alertName), metav1.GetOptions{}) s.Equal(actualReaderRole.Name, actualReaderRoleBinding.RoleRef.Name, "rolebinding role reference not as expected") s.Equal("Role", actualReaderRoleBinding.RoleRef.Kind, "rolebinding role kind not as expected") - s.ElementsMatch([]rbacv1.Subject{{APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "reader1"}, {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "reader2"}}, actualReaderRoleBinding.Subjects) + s.ElementsMatch( + []rbacv1.Subject{ + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "reader1"}, + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.GroupKind, Name: "reader2"}, + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.UserKind, Name: "readerUser1"}, + {APIGroup: "rbac.authorization.k8s.io", Kind: rbacv1.UserKind, Name: "readerUser2"}, + }, + actualReaderRoleBinding.Subjects) } func (s *alertTestSuite) Test_OnSync_Secret_RemoveOrphanedKeys() { diff --git a/pkg/apis/alert/rbac.go b/pkg/apis/alert/rbac.go index fef4b9e1d..108ba00e8 100644 --- a/pkg/apis/alert/rbac.go +++ b/pkg/apis/alert/rbac.go @@ -81,12 +81,10 @@ func (syncer *alertSyncer) grantAdminAccessToAlertConfigSecret(ctx context.Conte } // create rolebinding - adGroups, err := utils.GetAdGroups(rr) + subjects, err := utils.GetAppAdminRbacSubjects(rr) if err != nil { return err } - - subjects := kube.GetRoleBindingGroups(adGroups) rolebinding := kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) rolebinding.OwnerReferences = syncer.getOwnerReference() return syncer.kubeUtil.ApplyRoleBinding(ctx, namespace, rolebinding) @@ -105,7 +103,7 @@ func (syncer *alertSyncer) grantReaderAccessToAlertConfigSecret(ctx context.Cont return err } - subjects := kube.GetRoleBindingGroups(rr.Spec.ReaderAdGroups) + subjects := utils.GetAppReaderRbacSubjects(rr) rolebinding := kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) rolebinding.OwnerReferences = syncer.getOwnerReference() return syncer.kubeUtil.ApplyRoleBinding(ctx, namespace, rolebinding) diff --git a/pkg/apis/application/application_test.go b/pkg/apis/application/application_test.go index c443da6bc..d705706c6 100644 --- a/pkg/apis/application/application_test.go +++ b/pkg/apis/application/application_test.go @@ -78,7 +78,7 @@ func TestOnSync_CorrectRoleBindings_AppNamespace(t *testing.T) { appName := "any-app" rr, err := applyRegistrationWithSync(tu, client, kubeUtil, radixClient, utils.ARadixRegistration(). WithName(appName)) - assert.NoError(t, err) + require.NoError(t, err) roleBindings, _ := client.RbacV1().RoleBindings(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) assert.ElementsMatch(t, @@ -86,9 +86,9 @@ func TestOnSync_CorrectRoleBindings_AppNamespace(t *testing.T) { getRoleBindingNames(roleBindings), ) + require.Len(t, getRoleBindingByName(defaults.AppAdminRoleName, roleBindings).Subjects, 1) assert.Equal(t, getRoleBindingByName(defaults.PipelineAppRoleName, roleBindings).Subjects[0].Name, defaults.PipelineServiceAccountName) assert.Equal(t, getRoleBindingByName(defaults.AppAdminRoleName, roleBindings).Subjects[0].Name, rr.Spec.AdGroups[0]) - assert.Len(t, getRoleBindingByName(defaults.AppAdminRoleName, roleBindings).Subjects, 1) assert.Equal(t, getRoleBindingByName(defaults.RadixTektonAppRoleName, roleBindings).Subjects[0].Name, defaults.RadixTektonServiceAccountName) assert.Equal(t, getRoleBindingByName(defaults.AppReaderRoleName, roleBindings).Subjects[0].Name, rr.Spec.ReaderAdGroups[0]) assert.Equal(t, getRoleBindingByName("git-ssh-keys", roleBindings).Subjects[0].Name, rr.Spec.AdGroups[0]) @@ -205,10 +205,7 @@ func TestOnSync_NoUserGroupDefined_DefaultUserGroupSet(t *testing.T) { os.Setenv(defaults.OperatorDefaultUserGroupEnvironmentVariable, defaultRole) // Test - _, err := applyRegistrationWithSync(tu, client, kubeUtil, radixClient, utils.ARadixRegistration(). - WithName("any-app"). - WithAdGroups([]string{}). - WithReaderAdGroups([]string{})) + _, err := applyRegistrationWithSync(tu, client, kubeUtil, radixClient, utils.ARadixRegistration().WithName("any-app").WithAdGroups([]string{}).WithReaderAdGroups([]string{})) require.NoError(t, err) rolebindings, _ := client.RbacV1().RoleBindings("any-app-app").List(context.Background(), metav1.ListOptions{}) diff --git a/pkg/apis/application/rolebinding.go b/pkg/apis/application/rolebinding.go index c147e7548..5071acc4c 100644 --- a/pkg/apis/application/rolebinding.go +++ b/pkg/apis/application/rolebinding.go @@ -19,15 +19,13 @@ func (app *Application) applyRbacAppNamespace(ctx context.Context) error { registration := app.registration appNamespace := utils.GetAppNamespace(registration.Name) - adGroups, err := utils.GetAdGroups(registration) + subjects, err := utils.GetAppAdminRbacSubjects(registration) if err != nil { return err } - subjects := kube.GetRoleBindingGroups(adGroups) adminRoleBinding := kube.GetRolebindingToClusterRoleForSubjects(registration.Name, defaults.AppAdminRoleName, subjects) - readerAdGroups := registration.Spec.ReaderAdGroups - readerSubjects := kube.GetRoleBindingGroups(readerAdGroups) + readerSubjects := utils.GetAppReaderRbacSubjects(registration) readerRoleBinding := kube.GetRolebindingToClusterRoleForSubjects(registration.Name, defaults.AppReaderRoleName, readerSubjects) for _, roleBinding := range []*rbacv1.RoleBinding{adminRoleBinding, readerRoleBinding} { @@ -56,7 +54,7 @@ func (app *Application) applyRbacRadixRegistration(ctx context.Context) error { // Reader RBAC clusterRoleReaderName := fmt.Sprintf("radix-platform-user-rr-reader-%s", appName) readerClusterRole := app.buildRRClusterRole(ctx, clusterRoleReaderName, []string{"get", "list", "watch"}) - appReaderSubjects := kube.GetRoleBindingGroups(rr.Spec.ReaderAdGroups) + appReaderSubjects := utils.GetAppReaderRbacSubjects(rr) readerClusterRoleBinding := app.rrClusterRoleBinding(ctx, readerClusterRole, appReaderSubjects) // Apply roles and bindings diff --git a/pkg/apis/applicationconfig/applicationconfig_test.go b/pkg/apis/applicationconfig/applicationconfig_test.go index dda82c5df..0eacc02a0 100644 --- a/pkg/apis/applicationconfig/applicationconfig_test.go +++ b/pkg/apis/applicationconfig/applicationconfig_test.go @@ -3,6 +3,7 @@ package applicationconfig_test import ( "context" "fmt" + "slices" "testing" "github.com/equinor/radix-common/utils/pointers" @@ -409,10 +410,16 @@ func Test_AppReaderBuildSecretsRoleAndRoleBindingExists(t *testing.T) { func Test_AppReaderPrivateImageHubRoleAndRoleBindingExists(t *testing.T) { tu, client, kubeUtil, radixClient := setupTest(t) - adminGroups, readerGroups := []string{"admin1", "admin2"}, []string{"reader1", "reader2"} + adminGroups, adminUsers := []string{"admin1", "admin2"}, []string{"adminUser1", "adminUser2"} + readerGroups, readerUsers := []string{"reader1", "reader2"}, []string{"readerUser1", "readerUser2"} err := applyApplicationWithSync(tu, client, kubeUtil, radixClient, utils.ARadixApplication(). - WithRadixRegistration(utils.ARadixRegistration().WithAdGroups(adminGroups).WithReaderAdGroups(readerGroups)). + WithRadixRegistration( + utils.ARadixRegistration(). + WithAdGroups(adminGroups). + WithAdUsers(adminUsers). + WithReaderAdGroups(readerGroups). + WithReaderAdUsers(readerUsers)). WithAppName("any-app"). WithEnvironment("dev", "master")) require.NoError(t, err) @@ -423,8 +430,8 @@ func Test_AppReaderPrivateImageHubRoleAndRoleBindingExists(t *testing.T) { expectedSubjects []string } tests := []testSpec{ - {roleName: "radix-private-image-hubs-reader", expectedVerbs: []string{"get", "list", "watch"}, expectedSubjects: readerGroups}, - {roleName: "radix-private-image-hubs", expectedVerbs: []string{"get", "list", "watch", "update", "patch", "delete"}, expectedSubjects: adminGroups}, + {roleName: "radix-private-image-hubs-reader", expectedVerbs: []string{"get", "list", "watch"}, expectedSubjects: slices.Concat(readerGroups, readerUsers)}, + {roleName: "radix-private-image-hubs", expectedVerbs: []string{"get", "list", "watch", "update", "patch", "delete"}, expectedSubjects: slices.Concat(adminGroups, adminUsers)}, } roles, _ := client.RbacV1().Roles("any-app-app").List(context.Background(), metav1.ListOptions{}) diff --git a/pkg/apis/applicationconfig/rolebinding.go b/pkg/apis/applicationconfig/rolebinding.go index 7cd6c7e09..f1af88a86 100644 --- a/pkg/apis/applicationconfig/rolebinding.go +++ b/pkg/apis/applicationconfig/rolebinding.go @@ -9,14 +9,12 @@ import ( ) func rolebindingAppReaderToBuildSecrets(registration *radixv1.RadixRegistration, role *auth.Role) *auth.RoleBinding { - readerAdGroups := registration.Spec.ReaderAdGroups - subjects := kube.GetRoleBindingGroups(readerAdGroups) + subjects := utils.GetAppReaderRbacSubjects(registration) roleName := role.ObjectMeta.Name return kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) } func rolebindingAppAdminToBuildSecrets(registration *radixv1.RadixRegistration, role *auth.Role) *auth.RoleBinding { - adGroups, _ := utils.GetAdGroups(registration) - subjects := kube.GetRoleBindingGroups(adGroups) + subjects, _ := utils.GetAppAdminRbacSubjects(registration) roleName := role.ObjectMeta.Name return kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) } diff --git a/pkg/apis/deployment/deployment_test.go b/pkg/apis/deployment/deployment_test.go index 27871da88..ab2c2e3dc 100644 --- a/pkg/apis/deployment/deployment_test.go +++ b/pkg/apis/deployment/deployment_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "slices" "strings" "testing" "time" @@ -111,6 +112,7 @@ func TestObjectSynced_MultiComponent_ContainsAllElements(t *testing.T) { commitId := string(uuid.NewUUID()) const componentNameApp = "app" adminGroups, readerGroups := []string{"adm1", "adm2"}, []string{"rdr1", "rdr2"} + adminUsers, readerUsers := []string{"admUsr1", "admUsr2"}, []string{"rdrUsr1", "rdrUsr2"} for _, componentsExist := range []bool{true, false} { testScenario := utils.TernaryString(componentsExist, "Updating deployment", "Creating deployment") @@ -120,7 +122,7 @@ func TestObjectSynced_MultiComponent_ContainsAllElements(t *testing.T) { _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, "AnotherClusterName") t.Run("Test Suite", func(t *testing.T) { - aRadixRegistrationBuilder := utils.ARadixRegistration().WithAdGroups(adminGroups).WithReaderAdGroups(readerGroups) + aRadixRegistrationBuilder := utils.ARadixRegistration().WithAdGroups(adminGroups).WithAdUsers(adminUsers).WithReaderAdGroups(readerGroups).WithReaderAdUsers(readerUsers) aRadixApplicationBuilder := utils.ARadixApplication(). WithRadixRegistration(aRadixRegistrationBuilder) environment := "test" @@ -428,8 +430,8 @@ func TestObjectSynced_MultiComponent_ContainsAllElements(t *testing.T) { rolebindings, _ := kubeclient.RbacV1().RoleBindings(envNamespace).List(context.Background(), metav1.ListOptions{}) require.Subset(t, getRoleBindingNames(rolebindings), []string{"radix-app-adm-radixquote", "radix-app-reader-radixquote"}) - assert.ElementsMatch(t, adminGroups, slice.Map(getRoleBindingByName("radix-app-adm-radixquote", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) - assert.ElementsMatch(t, readerGroups, slice.Map(getRoleBindingByName("radix-app-reader-radixquote", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) + assert.ElementsMatch(t, slices.Concat(adminGroups, adminUsers), slice.Map(getRoleBindingByName("radix-app-adm-radixquote", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) + assert.ElementsMatch(t, slices.Concat(readerGroups, readerUsers), slice.Map(getRoleBindingByName("radix-app-reader-radixquote", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) }) t.Run(fmt.Sprintf("%s: validate networkpolicy", testScenario), func(t *testing.T) { @@ -507,6 +509,7 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { defer TeardownTest() commitId := string(uuid.NewUUID()) adminGroups, readerGroups := []string{"adm1", "adm2"}, []string{"rdr1", "rdr2"} + adminUsers, readerUsers := []string{"admUsr1", "admUsr2"}, []string{"rdrUsr1", "rdrUsr2"} for _, jobsExist := range []bool{false, true} { testScenario := utils.TernaryString(jobsExist, "Updating deployment", "Creating deployment") @@ -516,7 +519,7 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { os.Setenv(defaults.OperatorRadixJobSchedulerEnvironmentVariable, jobSchedulerImage) t.Run("Test Suite", func(t *testing.T) { - aRadixRegistrationBuilder := utils.ARadixRegistration().WithAdGroups(adminGroups).WithReaderAdGroups(readerGroups) + aRadixRegistrationBuilder := utils.ARadixRegistration().WithAdGroups(adminGroups).WithAdUsers(adminUsers).WithReaderAdGroups(readerGroups).WithReaderAdUsers(readerUsers) aRadixApplicationBuilder := utils.ARadixApplication(). WithRadixRegistration(aRadixRegistrationBuilder) environment := "test" @@ -755,8 +758,8 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { t.Run(fmt.Sprintf("%s validate rolebindings", testScenario), func(t *testing.T) { rolebindings, _ := kubeclient.RbacV1().RoleBindings(envNamespace).List(context.Background(), metav1.ListOptions{}) assert.Subset(t, getRoleBindingNames(rolebindings), []string{"radix-app-adm-job", "radix-app-reader-job", defaults.RadixJobSchedulerRoleName}) - assert.ElementsMatch(t, adminGroups, slice.Map(getRoleBindingByName("radix-app-adm-job", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) - assert.ElementsMatch(t, readerGroups, slice.Map(getRoleBindingByName("radix-app-reader-job", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) + assert.ElementsMatch(t, slices.Concat(adminGroups, adminUsers), slice.Map(getRoleBindingByName("radix-app-adm-job", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) + assert.ElementsMatch(t, slices.Concat(readerGroups, readerUsers), slice.Map(getRoleBindingByName("radix-app-reader-job", rolebindings).Subjects, func(s rbacv1.Subject) string { return s.Name })) }) t.Run(fmt.Sprintf("%s: validate networkpolicy", testScenario), func(t *testing.T) { @@ -4178,13 +4181,14 @@ func Test_ExternalDNS_ContainsAllResources(t *testing.T) { appName, envName := "anyapp", "anyenv" fqdnManual1, fqdnManual2 := "foo1.example.com", "foo2.example.com" fqdnAutomation1, fqdnAutomation2 := "bar1.example.com", "bar2.example.com" - adminGroups, readerGroups := []string{"adm1", "adm2"}, []string{"rdr1", "rdr2"} + adminGroups, adminUsers := []string{"adm1", "adm2"}, []string{"admUsr1", "admUsr2"} + readerGroups, readerUsers := []string{"rdr1", "rdr2"}, []string{"rdrUsr1", "rdrUsr2"} ns := utils.GetEnvironmentNamespace(appName, envName) tu, kubeclient, kubeUtil, radixclient, kedaClient, prometheusclient, _, certClient := SetupTest(t) defer TeardownTest() - rrBuilder := utils.NewRegistrationBuilder().WithName(appName).WithAdGroups(adminGroups).WithReaderAdGroups(readerGroups) + rrBuilder := utils.NewRegistrationBuilder().WithName(appName).WithAdGroups(adminGroups).WithAdUsers(adminUsers).WithReaderAdGroups(readerGroups).WithReaderAdUsers(readerUsers) raBuilder := utils.NewRadixApplicationBuilder().WithAppName(appName).WithRadixRegistration(rrBuilder) rdBuilder := utils.NewDeploymentBuilder(). WithRadixApplication(raBuilder). @@ -4252,21 +4256,24 @@ func Test_ExternalDNS_ContainsAllResources(t *testing.T) { roleBindings, _ := kubeclient.RbacV1().RoleBindings(ns).List(context.Background(), metav1.ListOptions{}) require.Subset(t, getRoleBindingNames(roleBindings), []string{"radix-app-externaldns-adm", "radix-app-externaldns-reader"}) assert.Equal(t, rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Name: "radix-app-externaldns-adm", Kind: "Role"}, getRoleBindingByName("radix-app-externaldns-adm", roleBindings).RoleRef) + assert.ElementsMatch(t, - slice.Map(adminGroups, func(group string) rbacv1.Subject { - return rbacv1.Subject{ - Kind: "Group", APIGroup: rbacv1.GroupName, Name: group, Namespace: "", - } - }), + []rbacv1.Subject{ + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "adm1"}, + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "adm2"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "admUsr1"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "admUsr2"}, + }, getRoleBindingByName("radix-app-externaldns-adm", roleBindings).Subjects, ) assert.Equal(t, rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Name: "radix-app-externaldns-reader", Kind: "Role"}, getRoleBindingByName("radix-app-externaldns-reader", roleBindings).RoleRef) assert.ElementsMatch(t, - slice.Map(readerGroups, func(group string) rbacv1.Subject { - return rbacv1.Subject{ - Kind: "Group", APIGroup: rbacv1.GroupName, Name: group, Namespace: "", - } - }), + []rbacv1.Subject{ + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "rdr1"}, + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "rdr2"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "rdrUsr1"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "rdrUsr2"}, + }, getRoleBindingByName("radix-app-externaldns-reader", roleBindings).Subjects, ) } diff --git a/pkg/apis/deployment/oauthproxyresourcemanager.go b/pkg/apis/deployment/oauthproxyresourcemanager.go index 20c1c32a5..11145bc2b 100644 --- a/pkg/apis/deployment/oauthproxyresourcemanager.go +++ b/pkg/apis/deployment/oauthproxyresourcemanager.go @@ -500,12 +500,10 @@ func (o *oauthProxyResourceManager) createOrUpdateAppAdminRbac(ctx context.Conte } // create rolebinding - adGroups, err := utils.GetAdGroups(o.rr) + subjects, err := utils.GetAppAdminRbacSubjects(o.rr) if err != nil { return err } - - subjects := kube.GetRoleBindingGroups(adGroups) rolebinding := kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) return o.kubeutil.ApplyRoleBinding(ctx, namespace, rolebinding) } @@ -529,7 +527,7 @@ func (o *oauthProxyResourceManager) createOrUpdateAppReaderRbac(ctx context.Cont } // create rolebinding - subjects := kube.GetRoleBindingGroups(o.rr.Spec.ReaderAdGroups) + subjects := utils.GetAppReaderRbacSubjects(o.rr) rolebinding := kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) return o.kubeutil.ApplyRoleBinding(ctx, namespace, rolebinding) } diff --git a/pkg/apis/deployment/oauthproxyresourcemanager_test.go b/pkg/apis/deployment/oauthproxyresourcemanager_test.go index 6081a4c68..9b553313d 100644 --- a/pkg/apis/deployment/oauthproxyresourcemanager_test.go +++ b/pkg/apis/deployment/oauthproxyresourcemanager_test.go @@ -372,8 +372,10 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxySecretAndRbacCr appName, envName, componentName := "anyapp", "qa", "server" envNs := utils.GetEnvironmentNamespace(appName, envName) s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&v1.OAuth2{}, nil) + adminGroups, adminUsers := []string{"adm1", "adm2"}, []string{"admUsr1", "admUsr2"} + // readerGroups, readerUsers := []string{"rdr1", "rdr2"}, []string{"rdrUsr1", "rdrUsr2"} - rr := utils.NewRegistrationBuilder().WithName(appName).WithAdGroups([]string{"ad1", "ad2"}).BuildRR() + rr := utils.NewRegistrationBuilder().WithName(appName).WithAdGroups(adminGroups).WithAdUsers(adminUsers).BuildRR() rd := utils.NewDeploymentBuilder(). WithAppName(appName). WithEnvironment(envName). @@ -396,8 +398,10 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyRbacCreated() { appName, envName, componentName := "anyapp", "qa", "server" envNs := utils.GetEnvironmentNamespace(appName, envName) s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&v1.OAuth2{}, nil) + adminGroups, adminUsers := []string{"adm1", "adm2"}, []string{"admUsr1", "admUsr2"} + readerGroups, readerUsers := []string{"rdr1", "rdr2"}, []string{"rdrUsr1", "rdrUsr2"} - rr := utils.NewRegistrationBuilder().WithName(appName).WithAdGroups([]string{"ad1", "ad2"}).WithReaderAdGroups([]string{"rd1", "rd2"}).BuildRR() + rr := utils.NewRegistrationBuilder().WithName(appName).WithAdGroups(adminGroups).WithAdUsers(adminUsers).WithReaderAdGroups(readerGroups).WithReaderAdUsers(readerUsers).BuildRR() rd := utils.NewDeploymentBuilder(). WithAppName(appName). WithEnvironment(envName). @@ -437,8 +441,10 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyRbacCreated() { s.Equal(expectedLabels, admRoleBinding.Labels) s.Equal(admRole.Name, admRoleBinding.RoleRef.Name) expectedSubjects := []rbacv1.Subject{ - {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "ad1"}, - {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "ad2"}, + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "adm1"}, + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "adm2"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "admUsr1"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "admUsr2"}, } s.ElementsMatch(expectedSubjects, admRoleBinding.Subjects) @@ -446,8 +452,10 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyRbacCreated() { s.Equal(expectedLabels, readerRoleBinding.Labels) s.Equal(readerRole.Name, readerRoleBinding.RoleRef.Name) expectedSubjects = []rbacv1.Subject{ - {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "rd1"}, - {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "rd2"}, + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "rdr1"}, + {Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "rdr2"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "rdrUsr1"}, + {Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "rdrUsr2"}, } s.ElementsMatch(expectedSubjects, readerRoleBinding.Subjects) } diff --git a/pkg/apis/deployment/rolebinding.go b/pkg/apis/deployment/rolebinding.go index 962562b81..acec7fc25 100644 --- a/pkg/apis/deployment/rolebinding.go +++ b/pkg/apis/deployment/rolebinding.go @@ -8,7 +8,6 @@ import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/labels" - rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubelabels "k8s.io/apimachinery/pkg/labels" ) @@ -56,12 +55,12 @@ func (deploy *Deployment) grantAccessToComponentRuntimeSecrets(ctx context.Conte func (deploy *Deployment) grantAdminAccessToSecrets(ctx context.Context, roleName string, secretNames []string, extraLabels map[string]string) error { namespace, registration := deploy.radixDeployment.Namespace, deploy.registration - adminGroups, err := utils.GetAdGroups(registration) + subjects, err := utils.GetAppAdminRbacSubjects(registration) if err != nil { return err } role := kube.CreateManageSecretRole(registration.Name, roleName, secretNames, extraLabels) - roleBinding := roleBindingAppSecrets(registration.Name, role, adminGroups) + roleBinding := kube.GetRolebindingToRoleForSubjectsWithLabels(role.ObjectMeta.Name, subjects, role.Labels) if err := deploy.kubeutil.ApplyRole(ctx, namespace, role); err != nil { return err @@ -74,7 +73,8 @@ func (deploy *Deployment) grantReaderAccessToSecrets(ctx context.Context, roleNa namespace, registration := deploy.radixDeployment.Namespace, deploy.registration role := kube.CreateReadSecretRole(registration.Name, roleName, secretNames, extraLabels) - roleBinding := roleBindingAppSecrets(registration.Name, role, registration.Spec.ReaderAdGroups) + subjects := utils.GetAppReaderRbacSubjects(registration) + roleBinding := kube.GetRolebindingToRoleForSubjectsWithLabels(role.ObjectMeta.Name, subjects, role.Labels) if err := deploy.kubeutil.ApplyRole(ctx, namespace, role); err != nil { return err @@ -125,9 +125,3 @@ func (deploy *Deployment) garbageCollectRoleBindingsNoLongerInSpec(ctx context.C return nil } - -func roleBindingAppSecrets(appName string, role *rbacv1.Role, groups []string) *rbacv1.RoleBinding { - roleName := role.ObjectMeta.Name - subjects := kube.GetRoleBindingGroups(groups) - return kube.GetRolebindingToRoleForSubjectsWithLabels(appName, roleName, subjects, role.Labels) -} diff --git a/pkg/apis/dnsalias/rbac.go b/pkg/apis/dnsalias/rbac.go index cf6291b34..34de196f8 100644 --- a/pkg/apis/dnsalias/rbac.go +++ b/pkg/apis/dnsalias/rbac.go @@ -41,7 +41,7 @@ func (s *syncer) syncAppAdminRbac(ctx context.Context, rr *radixv1.RadixRegistra } func (s *syncer) syncAppReaderRbac(ctx context.Context, rr *radixv1.RadixRegistration) error { - subjects := kube.GetRoleBindingGroups(rr.Spec.ReaderAdGroups) + subjects := utils.GetAppReaderRbacSubjects(rr) roleName := s.getClusterRoleNameForReader() return s.syncClusterRoleAndBinding(ctx, roleName, subjects) } diff --git a/pkg/apis/environment/environment.go b/pkg/apis/environment/environment.go index d2bd65bdd..b4ec9406a 100644 --- a/pkg/apis/environment/environment.go +++ b/pkg/apis/environment/environment.go @@ -158,17 +158,15 @@ func (env *Environment) ApplyNamespace(ctx context.Context, name string) error { // ApplyAdGroupRoleBinding grants access to environment namespace func (env *Environment) ApplyAdGroupRoleBinding(ctx context.Context, namespace string) error { - adGroups, err := utils.GetAdGroups(env.regConfig) + adminSubjects, err := utils.GetAppAdminRbacSubjects(env.regConfig) if err != nil { return err } - adminSubjects := kube.GetRoleBindingGroups(adGroups) adminRoleBinding := kube.GetRolebindingToClusterRoleForSubjects(env.config.Spec.AppName, defaults.AppAdminEnvironmentRoleName, adminSubjects) adminRoleBinding.SetOwnerReferences(env.AsOwnerReference()) - readerAdGroups := env.regConfig.Spec.ReaderAdGroups - readerSubjects := kube.GetRoleBindingGroups(readerAdGroups) + readerSubjects := utils.GetAppReaderRbacSubjects(env.regConfig) readerRoleBinding := kube.GetRolebindingToClusterRoleForSubjects(env.config.Spec.AppName, defaults.AppReaderEnvironmentsRoleName, readerSubjects) readerRoleBinding.SetOwnerReferences(env.AsOwnerReference()) diff --git a/pkg/apis/environment/environment_test.go b/pkg/apis/environment/environment_test.go index f37fd982d..c7abad073 100644 --- a/pkg/apis/environment/environment_test.go +++ b/pkg/apis/environment/environment_test.go @@ -164,19 +164,18 @@ func Test_Create_RoleBinding(t *testing.T) { rolebindings, _ := client.RbacV1().RoleBindings(namespaceName).List(context.Background(), metav1.ListOptions{}) commonAsserts(t, env, roleBindingsAsMeta(rolebindings.Items), "radix-tekton-env", "radix-app-admin-envs", "radix-pipeline-env", "radix-app-reader-envs") - adGroupName := rr.Spec.AdGroups[0] - t.Run("It contains the correct AD groups", func(t *testing.T) { - subjects := rolebindings.Items[0].Subjects - assert.Len(t, subjects, 1) - assert.Equal(t, adGroupName, subjects[0].Name) - }) - readerAdGroupName := rr.Spec.ReaderAdGroups[0] - t.Run("It contains the correct reader AD groups", func(t *testing.T) { - subjects := rolebindings.Items[1].Subjects - assert.Len(t, subjects, 1) - assert.Equal(t, readerAdGroupName, subjects[0].Name) - }) + // It contains the correct AD groups + subjects := rolebindings.Items[0].Subjects + require.Len(t, subjects, 2) + assert.Equal(t, rr.Spec.AdGroups[0], subjects[0].Name) + assert.Equal(t, rr.Spec.AdUsers[0], subjects[1].Name) + + // It contains the correct reader AD groups + subjects = rolebindings.Items[1].Subjects + require.Len(t, subjects, 2) + assert.Equal(t, rr.Spec.ReaderAdGroups[0], subjects[0].Name) + assert.Equal(t, rr.Spec.ReaderAdUsers[0], subjects[1].Name) } func Test_Create_LimitRange(t *testing.T) { diff --git a/pkg/apis/environment/testdata/rr.yaml b/pkg/apis/environment/testdata/rr.yaml index df7e63de6..326173317 100644 --- a/pkg/apis/environment/testdata/rr.yaml +++ b/pkg/apis/environment/testdata/rr.yaml @@ -7,4 +7,6 @@ spec: repository: "https://github.com/equinor/testapp" cloneURL: "git@github.com:equinor/testapp" adGroups: ["Tardis"] + adUsers: ["TardisUser"] readerAdGroups: ["ReaderAdGroupsTest"] + readerAdUsers: ["ReaderAdUserTest"] diff --git a/pkg/apis/kube/rolebinding.go b/pkg/apis/kube/rolebinding.go index af57c585f..99fbd9a07 100644 --- a/pkg/apis/kube/rolebinding.go +++ b/pkg/apis/kube/rolebinding.go @@ -18,9 +18,9 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" ) -// GetRoleBindingGroups Get subjects for list of ad groups -func GetRoleBindingGroups(groups []string) []rbacv1.Subject { - subjects := []rbacv1.Subject{} +// GetRoleBindingSubjects Get subjects for list of ad groups +func GetRoleBindingSubjects(groups, users []string) []rbacv1.Subject { + var subjects []rbacv1.Subject for _, group := range groups { subjects = append(subjects, rbacv1.Subject{ Kind: rbacv1.GroupKind, @@ -28,34 +28,21 @@ func GetRoleBindingGroups(groups []string) []rbacv1.Subject { APIGroup: rbacv1.GroupName, }) } + for _, user := range users { + subjects = append(subjects, rbacv1.Subject{ + Kind: rbacv1.UserKind, + Name: user, + APIGroup: rbacv1.GroupName, + }) + } return subjects } -// GetRolebindingToRole Get role binding object -func GetRolebindingToRole(appName, roleName string, groups []string) *rbacv1.RoleBinding { - return GetRolebindingToRoleWithLabels(roleName, groups, map[string]string{ - RadixAppLabel: appName, - }) -} - -// GetRolebindingToRoleWithLabels Get role binding object -func GetRolebindingToRoleWithLabels(roleName string, groups []string, labels map[string]string) *rbacv1.RoleBinding { - subjects := GetRoleBindingGroups(groups) - return getRoleBindingForSubjects(roleName, k8s.KindRole, subjects, labels) -} - // GetRolebindingToRoleWithLabelsForSubjects Get rolebinding object with subjects as input func GetRolebindingToRoleWithLabelsForSubjects(roleName string, subjects []rbacv1.Subject, labels map[string]string) *rbacv1.RoleBinding { return getRoleBindingForSubjects(roleName, k8s.KindRole, subjects, labels) } -// GetRolebindingToClusterRole Get role binding object -func GetRolebindingToClusterRole(appName, roleName string, groups []string) *rbacv1.RoleBinding { - return GetRolebindingToClusterRoleWithLabels(roleName, groups, map[string]string{ - RadixAppLabel: appName, - }) -} - // GetRolebindingToClusterRoleForSubjects Get role binding object for list of subjects func GetRolebindingToClusterRoleForSubjects(appName, roleName string, subjects []rbacv1.Subject) *rbacv1.RoleBinding { return GetRolebindingToClusterRoleForSubjectsWithLabels(roleName, subjects, map[string]string{ @@ -68,14 +55,8 @@ func GetRolebindingToClusterRoleForSubjectsWithLabels(roleName string, subjects return getRoleBindingForSubjects(roleName, k8s.KindClusterRole, subjects, labels) } -// GetRolebindingToClusterRoleWithLabels Get role binding object -func GetRolebindingToClusterRoleWithLabels(roleName string, groups []string, labels map[string]string) *rbacv1.RoleBinding { - subjects := GetRoleBindingGroups(groups) - return getRoleBindingForSubjects(roleName, k8s.KindClusterRole, subjects, labels) -} - // GetRolebindingToRoleForSubjectsWithLabels Get role binding object for list of subjects with labels set -func GetRolebindingToRoleForSubjectsWithLabels(appName, roleName string, subjects []rbacv1.Subject, labels map[string]string) *rbacv1.RoleBinding { +func GetRolebindingToRoleForSubjectsWithLabels(roleName string, subjects []rbacv1.Subject, labels map[string]string) *rbacv1.RoleBinding { return getRoleBindingForSubjects(roleName, k8s.KindRole, subjects, labels) } diff --git a/pkg/apis/radix/v1/radixregistrationtypes.go b/pkg/apis/radix/v1/radixregistrationtypes.go index 3b2e1bbb8..4da908096 100644 --- a/pkg/apis/radix/v1/radixregistrationtypes.go +++ b/pkg/apis/radix/v1/radixregistrationtypes.go @@ -28,7 +28,9 @@ type RadixRegistrationSpec struct { DeployKey string `json:"deployKey" yaml:"deployKey"` DeployKeyPublic string `json:"deployKeyPublic" yaml:"deployKeyPublic"` AdGroups []string `json:"adGroups" yaml:"adGroups"` + AdUsers []string `json:"adUsers" yaml:"adUsers"` ReaderAdGroups []string `json:"readerAdGroups" yaml:"readerAdGroups"` + ReaderAdUsers []string `json:"readerAdUsers" yaml:"readerAdUsers"` Creator string `json:"creator" yaml:"creator"` Owner string `json:"owner" yaml:"owner"` WBS string `json:"wbs" yaml:"wbs"` diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index 99b1d5c58..78473fdd2 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -2763,11 +2763,21 @@ func (in *RadixRegistrationSpec) DeepCopyInto(out *RadixRegistrationSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.AdUsers != nil { + in, out := &in.AdUsers, &out.AdUsers + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.ReaderAdGroups != nil { in, out := &in.ReaderAdGroups, &out.ReaderAdGroups *out = make([]string, len(*in)) copy(*out, *in) } + if in.ReaderAdUsers != nil { + in, out := &in.ReaderAdUsers, &out.ReaderAdUsers + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/apis/radixvalidators/validate_rr.go b/pkg/apis/radixvalidators/validate_rr.go index 761bb735a..a94fa0fae 100644 --- a/pkg/apis/radixvalidators/validate_rr.go +++ b/pkg/apis/radixvalidators/validate_rr.go @@ -20,12 +20,14 @@ const ( var ( ErrInvalidRadixConfigFullName = stderrors.New("invalid file name for radixconfig. See https://www.radix.equinor.com/references/reference-radix-config/ for more information") + ErrInvalidEntraUuid = stderrors.New("invalid Entra uuid") + validUuidRegex = regexp.MustCompile("^([A-Za-z0-9]{8})-([A-Za-z0-9]{4})-([A-Za-z0-9]{4})-([A-Za-z0-9]{4})-([A-Za-z0-9]{12})$") - requiredRadixRegistrationValidators []RadixRegistrationValidator = []RadixRegistrationValidator{ + requiredRadixRegistrationValidators = []RadixRegistrationValidator{ validateRadixRegistrationAppName, validateRadixRegistrationGitSSHUrl, validateRadixRegistrationSSHKey, - validateRadixRegistrationAdGroups, + validateRadixRegistrationAdConfig, validateRadixRegistrationConfigBranch, validateRadixRegistrationConfigurationItem, } @@ -138,18 +140,24 @@ func validateRequiredResourceName(resourceName, value string, maxLength int) err return nil } -func validateRadixRegistrationAdGroups(rr *v1.RadixRegistration) error { - return validateAdGroups(rr.Spec.AdGroups) -} - -func validateAdGroups(groups []string) error { - re := regexp.MustCompile("^([A-Za-z0-9]{8})-([A-Za-z0-9]{4})-([A-Za-z0-9]{4})-([A-Za-z0-9]{4})-([A-Za-z0-9]{12})$") - for _, group := range groups { - isValid := re.MatchString(group) - if !isValid { - return fmt.Errorf("refer ad group %s by object id. It should be in uuid format %s", group, re.String()) +func validateRadixRegistrationAdConfig(rr *v1.RadixRegistration) error { + errs := []error{} + for _, group := range rr.Spec.AdGroups { + if !validUuidRegex.MatchString(group) { + errs = append(errs, fmt.Errorf("%w: %s", ErrInvalidEntraUuid, group)) + } + } + for _, group := range rr.Spec.AdUsers { + if !validUuidRegex.MatchString(group) { + errs = append(errs, fmt.Errorf("%w: %s", ErrInvalidEntraUuid, group)) } } + + err := stderrors.Join(errs...) + if err != nil { + return fmt.Errorf("radix registration Entra validation error: %w", err) + } + return nil } diff --git a/pkg/apis/utils/applications.go b/pkg/apis/utils/applications.go index 03cb2ad1e..9115e77ee 100644 --- a/pkg/apis/utils/applications.go +++ b/pkg/apis/utils/applications.go @@ -10,27 +10,31 @@ import ( rbacv1 "k8s.io/api/rbac/v1" ) -// GetAdGroups Gets ad-groups from registration. If missing, gives default for cluster -func GetAdGroups(registration *v1.RadixRegistration) ([]string, error) { - if registration.Spec.AdGroups == nil || len(registration.Spec.AdGroups) <= 0 { - defaultGroup := os.Getenv(defaults.OperatorDefaultUserGroupEnvironmentVariable) - if defaultGroup == "" { - err := fmt.Errorf("cannot obtain ad-group as %s has not been set for the operator", defaults.OperatorDefaultUserGroupEnvironmentVariable) - return []string{}, err - } - - return []string{defaultGroup}, nil +// GetAppAdminRbacSubjects Get Role bindings for application admins +func GetAppAdminRbacSubjects(rr *v1.RadixRegistration) ([]rbacv1.Subject, error) { + adGroups, err := getAdGroupsWithDefault(rr) + if err != nil { + return nil, err } - return registration.Spec.AdGroups, nil + return kube.GetRoleBindingSubjects(adGroups, rr.Spec.AdUsers), nil } -// GetAppAdminRbacSubjects Get Role bindings for application admin -func GetAppAdminRbacSubjects(rr *v1.RadixRegistration) ([]rbacv1.Subject, error) { - adGroups, err := GetAdGroups(rr) - if err != nil { - return nil, err +// GetAppReaderRbacSubjects Get Role bindings for application readers +func GetAppReaderRbacSubjects(rr *v1.RadixRegistration) []rbacv1.Subject { + return kube.GetRoleBindingSubjects(rr.Spec.ReaderAdGroups, rr.Spec.ReaderAdUsers) +} + +func getAdGroupsWithDefault(registration *v1.RadixRegistration) ([]string, error) { + if len(registration.Spec.AdGroups) > 0 { + return registration.Spec.AdGroups, nil } - subjects := kube.GetRoleBindingGroups(adGroups) - return subjects, nil + + defaultGroup := os.Getenv(defaults.OperatorDefaultUserGroupEnvironmentVariable) + if defaultGroup != "" { + return []string{defaultGroup}, nil + } + + err := fmt.Errorf("cannot obtain ad-group as %s has not been set for the operator", defaults.OperatorDefaultUserGroupEnvironmentVariable) + return []string{}, err } diff --git a/pkg/apis/utils/registration_builder.go b/pkg/apis/utils/registration_builder.go index dacb2e136..e3079e974 100644 --- a/pkg/apis/utils/registration_builder.go +++ b/pkg/apis/utils/registration_builder.go @@ -16,6 +16,7 @@ type RegistrationBuilder interface { WithRepository(string) RegistrationBuilder WithSharedSecret(string) RegistrationBuilder WithAdGroups([]string) RegistrationBuilder + WithAdUsers([]string) RegistrationBuilder WithPublicKey(string) RegistrationBuilder WithPrivateKey(string) RegistrationBuilder WithCloneURL(string) RegistrationBuilder @@ -28,6 +29,7 @@ type RegistrationBuilder interface { WithConfigurationItem(string) RegistrationBuilder WithRadixRegistration(*v1.RadixRegistration) RegistrationBuilder WithReaderAdGroups([]string) RegistrationBuilder + WithReaderAdUsers([]string) RegistrationBuilder BuildRR() *v1.RadixRegistration } @@ -38,6 +40,7 @@ type RegistrationBuilderStruct struct { repository string sharedSecret string adGroups []string + adUsers []string publicKey string privateKey string cloneURL string @@ -49,6 +52,7 @@ type RegistrationBuilderStruct struct { radixConfigFullName string configurationItem string readerAdGroups []string + readerAdUsers []string } // WithRadixRegistration Re-enginers a builder from a registration @@ -57,7 +61,9 @@ func (rb *RegistrationBuilderStruct) WithRadixRegistration(radixRegistration *v1 rb.WithCloneURL(radixRegistration.Spec.CloneURL) rb.WithSharedSecret(radixRegistration.Spec.SharedSecret) rb.WithAdGroups(radixRegistration.Spec.AdGroups) + rb.WithAdUsers(radixRegistration.Spec.AdUsers) rb.WithReaderAdGroups(radixRegistration.Spec.ReaderAdGroups) + rb.WithReaderAdUsers(radixRegistration.Spec.ReaderAdUsers) rb.WithPublicKey(radixRegistration.Spec.DeployKeyPublic) rb.WithPrivateKey(radixRegistration.Spec.DeployKey) rb.WithOwner(radixRegistration.Spec.Owner) @@ -116,12 +122,24 @@ func (rb *RegistrationBuilderStruct) WithAdGroups(adGroups []string) Registratio return rb } +// WithAdUsers Sets ad user +func (rb *RegistrationBuilderStruct) WithAdUsers(adUsers []string) RegistrationBuilder { + rb.adUsers = adUsers + return rb +} + // WithReaderAdGroups Sets reader ad group func (rb *RegistrationBuilderStruct) WithReaderAdGroups(readerAdGroups []string) RegistrationBuilder { rb.readerAdGroups = readerAdGroups return rb } +// WithReaderAdUsers Sets reader ad user +func (rb *RegistrationBuilderStruct) WithReaderAdUsers(readerAdUsers []string) RegistrationBuilder { + rb.readerAdUsers = readerAdUsers + return rb +} + // WithPublicKey Sets public key func (rb *RegistrationBuilderStruct) WithPublicKey(publicKey string) RegistrationBuilder { rb.publicKey = strings.TrimSuffix(publicKey, "\n") @@ -193,7 +211,9 @@ func (rb *RegistrationBuilderStruct) BuildRR() *v1.RadixRegistration { DeployKey: rb.privateKey, DeployKeyPublic: rb.publicKey, AdGroups: rb.adGroups, + AdUsers: rb.adUsers, ReaderAdGroups: rb.readerAdGroups, + ReaderAdUsers: rb.readerAdUsers, Owner: rb.owner, Creator: rb.creator, WBS: rb.wbs, diff --git a/pkg/apis/utils/secrets.go b/pkg/apis/utils/secrets.go index b0a2dc93f..e39453495 100644 --- a/pkg/apis/utils/secrets.go +++ b/pkg/apis/utils/secrets.go @@ -87,10 +87,7 @@ func GrantAppReaderAccessToSecret(ctx context.Context, kubeutil *kube.Kube, regi } // create rolebinding - readerAdGroups := registration.Spec.ReaderAdGroups - - subjects := kube.GetRoleBindingGroups(readerAdGroups) - + subjects := GetAppReaderRbacSubjects(registration) rolebinding := kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) return kubeutil.ApplyRoleBinding(ctx, namespace, rolebinding) } @@ -107,12 +104,11 @@ func GrantAppAdminAccessToSecret(ctx context.Context, kubeutil *kube.Kube, regis } // create rolebinding - adGroups, err := GetAdGroups(registration) + subjects, err := GetAppAdminRbacSubjects(registration) if err != nil { return err } - subjects := kube.GetRoleBindingGroups(adGroups) rolebinding := kube.GetRolebindingToRoleWithLabelsForSubjects(roleName, subjects, role.Labels) return kubeutil.ApplyRoleBinding(ctx, namespace, rolebinding) } diff --git a/radix-operator/alert/controller.go b/radix-operator/alert/controller.go index 4262ee40a..1f34d5863 100644 --- a/radix-operator/alert/controller.go +++ b/radix-operator/alert/controller.go @@ -99,7 +99,9 @@ func NewController(ctx context.Context, // If neither admin nor reader AD groups change, this // does not affect the alert if radixutils.ArrayEqualElements(newRr.Spec.AdGroups, oldRr.Spec.AdGroups) && - radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) { + radixutils.ArrayEqualElements(newRr.Spec.AdUsers, oldRr.Spec.AdUsers) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdUsers, oldRr.Spec.ReaderAdUsers) { return } diff --git a/radix-operator/alert/controller_test.go b/radix-operator/alert/controller_test.go index 87eaaed8f..a47be1420 100644 --- a/radix-operator/alert/controller_test.go +++ b/radix-operator/alert/controller_test.go @@ -109,9 +109,16 @@ func (s *controllerTestSuite) Test_RadixRegistrationEvents() { s.Handler.EXPECT().Sync(gomock.Any(), namespace, alert1Name, s.EventRecorder).DoAndReturn(s.SyncedChannelCallback()).Times(1) s.WaitForSynced("sync on ReaderAdGroups update") + // Update adUsers should trigger sync of alert1 + rr.Spec.AdUsers = []string{"another-user"} + rr.ResourceVersion = "4" + rr, _ = s.RadixClient.RadixV1().RadixRegistrations().Update(context.Background(), rr, metav1.UpdateOptions{}) + s.Handler.EXPECT().Sync(gomock.Any(), namespace, alert1Name, s.EventRecorder).DoAndReturn(s.SyncedChannelCallback()).Times(1) + s.WaitForSynced("sync on AdUsers update") + // Update other props on RR should not trigger sync of alert1 rr.Spec.Owner = "owner" - rr.ResourceVersion = "4" + rr.ResourceVersion = "5" _, err = s.RadixClient.RadixV1().RadixRegistrations().Update(context.Background(), rr, metav1.UpdateOptions{}) s.Require().NoError(err) diff --git a/radix-operator/application/controller.go b/radix-operator/application/controller.go index d45f54913..25b784878 100644 --- a/radix-operator/application/controller.go +++ b/radix-operator/application/controller.go @@ -94,7 +94,9 @@ func NewController(ctx context.Context, // If neither admin or reader AD groups change, this // does not affect the deployment if radixutils.ArrayEqualElements(newRr.Spec.AdGroups, oldRr.Spec.AdGroups) && - radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) { + radixutils.ArrayEqualElements(newRr.Spec.AdUsers, oldRr.Spec.AdUsers) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdUsers, oldRr.Spec.ReaderAdUsers) { return } ra, err := radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(newRr.Name)).Get(ctx, newRr.Name, metav1.GetOptions{}) diff --git a/radix-operator/deployment/controller.go b/radix-operator/deployment/controller.go index d70bf0170..a05943a04 100644 --- a/radix-operator/deployment/controller.go +++ b/radix-operator/deployment/controller.go @@ -138,7 +138,9 @@ func NewController(ctx context.Context, // If neither admin or reader AD groups change, this // does not affect the deployment if radixutils.ArrayEqualElements(newRr.Spec.AdGroups, oldRr.Spec.AdGroups) && - radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) { + radixutils.ArrayEqualElements(newRr.Spec.AdUsers, oldRr.Spec.AdUsers) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdUsers, oldRr.Spec.ReaderAdUsers) { return } diff --git a/radix-operator/dnsalias/controller.go b/radix-operator/dnsalias/controller.go index 84b9e9286..e7e5aa60b 100644 --- a/radix-operator/dnsalias/controller.go +++ b/radix-operator/dnsalias/controller.go @@ -67,7 +67,9 @@ func addEventHandlersForRadixRegistrations(radixInformerFactory informers.Shared newRR := newObj.(*radixv1.RadixRegistration) if oldRR.GetResourceVersion() == newRR.GetResourceVersion() && radixutils.ArrayEqualElements(oldRR.Spec.AdGroups, newRR.Spec.AdGroups) && - radixutils.ArrayEqualElements(oldRR.Spec.ReaderAdGroups, newRR.Spec.ReaderAdGroups) { + radixutils.ArrayEqualElements(oldRR.Spec.AdUsers, newRR.Spec.AdUsers) && + radixutils.ArrayEqualElements(oldRR.Spec.ReaderAdGroups, newRR.Spec.ReaderAdGroups) && + radixutils.ArrayEqualElements(oldRR.Spec.ReaderAdUsers, newRR.Spec.ReaderAdUsers) { return // updating RadixRegistration has the same resource version. Do nothing. } enqueueRadixDNSAliasesForAppName(controller, radixClient, newRR.GetName(), logger) diff --git a/radix-operator/environment/controller.go b/radix-operator/environment/controller.go index ff25d0c90..6b39e4b3b 100644 --- a/radix-operator/environment/controller.go +++ b/radix-operator/environment/controller.go @@ -133,7 +133,9 @@ func NewController(ctx context.Context, // If neither admin or reader AD groups change, this // does not affect the deployment if radixutils.ArrayEqualElements(newRr.Spec.AdGroups, oldRr.Spec.AdGroups) && - radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) { + radixutils.ArrayEqualElements(newRr.Spec.AdUsers, oldRr.Spec.AdUsers) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdGroups, oldRr.Spec.ReaderAdGroups) && + radixutils.ArrayEqualElements(newRr.Spec.ReaderAdUsers, oldRr.Spec.ReaderAdUsers) { return }