From fd340dfc96dbe55ae933e802d48f02348339d1e0 Mon Sep 17 00:00:00 2001 From: Ran Wurmbrand Date: Mon, 29 Jun 2026 15:03:11 +0300 Subject: [PATCH 1/2] Add tier1 tests for cluster-level RBAC subject types Add e2e tests covering ClusterRoleBinding subject variations: - MTA-857: ServiceAccount subject type - MTA-858: User subject type - MTA-859: Group subject type Signed-off-by: Ran Wurmbrand --- e2e-tests/framework/resources.go | 7 +- .../mta_857_service_account_subject_test.go | 99 +++++++++++++++++ .../tests/tier1/mta_858_user_subject_test.go | 99 +++++++++++++++++ .../tests/tier1/mta_859_group_subject_test.go | 101 ++++++++++++++++++ 4 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 e2e-tests/tests/tier1/mta_857_service_account_subject_test.go create mode 100644 e2e-tests/tests/tier1/mta_858_user_subject_test.go create mode 100644 e2e-tests/tests/tier1/mta_859_group_subject_test.go diff --git a/e2e-tests/framework/resources.go b/e2e-tests/framework/resources.go index 405e6675..fb8d3e37 100644 --- a/e2e-tests/framework/resources.go +++ b/e2e-tests/framework/resources.go @@ -69,10 +69,15 @@ type ClusterRoleBinding struct { Name string ClusterRoleName string Label string + Subject string } func (crb ClusterRoleBinding) Create(k KubectlRunner) error { - _, err := k.Run("create", "clusterrolebinding", crb.Name, "--clusterrole="+crb.ClusterRoleName) + args := []string{"create", "clusterrolebinding", crb.Name, "--clusterrole=" + crb.ClusterRoleName} + if crb.Subject != "" { + args = append(args, crb.Subject) + } + _, err := k.Run(args...) if err != nil { return fmt.Errorf("failed to create ClusterRoleBinding %s: %w", crb.Name, err) } diff --git a/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go b/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go new file mode 100644 index 00000000..9faf42b1 --- /dev/null +++ b/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go @@ -0,0 +1,99 @@ +package e2e + +import ( + "log" + "path/filepath" + + "github.com/konveyor/crane/e2e-tests/config" + . "github.com/konveyor/crane/e2e-tests/framework" + "github.com/konveyor/crane/e2e-tests/utils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Cluster-level RBAC export", func() { + It("[MTA-857] Should export CRB with ServiceAccount subject referencing ServiceAccount", Label("tier1"), func() { + appName := "simple-nginx-nopv" + namespace := "simple-nginx-nopv" + serviceName := "my-" + appName + scenario := NewMigrationScenario( + appName, + namespace, + config.K8sDeployBin, + config.CraneBin, + config.SourceContext, + config.TargetContext, + ) + srcApp := scenario.SrcApp + tgtApp := scenario.TgtApp + kubectlSrc := scenario.KubectlSrc + kubectlTgt := scenario.KubectlTgt + runner := scenario.Crane + paths, err := NewScenarioPaths("crane-*") + Expect(err).NotTo(HaveOccurred()) + + sa := ServiceAccount{Name: "crane-sa", Namespace: namespace} + subjectName := sa.Name + subject := "--serviceaccount=" + namespace + ":" + sa.Name + + cr := ClusterRole{Name: "crane-cr", Verb: "get,list,watch,create,update,delete", Resource: "pods"} + crb := ClusterRoleBinding{Name: "crane-crb", ClusterRoleName: cr.Name, Subject: subject} + tgtNamespace := Namespace{Name: namespace} + + exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir} + transformOpts := TransformOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir} + applyOpts := ApplyOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir, + OutputDir: paths.OutputDir} + + DeferCleanup(func() { + if err := ResourceCleanup([]KubectlRunner{kubectlSrc, kubectlTgt}, []Resource{cr, crb}); err != nil { + log.Printf("Resources cleanup: %v", err) + } + if err := CleanupScenario(paths.TempDir, srcApp, tgtApp); err != nil { + log.Printf("Scenario cleanup: %v", err) + } + }) + + By("Deploying app on source cluster") + Expect(PrepareSourceApp(srcApp, kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating Creating first Service-account source") + Expect(sa.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating ClusterRole on source Clusster") + Expect(cr.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating ClusterRoleBinding with User subject referencing ServiceAccount") + Expect(crb.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Waiting for source pods and endpoints to drain") + WaitForSourceQuiesce(kubectlSrc, namespace, "app="+appName, serviceName) + + By("Running crane export, transform, apply") + Expect(RunCranePipelineWithChecks(runner, exportOpts, transformOpts, applyOpts)).NotTo(HaveOccurred()) + + By("Verifying no resources failed to export") + failuresDir := filepath.Join(paths.ExportDir, "failures", namespace) + hasFiles, _, err := utils.HasFilesRecursively(failuresDir) + Expect(err).NotTo(HaveOccurred()) + Expect(hasFiles).To(BeFalse()) + + By("Create Namespace On target cluster") + Expect(tgtNamespace.Create(kubectlTgt)).NotTo(HaveOccurred()) + + By("Dry-run applying output manifests on target") + Expect(kubectlTgt.ValidateApplyDir(paths.OutputDir)).NotTo(HaveOccurred()) + + By("Applying migrated manifests to target cluster") + Expect(ApplyOutputToTarget(kubectlTgt, tgtNamespace.Name, paths.OutputDir)).NotTo(HaveOccurred()) + + By("Scaling target deployment and validating app") + Expect(kubectlTgt.ScaleDeployment(tgtNamespace.Name, appName, 1)).NotTo(HaveOccurred()) + Eventually(tgtApp.Validate, "2m", "10s").Should(Succeed()) + + By("Verifying ClusterRoleBinding on target references correct ClusterRole and User subject") + Expect(ValidateClusterRBAC(kubectlTgt, []ExpectedClusterRoleBinding{ + {ClusterRoleBindingName: crb.Name, ClusterRoleName: crb.ClusterRoleName, SubjectName: subjectName}, + })).NotTo(HaveOccurred()) + }) +}) diff --git a/e2e-tests/tests/tier1/mta_858_user_subject_test.go b/e2e-tests/tests/tier1/mta_858_user_subject_test.go new file mode 100644 index 00000000..463a3960 --- /dev/null +++ b/e2e-tests/tests/tier1/mta_858_user_subject_test.go @@ -0,0 +1,99 @@ +package e2e + +import ( + "log" + "path/filepath" + + "github.com/konveyor/crane/e2e-tests/config" + . "github.com/konveyor/crane/e2e-tests/framework" + "github.com/konveyor/crane/e2e-tests/utils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Cluster-level RBAC export", func() { + It("[MTA-858] Should export CRB with User subject referencing ServiceAccount", Label("tier1"), func() { + appName := "simple-nginx-nopv" + namespace := "simple-nginx-nopv" + serviceName := "my-" + appName + scenario := NewMigrationScenario( + appName, + namespace, + config.K8sDeployBin, + config.CraneBin, + config.SourceContext, + config.TargetContext, + ) + srcApp := scenario.SrcApp + tgtApp := scenario.TgtApp + kubectlSrc := scenario.KubectlSrc + kubectlTgt := scenario.KubectlTgt + runner := scenario.Crane + paths, err := NewScenarioPaths("crane-*") + Expect(err).NotTo(HaveOccurred()) + + sa := ServiceAccount{Name: "crane-sa", Namespace: namespace} + subjectName := "system:serviceaccount:" + namespace + ":" + sa.Name + subject := "--user=" + subjectName + + cr := ClusterRole{Name: "crane-cr", Verb: "get,list,watch,create,update,delete", Resource: "pods"} + crb := ClusterRoleBinding{Name: "crane-crb", ClusterRoleName: cr.Name, Subject: subject} + tgtNamespace := Namespace{Name: namespace} + + exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir} + transformOpts := TransformOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir} + applyOpts := ApplyOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir, + OutputDir: paths.OutputDir} + + DeferCleanup(func() { + if err := ResourceCleanup([]KubectlRunner{kubectlSrc, kubectlTgt}, []Resource{cr, crb}); err != nil { + log.Printf("Resources cleanup: %v", err) + } + if err := CleanupScenario(paths.TempDir, srcApp, tgtApp); err != nil { + log.Printf("Scenario cleanup: %v", err) + } + }) + + By("Deploying app on source cluster") + Expect(PrepareSourceApp(srcApp, kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating Creating first Service-account source") + Expect(sa.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating ClusterRole on source Clusster") + Expect(cr.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating ClusterRoleBinding with User subject referencing ServiceAccount") + Expect(crb.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Waiting for source pods and endpoints to drain") + WaitForSourceQuiesce(kubectlSrc, namespace, "app="+appName, serviceName) + + By("Running crane export, transform, apply") + Expect(RunCranePipelineWithChecks(runner, exportOpts, transformOpts, applyOpts)).NotTo(HaveOccurred()) + + By("Verifying no resources failed to export") + failuresDir := filepath.Join(paths.ExportDir, "failures", namespace) + hasFiles, _, err := utils.HasFilesRecursively(failuresDir) + Expect(err).NotTo(HaveOccurred()) + Expect(hasFiles).To(BeFalse()) + + By("Create Namespace On target cluster") + Expect(tgtNamespace.Create(kubectlTgt)).NotTo(HaveOccurred()) + + By("Dry-run applying output manifests on target") + Expect(kubectlTgt.ValidateApplyDir(paths.OutputDir)).NotTo(HaveOccurred()) + + By("Applying migrated manifests to target cluster") + Expect(ApplyOutputToTarget(kubectlTgt, tgtNamespace.Name, paths.OutputDir)).NotTo(HaveOccurred()) + + By("Scaling target deployment and validating app") + Expect(kubectlTgt.ScaleDeployment(tgtNamespace.Name, appName, 1)).NotTo(HaveOccurred()) + Eventually(tgtApp.Validate, "2m", "10s").Should(Succeed()) + + By("Verifying ClusterRoleBinding on target references correct ClusterRole and User subject") + Expect(ValidateClusterRBAC(kubectlTgt, []ExpectedClusterRoleBinding{ + {ClusterRoleBindingName: crb.Name, ClusterRoleName: crb.ClusterRoleName, SubjectName: subjectName}, + })).NotTo(HaveOccurred()) + }) +}) diff --git a/e2e-tests/tests/tier1/mta_859_group_subject_test.go b/e2e-tests/tests/tier1/mta_859_group_subject_test.go new file mode 100644 index 00000000..a4469d07 --- /dev/null +++ b/e2e-tests/tests/tier1/mta_859_group_subject_test.go @@ -0,0 +1,101 @@ +package e2e + +import ( + "log" + "path/filepath" + + "github.com/konveyor/crane/e2e-tests/config" + . "github.com/konveyor/crane/e2e-tests/framework" + "github.com/konveyor/crane/e2e-tests/utils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Cluster-level RBAC export", func() { + It("[MTA-859] Should export CRB with Group subject referencing namespace ServiceAccounts", Label("tier1"), func() { + appName := "simple-nginx-nopv" + namespace := "simple-nginx-nopv" + serviceName := "my-" + appName + + scenario := NewMigrationScenario( + appName, + namespace, + config.K8sDeployBin, + config.CraneBin, + config.SourceContext, + config.TargetContext, + ) + srcApp := scenario.SrcApp + tgtApp := scenario.TgtApp + kubectlSrc := scenario.KubectlSrc + kubectlTgt := scenario.KubectlTgt + runner := scenario.Crane + paths, err := NewScenarioPaths("crane-ca4c-*") + Expect(err).NotTo(HaveOccurred()) + + sa := ServiceAccount{Name: "crane-sa", Namespace: namespace} + subjectName := "system:serviceaccounts:" + namespace + subject := "--group=" + subjectName + + cr := ClusterRole{Name: "crane-cr", Verb: "get,list,watch,create,update,delete", Resource: "pods"} + crb := ClusterRoleBinding{Name: "crane-crb", ClusterRoleName: cr.Name, Subject: subject} + tgtNamespace := Namespace{Name: namespace} + + exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir} + transformOpts := TransformOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir} + applyOpts := ApplyOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir, + OutputDir: paths.OutputDir} + + DeferCleanup(func() { + if err := ResourceCleanup([]KubectlRunner{kubectlSrc, kubectlTgt}, []Resource{cr, crb}); err != nil { + log.Printf("Resources cleanup: %v", err) + } + if err := CleanupScenario(paths.TempDir, srcApp, tgtApp); err != nil { + log.Printf("Scenario cleanup: %v", err) + } + }) + + By("Deploying app with ServiceAccount on source cluster") + Expect(PrepareSourceApp(srcApp, kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating Creating first Service-account source") + Expect(sa.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating ClusterRole with pod read permissions") + Expect(cr.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Creating ClusterRoleBinding with Group subject for namespace ServiceAccounts") + Expect(crb.Create(kubectlSrc)).NotTo(HaveOccurred()) + + By("Waiting for source pods and endpoints to drain") + WaitForSourceQuiesce(kubectlSrc, namespace, "app="+appName, serviceName) + + By("Running crane export, transform, apply") + Expect(RunCranePipelineWithChecks(runner, exportOpts, transformOpts, applyOpts)).NotTo(HaveOccurred()) + + By("Verifying no resources failed to export") + failuresDir := filepath.Join(paths.ExportDir, "failures", namespace) + hasFiles, _, err := utils.HasFilesRecursively(failuresDir) + Expect(err).NotTo(HaveOccurred()) + Expect(hasFiles).To(BeFalse()) + + By("Create Namespace On target cluster") + Expect(tgtNamespace.Create(kubectlTgt)).NotTo(HaveOccurred()) + + By("Dry-run applying output manifests on target") + Expect(kubectlTgt.ValidateApplyDir(paths.OutputDir)).NotTo(HaveOccurred()) + + By("Applying migrated manifests to target cluster") + Expect(ApplyOutputToTarget(kubectlTgt, namespace, paths.OutputDir)).NotTo(HaveOccurred()) + + By("Scaling target deployment and validating app") + Expect(kubectlTgt.ScaleDeployment(tgtNamespace.Name, appName, 1)).NotTo(HaveOccurred()) + Eventually(tgtApp.Validate, "2m", "10s").Should(Succeed()) + + By("Verifying ClusterRoleBinding on target references correct ClusterRole and Group subject") + Expect(ValidateClusterRBAC(kubectlTgt, []ExpectedClusterRoleBinding{ + {ClusterRoleBindingName: crb.Name, ClusterRoleName: crb.ClusterRoleName, SubjectName: subjectName}, + })).NotTo(HaveOccurred()) + }) + +}) From 7b867ad918e7d0b5414df7d0c6fa6297e296b27d Mon Sep 17 00:00:00 2001 From: Ran Wurmbrand Date: Mon, 29 Jun 2026 15:59:23 +0300 Subject: [PATCH 2/2] addressed coderabit comments Signed-off-by: Ran Wurmbrand --- e2e-tests/tests/tier1/mta_857_service_account_subject_test.go | 4 ++-- e2e-tests/tests/tier1/mta_858_user_subject_test.go | 4 ++-- e2e-tests/tests/tier1/mta_859_group_subject_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go b/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go index 9faf42b1..e2741c72 100644 --- a/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go +++ b/e2e-tests/tests/tier1/mta_857_service_account_subject_test.go @@ -36,8 +36,8 @@ var _ = Describe("Cluster-level RBAC export", func() { subjectName := sa.Name subject := "--serviceaccount=" + namespace + ":" + sa.Name - cr := ClusterRole{Name: "crane-cr", Verb: "get,list,watch,create,update,delete", Resource: "pods"} - crb := ClusterRoleBinding{Name: "crane-crb", ClusterRoleName: cr.Name, Subject: subject} + cr := ClusterRole{Name: "crane-cr-subject-sa", Verb: "get,list,watch,create,update,delete", Resource: "pods"} + crb := ClusterRoleBinding{Name: "crane-crb-subject-sa", ClusterRoleName: cr.Name, Subject: subject} tgtNamespace := Namespace{Name: namespace} exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir} diff --git a/e2e-tests/tests/tier1/mta_858_user_subject_test.go b/e2e-tests/tests/tier1/mta_858_user_subject_test.go index 463a3960..656ace13 100644 --- a/e2e-tests/tests/tier1/mta_858_user_subject_test.go +++ b/e2e-tests/tests/tier1/mta_858_user_subject_test.go @@ -36,8 +36,8 @@ var _ = Describe("Cluster-level RBAC export", func() { subjectName := "system:serviceaccount:" + namespace + ":" + sa.Name subject := "--user=" + subjectName - cr := ClusterRole{Name: "crane-cr", Verb: "get,list,watch,create,update,delete", Resource: "pods"} - crb := ClusterRoleBinding{Name: "crane-crb", ClusterRoleName: cr.Name, Subject: subject} + cr := ClusterRole{Name: "crane-cr-subject-user", Verb: "get,list,watch,create,update,delete", Resource: "pods"} + crb := ClusterRoleBinding{Name: "crane-crb-subject-user", ClusterRoleName: cr.Name, Subject: subject} tgtNamespace := Namespace{Name: namespace} exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir} diff --git a/e2e-tests/tests/tier1/mta_859_group_subject_test.go b/e2e-tests/tests/tier1/mta_859_group_subject_test.go index a4469d07..9369a6f0 100644 --- a/e2e-tests/tests/tier1/mta_859_group_subject_test.go +++ b/e2e-tests/tests/tier1/mta_859_group_subject_test.go @@ -37,8 +37,8 @@ var _ = Describe("Cluster-level RBAC export", func() { subjectName := "system:serviceaccounts:" + namespace subject := "--group=" + subjectName - cr := ClusterRole{Name: "crane-cr", Verb: "get,list,watch,create,update,delete", Resource: "pods"} - crb := ClusterRoleBinding{Name: "crane-crb", ClusterRoleName: cr.Name, Subject: subject} + cr := ClusterRole{Name: "crane-cr-subject-group", Verb: "get,list,watch,create,update,delete", Resource: "pods"} + crb := ClusterRoleBinding{Name: "crane-crb-subject-group", ClusterRoleName: cr.Name, Subject: subject} tgtNamespace := Namespace{Name: namespace} exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir}