diff --git a/Documentation/CRDs/specification.md b/Documentation/CRDs/specification.md index ed9c73bb6..ce61b98c4 100644 --- a/Documentation/CRDs/specification.md +++ b/Documentation/CRDs/specification.md @@ -3120,6 +3120,19 @@ CSICephFSSpec

CephFS defines CSI Driver settings for CephFS driver.

+ + +skipUserCreation
+ +bool + + + +(Optional) +

SkipUserCreation determines whether CSI users and their associated secrets should be skipped. +If set to true, the user must manually manage these secrets.

+ +

Capacity diff --git a/deploy/charts/rook-ceph/templates/resources.yaml b/deploy/charts/rook-ceph/templates/resources.yaml index feddc45bd..434d25821 100644 --- a/deploy/charts/rook-ceph/templates/resources.yaml +++ b/deploy/charts/rook-ceph/templates/resources.yaml @@ -1560,6 +1560,11 @@ spec: description: Enables read affinity for CSI driver. type: boolean type: object + skipUserCreation: + description: |- + SkipUserCreation determines whether CSI users and their associated secrets should be skipped. + If set to true, the user must manually manage these secrets. + type: boolean type: object dashboard: description: Dashboard settings diff --git a/deploy/examples/crds.yaml b/deploy/examples/crds.yaml index 13f6dba30..6b17ef685 100644 --- a/deploy/examples/crds.yaml +++ b/deploy/examples/crds.yaml @@ -1558,6 +1558,11 @@ spec: description: Enables read affinity for CSI driver. type: boolean type: object + skipUserCreation: + description: |- + SkipUserCreation determines whether CSI users and their associated secrets should be skipped. + If set to true, the user must manually manage these secrets. + type: boolean type: object dashboard: description: Dashboard settings diff --git a/pkg/apis/ceph.rook.io/v1/types.go b/pkg/apis/ceph.rook.io/v1/types.go index 538feac96..a2cb12a06 100755 --- a/pkg/apis/ceph.rook.io/v1/types.go +++ b/pkg/apis/ceph.rook.io/v1/types.go @@ -255,6 +255,10 @@ type CSIDriverSpec struct { // CephFS defines CSI Driver settings for CephFS driver. // +optional CephFS CSICephFSSpec `json:"cephfs,omitempty"` + // SkipUserCreation determines whether CSI users and their associated secrets should be skipped. + // If set to true, the user must manually manage these secrets. + // +optional + SkipUserCreation bool `json:"skipUserCreation,omitempty"` } // CSICephFSSpec defines the settings for CephFS CSI driver. diff --git a/pkg/operator/ceph/csi/secrets.go b/pkg/operator/ceph/csi/secrets.go index 86739101f..28c424d02 100644 --- a/pkg/operator/ceph/csi/secrets.go +++ b/pkg/operator/ceph/csi/secrets.go @@ -17,6 +17,8 @@ limitations under the License. package csi import ( + "fmt" + "github.com/pkg/errors" "github.com/rook/rook/pkg/clusterd" "github.com/rook/rook/pkg/daemon/ceph/client" @@ -172,6 +174,13 @@ func createOrUpdateCSISecret(clusterInfo *client.ClusterInfo, csiRBDProvisionerS // CreateCSISecrets creates all the Kubernetes CSI Secrets func CreateCSISecrets(context *clusterd.Context, clusterInfo *client.ClusterInfo) error { + if clusterInfo.CSIDriverSpec.SkipUserCreation { + if err := deleteOwnedCSISecretsByCephCluster(context, clusterInfo); err != nil { + return err + } + logger.Info("CSI user creation is disabled; skipping user and secret creation") + return nil + } k := keyring.GetSecretStore(context, clusterInfo, clusterInfo.OwnerInfo) // Create CSI RBD Provisioner Ceph key @@ -205,3 +214,26 @@ func CreateCSISecrets(context *clusterd.Context, clusterInfo *client.ClusterInfo return nil } + +func deleteOwnedCSISecretsByCephCluster(context *clusterd.Context, clusterInfo *client.ClusterInfo) error { + ownerRef := metav1.OwnerReference{ + APIVersion: "ceph.rook.io/v1", + Kind: "CephCluster", + Name: clusterInfo.NamespacedName().Name, + } + secrets := []string{CsiRBDNodeSecret, CsiRBDProvisionerSecret, CsiCephFSNodeSecret, CsiCephFSProvisionerSecret} + for _, secretName := range secrets { + err := k8sutil.DeleteSecretIfOwnedBy( + clusterInfo.Context, + context.Clientset, + secretName, + clusterInfo.Namespace, + ownerRef, + ) + if err != nil { + return fmt.Errorf("failed to delete secret %q: %w", secretName, err) + } + } + + return nil +} diff --git a/pkg/operator/ceph/csi/secrets_test.go b/pkg/operator/ceph/csi/secrets_test.go index 7b3d2116a..61f3e6cf0 100644 --- a/pkg/operator/ceph/csi/secrets_test.go +++ b/pkg/operator/ceph/csi/secrets_test.go @@ -17,9 +17,17 @@ limitations under the License. package csi import ( + "context" "testing" + "github.com/rook/rook/pkg/clusterd" + "github.com/rook/rook/pkg/daemon/ceph/client" + "github.com/rook/rook/pkg/operator/ceph/config/keyring" + "github.com/rook/rook/pkg/operator/k8sutil" "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sfake "k8s.io/client-go/kubernetes/fake" ) func TestCephCSIKeyringRBDNodeCaps(t *testing.T) { @@ -41,3 +49,78 @@ func TestCephCSIKeyringCephFSProvisionerCaps(t *testing.T) { caps := cephCSIKeyringCephFSProvisionerCaps() assert.Equal(t, caps, []string{"mon", "allow r, allow command 'osd blocklist'", "mgr", "allow rw", "osd", "allow rw tag cephfs metadata=*", "mds", "allow *"}) } + +func Test_deleteOwnedCSISecretsByCephCluster(t *testing.T) { + const ( + namespace = "rook-ceph" + cephCluster = "my-ceph-cluster" + ) + + // Create fake client + clientset := k8sfake.NewSimpleClientset() + + // Cluster context + clusterContext := &clusterd.Context{ + Clientset: clientset, + } + + clusterInfo := &client.ClusterInfo{ + OwnerInfo: k8sutil.NewOwnerInfoWithOwnerRef(&metav1.OwnerReference{ + APIVersion: "ceph.rook.io/v1", + Kind: "CephCluster", + Name: cephCluster, + }, namespace), + } + clusterInfo.Namespace = namespace + clusterInfo.SetName(cephCluster) + + ctx := &clusterd.Context{ + Clientset: clientset, + } + + k := keyring.GetSecretStore(ctx, clusterInfo, clusterInfo.OwnerInfo) + + // create csi secrets + err := createOrUpdateCSISecret(clusterInfo, "", "", "", "", k) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Remove ownerRef from the CsiCephFSNodeSecret to ensure it won't get deleted + secret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), CsiCephFSProvisionerSecret, metav1.GetOptions{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + secret.OwnerReferences = nil + secret.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "ceph.rook.io/v1", + Kind: "CephClient", + Name: "test-client", + }, + }) + _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = deleteOwnedCSISecretsByCephCluster(clusterContext, clusterInfo) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Verify owned secret is deleted + secrets := []string{CsiRBDNodeSecret, CsiRBDProvisionerSecret, CsiCephFSNodeSecret} + for _, secretName := range secrets { + _, err = clientset.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + if err == nil { + t.Errorf("expected owned secret %q to be deleted", secretName) + } + } + // Verify unowned secret still exists + _, err = clientset.CoreV1().Secrets(namespace).Get(context.TODO(), CsiCephFSProvisionerSecret, metav1.GetOptions{}) + if err != nil && apierrors.IsNotFound(err) { + t.Errorf("expected unowned secret %q to still exist", CsiCephFSProvisionerSecret) + } +}