From a81a21cc5256895fcc65b63b9a655cff6320b2fc Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Fri, 7 Nov 2025 15:08:39 +0100 Subject: [PATCH] Add secret reference in PV annotations for ControllerModifyVolume The ControllerModifyVolume CSI procedure should be able to receive credentials if the storage provider requires them. The values of the following keys in the StorageClass are copied into annotations of the PersistentVolume: - csi.storage.k8s.io/controller-modify-secret-name > volume.kubernetes.io/controller-modify-secret-name - csi.storage.k8s.io/controller-modify-secret-namespace > volume.kubernetes.io/controller-modify-secret-namespace The external-resizer can use these annotations to resolve the secret that needs to be passed in ControllerModifyVolume. --- pkg/controller/controller.go | 44 ++++++++++++++++++++++++++++--- pkg/controller/controller_test.go | 10 +++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 62e480bf58..6c21b3e888 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -105,6 +105,9 @@ const ( prefixedNodeExpandSecretNameKey = csiParameterPrefix + "node-expand-secret-name" prefixedNodeExpandSecretNamespaceKey = csiParameterPrefix + "node-expand-secret-namespace" + prefixedControllerModifySecretNameKey = csiParameterPrefix + "controller-modify-secret-name" + prefixedControllerModifySecretNamespaceKey = csiParameterPrefix + "controller-modify-secret-namespace" + // [Deprecated] CSI Parameters that are put into fields but // NOT stripped from the parameters passed to CreateVolume provisionerSecretNameKey = "csiProvisionerSecretName" @@ -148,6 +151,11 @@ const ( annDeletionProvisionerSecretRefName = "volume.kubernetes.io/provisioner-deletion-secret-name" annDeletionProvisionerSecretRefNamespace = "volume.kubernetes.io/provisioner-deletion-secret-namespace" + // Annotation for secret name and namespace will be added to the pv object + // and used for ControllerModifyVolume procedures by the external-resizer + annModifyControllerSecretRefName = "volume.kubernetes.io/controller-modify-secret-name" + annModifyControllerSecretRefNamespace = "volume.kubernetes.io/controller-modify-secret-namespace" + snapshotNotBound = "snapshot %s not bound" pvcCloneFinalizer = "provisioner.storage.kubernetes.io/cloning-protection" @@ -205,6 +213,12 @@ var ( secretNameKey: prefixedNodeExpandSecretNameKey, secretNamespaceKey: prefixedNodeExpandSecretNamespaceKey, } + + controllerModifySecretParams = secretParamsMap{ + name: "ControllerModify", + secretNameKey: prefixedControllerModifySecretNameKey, + secretNamespaceKey: prefixedControllerModifySecretNamespaceKey, + } ) // ProvisionerCSITranslator contains the set of CSI Translation functionality @@ -540,7 +554,7 @@ func (p *csiProvisioner) getVolumeCapabilities( return volumeCaps, nil } -type deletionSecretParams struct { +type annotatedSecretParams struct { name string namespace string } @@ -550,7 +564,8 @@ type prepareProvisionResult struct { migratedVolume bool req *csi.CreateVolumeRequest csiPVSource *v1.CSIPersistentVolumeSource - provDeletionSecrets *deletionSecretParams + provDeletionSecrets *annotatedSecretParams + provModifySecrets *annotatedSecretParams } // prepareProvision does non-destructive parameter checking and preparations for provisioning a volume. @@ -739,6 +754,10 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist if err != nil { return nil, controller.ProvisioningNoChange, err } + controllerModifySecretRef, err := getSecretReference(controllerModifySecretParams, sc.Parameters, pvName, claim) + if err != nil { + return nil, controller.ProvisioningNoChange, err + } csiPVSource := &v1.CSIPersistentVolumeSource{ Driver: p.driverName, // VolumeHandle and VolumeAttributes will be added after provisioning. @@ -760,13 +779,21 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist req.Parameters[pvcNamespaceKey] = claim.GetNamespace() req.Parameters[pvNameKey] = pvName } - deletionAnnSecrets := new(deletionSecretParams) + deletionAnnSecrets := new(annotatedSecretParams) if provisionerSecretRef != nil { deletionAnnSecrets.name = provisionerSecretRef.Name deletionAnnSecrets.namespace = provisionerSecretRef.Namespace } + var modifyAnnSecrets *annotatedSecretParams + if controllerModifySecretRef != nil { + modifyAnnSecrets = &annotatedSecretParams{ + name: controllerModifySecretRef.Name, + namespace: controllerModifySecretRef.Namespace, + } + } + if vacName != "" { vac, err := p.client.StorageV1().VolumeAttributesClasses().Get(ctx, vacName, metav1.GetOptions{}) if err != nil { @@ -786,6 +813,7 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist req: &req, csiPVSource: csiPVSource, provDeletionSecrets: deletionAnnSecrets, + provModifySecrets: modifyAnnSecrets, }, controller.ProvisioningNoChange, nil } @@ -868,6 +896,7 @@ func (p *csiProvisioner) Provision(ctx context.Context, options controller.Provi klog.V(3).Infof("create volume rep: %+v", rep.Volume) } volumeAttributes := map[string]string{provisionerIDKey: p.identity} + maps.Copy(volumeAttributes, rep.Volume.VolumeContext) respCap := rep.GetVolume().GetCapacityBytes() @@ -943,6 +972,13 @@ func (p *csiProvisioner) Provision(ctx context.Context, options controller.Provi metav1.SetMetaDataAnnotation(&pv.ObjectMeta, annDeletionProvisionerSecretRefNamespace, "") } + // Set annModifyControllerSecretRefName and namespace in PV object when modify secrets are configured. + if result.provModifySecrets != nil { + klog.V(5).Infof("createVolumeOperation: set annotation [%s/%s] on pv [%s].", annModifyControllerSecretRefNamespace, annModifyControllerSecretRefName, pv.Name) + metav1.SetMetaDataAnnotation(&pv.ObjectMeta, annModifyControllerSecretRefName, result.provModifySecrets.name) + metav1.SetMetaDataAnnotation(&pv.ObjectMeta, annModifyControllerSecretRefNamespace, result.provModifySecrets.namespace) + } + if options.StorageClass.ReclaimPolicy != nil { pv.Spec.PersistentVolumeReclaimPolicy = *options.StorageClass.ReclaimPolicy } @@ -1030,6 +1066,8 @@ func removePrefixedParameters(param map[string]string) (map[string]string, error case prefixedDefaultSecretNamespaceKey: case prefixedNodeExpandSecretNameKey: case prefixedNodeExpandSecretNamespaceKey: + case prefixedControllerModifySecretNameKey: + case prefixedControllerModifySecretNamespaceKey: default: return map[string]string{}, fmt.Errorf("found unknown parameter key \"%s\" with reserved namespace %s", k, csiParameterPrefix) } diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 4ecaf7426b..8a9e309a88 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -237,6 +237,8 @@ func TestStripPrefixedCSIParams(t *testing.T) { prefixedDefaultSecretNamespaceKey: "csiBar", prefixedNodeExpandSecretNameKey: "csiBar", prefixedNodeExpandSecretNamespaceKey: "csiBar", + prefixedControllerModifySecretNameKey: "csiBar", + prefixedControllerModifySecretNamespaceKey: "csiBar", }, expectedParams: map[string]string{}, }, @@ -926,6 +928,8 @@ func getDefaultStorageClassSecretParameters() map[string]string { prefixedProvisionerSecretNamespaceKey: defaultSecretNsName, prefixedNodeExpandSecretNameKey: "nodeexpandsecret", prefixedNodeExpandSecretNamespaceKey: defaultSecretNsName, + prefixedControllerModifySecretNameKey: "ctrlmodifysecret", + prefixedControllerModifySecretNamespaceKey: defaultSecretNsName, } } @@ -1623,6 +1627,8 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) { expectedPVSpec: &pvSpec{ Name: "test-testi", Annotations: map[string]string{ + annModifyControllerSecretRefName: "ctrlmodifysecret", + annModifyControllerSecretRefNamespace: defaultSecretNsName, annDeletionProvisionerSecretRefName: "provisionersecret", annDeletionProvisionerSecretRefNamespace: defaultSecretNsName, }, @@ -1682,6 +1688,8 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) { expectedPVSpec: &pvSpec{ Name: "test-testi", Annotations: map[string]string{ + annModifyControllerSecretRefName: "default-secret", + annModifyControllerSecretRefNamespace: "default-ns", annDeletionProvisionerSecretRefName: "default-secret", annDeletionProvisionerSecretRefNamespace: "default-ns", }, @@ -1741,6 +1749,8 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) { expectedPVSpec: &pvSpec{ Name: "test-testi", Annotations: map[string]string{ + annModifyControllerSecretRefName: "my-pvc", + annModifyControllerSecretRefNamespace: "default-ns", annDeletionProvisionerSecretRefName: "my-pvc", annDeletionProvisionerSecretRefNamespace: "default-ns", },