diff --git a/cmd/export/cluster.go b/cmd/export/cluster.go index e41e3a3..8c58ac3 100644 --- a/cmd/export/cluster.go +++ b/cmd/export/cluster.go @@ -218,12 +218,18 @@ func (c *ClusterScopedRbacHandler) acceptSecurityContextConstraints(clusterResou if crb.RoleRef.Kind == "SecurityContextConstraints" && crb.RoleRef.Name == scc.Name { c.log.Infof("Accepted %s of kind %s", clusterResource.GetName(), clusterResource.GetKind()) + c.log.Warnf("WARNING: SecurityContextConstraints '%s' requires elevated privileges on the destination cluster. "+ + "Ensure you have access to appropriate SCCs when applying to the target environment, especially when migrating to OpenShift.", + clusterResource.GetName()) return true } else { sccSystemName := fmt.Sprintf("system:openshift:scc:%s", clusterResource.GetName()) if crb.RoleRef.Kind == "ClusterRole" && crb.RoleRef.Name == sccSystemName { c.log.Infof("Accepted %s of kind %s (match via ClusterRoleBinding %s)", clusterResource.GetName(), clusterResource.GetKind(), crb.Name) + c.log.Warnf("WARNING: SecurityContextConstraints '%s' requires elevated privileges on the destination cluster. "+ + "Ensure you have access to appropriate SCCs when applying to the target environment, especially when migrating to OpenShift.", + clusterResource.GetName()) return true } } @@ -237,6 +243,9 @@ func (c *ClusterScopedRbacHandler) acceptSecurityContextConstraints(clusterResou if c.anyServiceAccountInNamespace(namespaceName, serviceAccountName) { c.log.Infof("Accepted %s of kind %s (match wia user %s)", clusterResource.GetName(), clusterResource.GetKind(), u) + c.log.Warnf("WARNING: SecurityContextConstraints '%s' requires elevated privileges on the destination cluster. "+ + "Ensure you have access to appropriate SCCs when applying to the target environment, especially when migrating to OpenShift.", + clusterResource.GetName()) return true } } diff --git a/cmd/export/cluster_test.go b/cmd/export/cluster_test.go new file mode 100644 index 0000000..6468a46 --- /dev/null +++ b/cmd/export/cluster_test.go @@ -0,0 +1,138 @@ +package export + +import ( + "bytes" + "strings" + "testing" + + securityv1 "github.com/openshift/api/security/v1" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestSecurityContextConstraintsWarning(t *testing.T) { + // Create a test logger that captures output + var logOutput bytes.Buffer + logger := logrus.New() + logger.SetOutput(&logOutput) + logger.SetLevel(logrus.InfoLevel) // Set to Info to capture both info and warning messages + + // Create a test SecurityContextConstraints + scc := &securityv1.SecurityContextConstraints{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "security.openshift.io/v1", + Kind: "SecurityContextConstraints", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-scc", + }, + Users: []string{"system:serviceaccount:test-namespace:test-sa"}, + } + + // Convert to unstructured + sccObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(scc) + if err != nil { + t.Fatalf("Failed to convert SCC to unstructured: %v", err) + } + + sccUnstructured := &unstructured.Unstructured{Object: sccObj} + sccUnstructured.SetGroupVersionKind(securityv1.GroupVersion.WithKind("SecurityContextConstraints")) + + // Create a test service account + saObj := &unstructured.Unstructured{} + saObj.SetAPIVersion("v1") + saObj.SetKind("ServiceAccount") + saObj.SetName("test-sa") + saObj.SetNamespace("test-namespace") + + // Create handler with test logger + handler := NewClusterScopedRbacHandler(logger) + handler.serviceAccounts = []unstructured.Unstructured{*saObj} + handler.filteredClusterRoleBindings = &groupResource{ + objects: &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}}, + } + + // Test the acceptance function + result := handler.acceptSecurityContextConstraints(*sccUnstructured) + + // Check that the function returns true (SCC is accepted) + if !result { + t.Errorf("Expected acceptSecurityContextConstraints to return true, got false") + } + + // Check that warning message is logged + logContents := logOutput.String() + t.Logf("Log output: %s", logContents) // Show the actual log output for verification + + if !strings.Contains(logContents, "WARNING: SecurityContextConstraints 'test-scc' requires elevated privileges") { + t.Errorf("Expected warning message about SecurityContextConstraints privileges, got: %s", logContents) + } + + // Check that the warning mentions destination cluster and OpenShift + if !strings.Contains(logContents, "destination cluster") { + t.Errorf("Expected warning to mention 'destination cluster', got: %s", logContents) + } + + if !strings.Contains(logContents, "OpenShift") { + t.Errorf("Expected warning to mention 'OpenShift', got: %s", logContents) + } +} + +func TestSecurityContextConstraintsNoWarningWhenNotAccepted(t *testing.T) { + // Create a test logger that captures output + var logOutput bytes.Buffer + logger := logrus.New() + logger.SetOutput(&logOutput) + logger.SetLevel(logrus.WarnLevel) + + // Create a test SecurityContextConstraints that won't be accepted + scc := &securityv1.SecurityContextConstraints{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "security.openshift.io/v1", + Kind: "SecurityContextConstraints", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-scc", + }, + Users: []string{"system:serviceaccount:other-namespace:other-sa"}, + } + + // Convert to unstructured + sccObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(scc) + if err != nil { + t.Fatalf("Failed to convert SCC to unstructured: %v", err) + } + + sccUnstructured := &unstructured.Unstructured{Object: sccObj} + sccUnstructured.SetGroupVersionKind(securityv1.GroupVersion.WithKind("SecurityContextConstraints")) + + // Create a test service account with different name/namespace + saObj := &unstructured.Unstructured{} + saObj.SetAPIVersion("v1") + saObj.SetKind("ServiceAccount") + saObj.SetName("test-sa") + saObj.SetNamespace("test-namespace") + + // Create handler with test logger + handler := NewClusterScopedRbacHandler(logger) + handler.serviceAccounts = []unstructured.Unstructured{*saObj} + handler.filteredClusterRoleBindings = &groupResource{ + objects: &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}}, + } + + // Test the acceptance function + result := handler.acceptSecurityContextConstraints(*sccUnstructured) + + // Check that the function returns false (SCC is not accepted) + if result { + t.Errorf("Expected acceptSecurityContextConstraints to return false, got true") + } + + // Check that no warning message is logged since SCC was not accepted + logContents := logOutput.String() + if strings.Contains(logContents, "WARNING: SecurityContextConstraints") { + t.Errorf("Expected no warning message when SCC is not accepted, but got: %s", logContents) + } +} \ No newline at end of file