Kubernetes Secrets Store CSI Driver sync/rotate directory traversal
Description
Kubernetes Secrets Store CSI Driver versions v0.0.15 and v0.0.16 allow an attacker who can modify a SecretProviderClassPodStatus/Status resource the ability to write content to the host filesystem and sync file contents to Kubernetes Secrets. This includes paths under var/lib/kubelet/pods that contain other Kubernetes Secrets.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An attacker in Kubernetes with write access to a SecretProviderClassPodStatus/Status resource could write to the host filesystem and sync arbitrary data into Kubernetes Secrets.
Vulnerability
Overview
The Kubernetes Secrets Store CSI Driver, versions v0.0.15 and v0.0.16, contained an improper input validation vulnerability in the controller responsible for reconciling SecretProviderClassPodStatus (SPCPS) resources. [1] The software did not sufficiently verify that the TargetPath specified in a SPCPS resource matched the UID of the pod associated with that status object. This oversight allowed an attacker who could modify a SPCPS or its Status subresource to set an arbitrary TargetPath.
Exploitation
Vector
An attacker with permissions to update SecretProviderClassPodStatus or SecretProviderClassPodStatus/Status resources in a Kubernetes cluster could exploit this flaw. [1] By crafting a malicious TargetPath that pointed outside the intended pod-specific directory under /var/lib/kubelet/pods/, the attacker could cause the driver's controller to write secret material to any path on the host filesystem or to any path under /var/lib/kubelet/pods/ that contains other pods' Kubernetes Secrets. [1]
Impact
The primary impact is an escalation of privilege and confidentiality breach. An attacker who successfully exploits this vulnerability can write arbitrary content to the host filesystem or overwrite existing Kubernetes Secrets belonging to other pods. [1] This could lead to the compromise of sensitive data managed by the Secrets Store CSI Driver, as well as potentially affecting other pods or the node's own configuration.
Mitigation
The vulnerability was addressed in a commit that added validation ensuring the TargetPath in a SPCPS resource matches the UID of the corresponding pod. [2] [3] Users must upgrade the Secrets Store CSI Driver to a version beyond v0.0.16. The project is hosted on GitHub under the kubernetes-sigs organization and is a SIG Auth subproject. [4] As no workaround exists other than upgrading, applying the patched version is the recommended action.
- NVD - CVE-2020-8568
- fix: validate SPCPS targetPaths match Pod UIDs · kubernetes-sigs/secrets-store-csi-driver@c2cbb19
- fix: validate SPCPS targetPaths match Pod UIDs by tam7t · Pull Request #371 · kubernetes-sigs/secrets-store-csi-driver
- GitHub - kubernetes-sigs/secrets-store-csi-driver: Secrets Store CSI driver for Kubernetes secrets - Integrates secrets stores with Kubernetes via a CSI volume.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
sigs.k8s.io/secrets-store-csi-driverGo | >= 0.0.15, < 0.0.17 | 0.0.17 |
Affected products
2- Kubernetes/Kubernetes Secrets Store CSI Driverv5Range: Kubernetes Secrets Store CSI Driver v0.0.15
Patches
1c2cbb19e2eeffix: validate SPCPS targetPaths match Pod UIDs
13 files changed · +645 −133
controllers/secretproviderclasspodstatus_controller.go+27 −36 modified@@ -19,7 +19,6 @@ package controllers import ( "context" "fmt" - "regexp" "strings" "sync" "time" @@ -35,6 +34,7 @@ import ( "sigs.k8s.io/secrets-store-csi-driver/apis/v1alpha1" "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/scheme" "sigs.k8s.io/secrets-store-csi-driver/pkg/util/fileutil" + "sigs.k8s.io/secrets-store-csi-driver/pkg/util/k8sutil" "sigs.k8s.io/secrets-store-csi-driver/pkg/util/secretutil" ctrl "sigs.k8s.io/controller-runtime" @@ -227,18 +227,34 @@ func (r *SecretProviderClassPodStatusReconciler) Reconcile(req ctrl.Request) (ct return ctrl.Result{}, nil } - // podObjectReference is an object reference to the pod that spc pod status - // is created for. The object reference is created with minimal required fields - // name, namespace and UID. By doing this we can skip an additional client call - // to fetch the pod object - podObjectReference, err := getPodObjectReference(spcPodStatus) - if err != nil { - logger.Errorf("failed to get pod object reference, error: %+v", err) + // Obtain the full pod metadata. An object reference is needed for sending + // events and the UID is helpful for validating the SPCPS TargetPath. + pod := &v1.Pod{} + if err := r.reader.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: spcPodStatus.Status.PodName}, pod); err != nil { + logger.Errorf("failed to get pod %s/%s, err: %+v", req.Namespace, spcPodStatus.Status.PodName, err) + if apierrors.IsNotFound(err) { + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + return ctrl.Result{}, err + } + + // determine which pod volume this is associated with + podVol := k8sutil.SPCVolume(pod, spc.Name) + if podVol == nil { + return ctrl.Result{}, fmt.Errorf("failed to find secret provider class pod status volume for pod %s/%s", req.Namespace, spcPodStatus.Status.PodName) + } + + // validate TargetPath + if fileutil.GetPodUIDFromTargetPath(spcPodStatus.Status.TargetPath) != string(pod.UID) { + return ctrl.Result{}, fmt.Errorf("secret provider class pod status targetPath did not match pod UID for pod %s/%s", req.Namespace, spcPodStatus.Status.PodName) + } + if fileutil.GetVolumeNameFromTargetPath(spcPodStatus.Status.TargetPath) != podVol.Name { + return ctrl.Result{}, fmt.Errorf("secret provider class pod status volume name did not match pod Volume for pod %s/%s", req.Namespace, spcPodStatus.Status.PodName) } files, err := fileutil.GetMountedFiles(spcPodStatus.Status.TargetPath) if err != nil { - r.generateEvent(podObjectReference, corev1.EventTypeWarning, secretCreationFailedReason, fmt.Sprintf("failed to get mounted files, err: %+v", err)) + r.generateEvent(pod, corev1.EventTypeWarning, secretCreationFailedReason, fmt.Sprintf("failed to get mounted files, err: %+v", err)) logger.Errorf("failed to get mounted files, err: %+v", err) return ctrl.Result{RequeueAfter: 10 * time.Second}, err } @@ -265,7 +281,7 @@ func (r *SecretProviderClassPodStatusReconciler) Reconcile(req ctrl.Request) (ct datamap := make(map[string][]byte) if datamap, err = secretutil.GetSecretData(secretObj.Data, secretType, files); err != nil { - r.generateEvent(podObjectReference, corev1.EventTypeWarning, secretCreationFailedReason, fmt.Sprintf("failed to get data in spc %s/%s for secret %s, err: %+v", req.Namespace, spcName, secretName, err)) + r.generateEvent(pod, corev1.EventTypeWarning, secretCreationFailedReason, fmt.Sprintf("failed to get data in spc %s/%s for secret %s, err: %+v", req.Namespace, spcName, secretName, err)) log.Errorf("failed to get data in spc %s/%s for secret %s, err: %+v", req.Namespace, spcName, secretName, err) errs = append(errs, fmt.Errorf("failed to get data in spc %s/%s for secret %s, err: %+v", req.Namespace, spcName, secretName, err)) continue @@ -297,7 +313,7 @@ func (r *SecretProviderClassPodStatusReconciler) Reconcile(req ctrl.Request) (ct Factor: 1.0, Jitter: 0.1, }, f); err != nil { - r.generateEvent(podObjectReference, corev1.EventTypeWarning, secretCreationFailedReason, err.Error()) + r.generateEvent(pod, corev1.EventTypeWarning, secretCreationFailedReason, err.Error()) return ctrl.Result{RequeueAfter: 5 * time.Second}, err } } @@ -401,31 +417,6 @@ func (r *SecretProviderClassPodStatusReconciler) secretExists(ctx context.Contex return false, err } -// getPodObjectReference returns a v1.ObjectReference for the pod object -func getPodObjectReference(spcPodStatus v1alpha1.SecretProviderClassPodStatus) (*v1.ObjectReference, error) { - podName := spcPodStatus.Status.PodName - podNamespace := spcPodStatus.Namespace - podUID := getPodUIDFromTargetPath(spcPodStatus.Status.TargetPath) - if podUID == "" { - return nil, fmt.Errorf("failed to get pod UID from target path") - } - return &v1.ObjectReference{ - Name: podName, - Namespace: podNamespace, - UID: types.UID(podUID), - }, nil -} - -// getPodUIDFromTargetPath returns podUID from targetPath -func getPodUIDFromTargetPath(targetPath string) string { - re := regexp.MustCompile(`[\\|\/]+pods[\\|\/]+(.+?)[\\|\/]+volumes`) - match := re.FindStringSubmatch(targetPath) - if len(match) < 2 { - return "" - } - return match[1] -} - // generateEvent generates an event func (r *SecretProviderClassPodStatusReconciler) generateEvent(obj runtime.Object, eventType, reason, message string) { if obj != nil {
controllers/secretproviderclasspodstatus_controller_test.go+0 −43 modified@@ -172,49 +172,6 @@ func TestCreateK8sSecret(t *testing.T) { g.Expect(secret.Name).To(Equal("my-secret2")) } -func TestGetPodUIDFromTargetPath(t *testing.T) { - g := NewWithT(t) - - cases := []struct { - targetPath string - expectedPodUID string - }{ - { - targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/", - expectedPodUID: "7e7686a1-56c4-4c67-a6fd-4656ac484f0a", - }, - { - targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes`, - expectedPodUID: "d4fd876f-bdb3-11e9-a369-0a5d188d99c0", - }, - { - targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes`, - expectedPodUID: "d4fd876f-bdb3-11e9-a369-0a5d188d9934", - }, - { - targetPath: "/var/lib/", - expectedPodUID: "", - }, - { - targetPath: "/var/lib/kubelet/pods", - expectedPodUID: "", - }, - { - targetPath: "/opt/new/var/lib/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", - expectedPodUID: "456457fc-d980-4191-b5eb-daf70c4ff7c1", - }, - { - targetPath: "data/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", - expectedPodUID: "456457fc-d980-4191-b5eb-daf70c4ff7c1", - }, - } - - for _, tc := range cases { - actualPodUID := getPodUIDFromTargetPath(tc.targetPath) - g.Expect(actualPodUID).To(Equal(tc.expectedPodUID)) - } -} - func TestGenerateEvent(t *testing.T) { g := NewWithT(t)
go.mod+1 −0 modified@@ -6,6 +6,7 @@ require ( github.com/blang/semver v3.5.0+incompatible github.com/container-storage-interface/spec v1.0.0 github.com/golang/protobuf v1.4.2 + github.com/google/go-cmp v0.5.0 github.com/kubernetes-csi/csi-lib-utils v0.6.1 github.com/kubernetes-csi/csi-test v1.1.0 github.com/onsi/gomega v1.8.1
pkg/errors/errors.go+5 −0 modified@@ -40,4 +40,9 @@ const ( // NodePublishSecretRefNotFound error // #nosec G101 (Ref: https://github.com/securego/gosec/issues/295) NodePublishSecretRefNotFound = "NodePublishSecretRefNotFound" + // UnexpectedTargetPath error + // Indicated SecretProviderClassPodStatus status.targetPath is an invalid value. + UnexpectedTargetPath = "UnexpectedTargetPath" + // PodVolumeNotFound error + PodVolumeNotFound = "PodVolumeNotFound" )
pkg/rotation/reconciler.go+19 −14 modified@@ -30,6 +30,7 @@ import ( internalerrors "sigs.k8s.io/secrets-store-csi-driver/pkg/errors" "sigs.k8s.io/secrets-store-csi-driver/pkg/util/fileutil" + "sigs.k8s.io/secrets-store-csi-driver/pkg/util/k8sutil" "sigs.k8s.io/secrets-store-csi-driver/pkg/util/secretutil" "k8s.io/client-go/tools/cache" @@ -196,6 +197,23 @@ func (r *Reconciler) reconcile(ctx context.Context, spcps *v1alpha1.SecretProvid return fmt.Errorf("failed to get pod %s/%s, err: %+v", podNamespace, podName, err) } + // determine which pod volume this is associated with + podVol := k8sutil.SPCVolume(pod, spc.Name) + if podVol == nil { + errorReason = internalerrors.PodVolumeNotFound + return fmt.Errorf("could not find secret provider class pod status volume for pod %s/%s", podNamespace, podName) + } + + // validate TargetPath + if fileutil.GetPodUIDFromTargetPath(spcps.Status.TargetPath) != string(pod.UID) { + errorReason = internalerrors.UnexpectedTargetPath + return fmt.Errorf("secret provider class pod status targetPath did not match pod UID for pod %s/%s", podNamespace, podName) + } + if fileutil.GetVolumeNameFromTargetPath(spcps.Status.TargetPath) != podVol.Name { + errorReason = internalerrors.UnexpectedTargetPath + return fmt.Errorf("secret provider class pod status volume name did not match pod Volume for pod %s/%s", podNamespace, podName) + } + parameters := make(map[string]string) if spc.Spec.Parameters != nil { parameters = spc.Spec.Parameters @@ -217,20 +235,7 @@ func (r *Reconciler) reconcile(ctx context.Context, spcps *v1alpha1.SecretProvid // check if the volume pertaining to the current spc is using nodePublishSecretRef for // accessing external secrets store - var nodePublishSecretRef *v1.LocalObjectReference - for _, vol := range pod.Spec.Volumes { - if vol.CSI == nil { - continue - } - if vol.CSI.Driver != "secrets-store.csi.k8s.io" { - continue - } - if vol.CSI.VolumeAttributes["secretProviderClass"] != spc.Name { - continue - } - nodePublishSecretRef = vol.CSI.NodePublishSecretRef - break - } + nodePublishSecretRef := podVol.CSI.NodePublishSecretRef var secretsJSON []byte nodePublishSecretData := make(map[string]string)
pkg/rotation/reconciler_test.go+162 −2 modified@@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "testing" "time" @@ -173,6 +174,7 @@ func TestReconcileError(t *testing.T) { Status: v1alpha1.SecretProviderClassPodStatusStatus{ SecretProviderClassName: "spc1", PodName: "pod1", + TargetPath: getTestTargetPath(t, "foo", "csi-volume"), }, }, secretProviderClassToAdd: &v1alpha1.SecretProviderClass{ @@ -198,6 +200,7 @@ func TestReconcileError(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "default", + UID: types.UID("foo"), }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ @@ -221,6 +224,152 @@ func TestReconcileError(t *testing.T) { expectedErr: true, expectedErrorEvents: true, }, + { + name: "failed to validate targetpath UID", + rotationPollInterval: 60 * time.Second, + secretProviderClassPodStatusToProcess: &v1alpha1.SecretProviderClassPodStatus{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1-default-spc1", + Namespace: "default", + Labels: map[string]string{v1alpha1.InternalNodeLabel: "nodeName"}, + }, + Status: v1alpha1.SecretProviderClassPodStatusStatus{ + SecretProviderClassName: "spc1", + PodName: "pod1", + TargetPath: getTestTargetPath(t, "bad-uid", "csi-volume"), + Objects: []v1alpha1.SecretProviderClassObject{ + { + ID: "secret/object1", + Version: "v1", + }, + }, + }, + }, + secretProviderClassToAdd: &v1alpha1.SecretProviderClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spc1", + Namespace: "default", + }, + Spec: v1alpha1.SecretProviderClassSpec{ + SecretObjects: []*v1alpha1.SecretObject{ + { + Data: []*v1alpha1.SecretObjectData{ + { + ObjectName: "object1", + Key: "foo", + }, + }, + }, + }, + Provider: "provider1", + }, + }, + podToAdd: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + UID: types.UID("foo"), + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + }, + }, + socketPath: getTempTestDir(t), + secretToAdd: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "object1", + Namespace: "default", + ResourceVersion: "rv1", + }, + Data: map[string][]byte{"foo": []byte("olddata")}, + }, + expectedObjectVersions: map[string]string{"secret/object1": "v2"}, + expectedErr: true, + expectedErrorEvents: false, + }, + { + name: "failed to validate targetpath volume name", + rotationPollInterval: 60 * time.Second, + secretProviderClassPodStatusToProcess: &v1alpha1.SecretProviderClassPodStatus{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1-default-spc1", + Namespace: "default", + Labels: map[string]string{v1alpha1.InternalNodeLabel: "nodeName"}, + }, + Status: v1alpha1.SecretProviderClassPodStatusStatus{ + SecretProviderClassName: "spc1", + PodName: "pod1", + TargetPath: getTestTargetPath(t, "foo", "bad-volume-name"), + Objects: []v1alpha1.SecretProviderClassObject{ + { + ID: "secret/object1", + Version: "v1", + }, + }, + }, + }, + secretProviderClassToAdd: &v1alpha1.SecretProviderClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spc1", + Namespace: "default", + }, + Spec: v1alpha1.SecretProviderClassSpec{ + SecretObjects: []*v1alpha1.SecretObject{ + { + Data: []*v1alpha1.SecretObjectData{ + { + ObjectName: "object1", + Key: "foo", + }, + }, + }, + }, + Provider: "provider1", + }, + }, + podToAdd: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + UID: types.UID("foo"), + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + }, + }, + socketPath: getTempTestDir(t), + secretToAdd: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "object1", + Namespace: "default", + ResourceVersion: "rv1", + }, + Data: map[string][]byte{"foo": []byte("olddata")}, + }, + expectedObjectVersions: map[string]string{"secret/object1": "v2"}, + expectedErr: true, + expectedErrorEvents: false, + }, { name: "failed to create provider client", rotationPollInterval: 60 * time.Second, @@ -233,7 +382,7 @@ func TestReconcileError(t *testing.T) { Status: v1alpha1.SecretProviderClassPodStatusStatus{ SecretProviderClassName: "spc1", PodName: "pod1", - TargetPath: getTempTestDir(t), + TargetPath: getTestTargetPath(t, "foo", "csi-volume"), }, }, secretProviderClassToAdd: &v1alpha1.SecretProviderClass{ @@ -259,6 +408,7 @@ func TestReconcileError(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "default", + UID: types.UID("foo"), }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ @@ -336,7 +486,7 @@ func TestReconcileNoError(t *testing.T) { Status: v1alpha1.SecretProviderClassPodStatusStatus{ SecretProviderClassName: "spc1", PodName: "pod1", - TargetPath: getTempTestDir(t), + TargetPath: getTestTargetPath(t, "foo", "csi-volume"), Objects: []v1alpha1.SecretProviderClassObject{ { ID: "secret/object1", @@ -370,6 +520,7 @@ func TestReconcileNoError(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "default", + UID: types.UID("foo"), }, Spec: v1.PodSpec{ Volumes: []v1.Volume{ @@ -571,3 +722,12 @@ func getTempTestDir(t *testing.T) string { } return tmpDir } + +func getTestTargetPath(t *testing.T, uid, vol string) string { + dir := getTempTestDir(t) + path := filepath.Join(dir, "pods", uid, "volumes", "kubernetes.io~csi", vol, "mount") + if err := os.MkdirAll(path, 0755); err != nil { + t.Fatalf("expected err to be nil, got: %+v", err) + } + return path +}
pkg/secrets-store/nodeserver.go+4 −2 modified@@ -31,6 +31,7 @@ import ( csicommon "sigs.k8s.io/secrets-store-csi-driver/pkg/csi-common" internalerrors "sigs.k8s.io/secrets-store-csi-driver/pkg/errors" + "sigs.k8s.io/secrets-store-csi-driver/pkg/util/fileutil" "sigs.k8s.io/secrets-store-csi-driver/pkg/version" log "github.com/sirupsen/logrus" @@ -223,13 +224,14 @@ func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu } targetPath := req.GetTargetPath() volumeID := req.GetVolumeId() - files, err := getMountedFiles(targetPath) + // Assume no mounted files if GetMountedFiles fails. + files, _ := fileutil.GetMountedFiles(targetPath) if isMockTargetPath(targetPath) { return &csi.NodeUnpublishVolumeResponse{}, nil } - podUID = getPodUIDFromTargetPath(targetPath) + podUID = fileutil.GetPodUIDFromTargetPath(targetPath) if len(podUID) == 0 { return nil, status.Error(codes.InvalidArgument, "Cannot get podUID from Target path") }
pkg/secrets-store/nodeserver_test.go+1 −1 modified@@ -477,7 +477,7 @@ func TestNodeUnpublishVolume(t *testing.T) { name: "Success for a mounted volume with a retry", nodeUnpublishVolReq: csi.NodeUnpublishVolumeRequest{ VolumeId: "testvolid1", - TargetPath: getTestTargetPath(`\\pods\fakePod\\volumes`, t), + TargetPath: getTestTargetPath(`*\\pods\\fakePod\\volumes\\kubernetes.io~csi\\myvol\\mount`, t), }, mountPoints: []mount.MountPoint{}, shouldRetryUnmount: true,
pkg/secrets-store/utils.go+0 −35 modified@@ -20,14 +20,11 @@ import ( "fmt" "io/ioutil" "os" - "regexp" "runtime" "strings" log "github.com/sirupsen/logrus" "golang.org/x/net/context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,38 +50,6 @@ func normalizeWindowsPath(path string) string { return normalizedPath } -// getMountedFiles returns all the mounted files names -func getMountedFiles(targetPath string) ([]string, error) { - var paths []string - // loop thru all the mounted files - files, err := ioutil.ReadDir(targetPath) - if err != nil { - log.Errorf("failed to list all files in target path %s, err: %v", targetPath, err) - return nil, - status.Error(codes.Internal, err.Error()) - } - sep := "/" - if strings.HasPrefix(targetPath, "c:\\") { - sep = "\\" - } else if strings.HasPrefix(targetPath, `c:\`) { - sep = `\` - } - for _, file := range files { - paths = append(paths, targetPath+sep+file.Name()) - } - return paths, nil -} - -// getPodUIDFromTargetPath returns podUID from targetPath -func getPodUIDFromTargetPath(targetPath string) string { - re := regexp.MustCompile(`[\\|\/]+pods[\\|\/]+(.+?)[\\|\/]+volumes`) - match := re.FindStringSubmatch(targetPath) - if len(match) < 2 { - return "" - } - return match[1] -} - // ensureMountPoint ensures mount point is valid func (ns *nodeServer) ensureMountPoint(target string) (bool, error) { notMnt, err := ns.mounter.IsLikelyNotMountPoint(target)
pkg/util/fileutil/filesystem.go+23 −0 modified@@ -19,9 +19,14 @@ package fileutil import ( "fmt" "io/ioutil" + "regexp" "strings" ) +var ( + targetPathRe = regexp.MustCompile(`[\\|\/]+pods[\\|\/]+(.+?)[\\|\/]+volumes[\\|\/]+kubernetes.io~csi[\\|\/]+(.+?)[\\|\/]+mount$`) +) + // GetMountedFiles returns all the mounted files names with filepath base as key func GetMountedFiles(targetPath string) (map[string]string, error) { paths := make(map[string]string) @@ -42,3 +47,21 @@ func GetMountedFiles(targetPath string) (map[string]string, error) { } return paths, nil } + +// GetPodUIDFromTargetPath returns podUID from targetPath +func GetPodUIDFromTargetPath(targetPath string) string { + match := targetPathRe.FindStringSubmatch(targetPath) + if len(match) < 2 { + return "" + } + return match[1] +} + +// GetVolumeNameFromTargetPath returns volumeName from targetPath +func GetVolumeNameFromTargetPath(targetPath string) string { + match := targetPathRe.FindStringSubmatch(targetPath) + if len(match) < 2 { + return "" + } + return match[2] +}
pkg/util/fileutil/filesystem_test.go+194 −0 modified@@ -55,3 +55,197 @@ func TestGetMountedFiles(t *testing.T) { }) } } + +func TestGetPodUIDFromTargetPath(t *testing.T) { + cases := []struct { + targetPath string + want string + }{ + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~csi", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~pv/pvvol/mount", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "7e7686a1-56c4-4c67-a6fd-4656ac484f0a", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes`, + want: "", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~csi`, + want: "", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~pv\pvvol\mount`, + want: "", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~csi\secrets-store-inline\mount`, + want: "d4fd876f-bdb3-11e9-a369-0a5d188d99c0", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes`, + want: "", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~csi`, + want: "", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~pv\\pvvol\\mount`, + want: "", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount`, + want: "d4fd876f-bdb3-11e9-a369-0a5d188d9934", + }, + { + targetPath: "/var/lib/", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods", + want: "", + }, + { + targetPath: "/opt/new/var/lib/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "456457fc-d980-4191-b5eb-daf70c4ff7c1", + }, + { + targetPath: "data/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "456457fc-d980-4191-b5eb-daf70c4ff7c1", + }, + { + targetPath: "data/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~pv/secrets-store-inline/mount", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "7e7686a1-56c4-4c67-a6fd-4656ac484f0a", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~csi\secrets-store-inline\mount`, + want: "d4fd876f-bdb3-11e9-a369-0a5d188d99c0", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount`, + want: "d4fd876f-bdb3-11e9-a369-0a5d188d9934", + }, + { + targetPath: "/opt/new/var/lib/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "456457fc-d980-4191-b5eb-daf70c4ff7c1", + }, + { + targetPath: "data/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "456457fc-d980-4191-b5eb-daf70c4ff7c1", + }, + { + targetPath: "/var/lib/kubelet/pods/64f9ffb2-409e-4c58-9ea8-2a7d21050ece/volumes/kubernetes.io~secret/server-token-npdwt", + want: "", + }, + { + targetPath: `\\pods\\fakePod\\volumes\\kubernetes.io~csi\\myvol\\mount`, + want: "fakePod", + }, + } + + for _, tc := range cases { + got := GetPodUIDFromTargetPath(tc.targetPath) + if got != tc.want { + t.Errorf("GetPodUIDFromTargetPath(%v) = %v, want %v", tc.targetPath, got, tc.want) + } + } +} + +func TestGetVolumeNameFromTargetPath(t *testing.T) { + cases := []struct { + targetPath string + want string + }{ + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~csi", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~pv/pvvol/mount", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods/7e7686a1-56c4-4c67-a6fd-4656ac484f0a/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "secrets-store-inline", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes`, + want: "", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~csi`, + want: "", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~pv\pvvol\mount`, + want: "", + }, + { + targetPath: `c:\var\lib\kubelet\pods\d4fd876f-bdb3-11e9-a369-0a5d188d99c0\volumes\kubernetes.io~csi\secrets-store-inline\mount`, + want: "secrets-store-inline", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes`, + want: "", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~csi`, + want: "", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~pv\\pvvol\\mount`, + want: "", + }, + { + targetPath: `c:\\var\\lib\\kubelet\\pods\\d4fd876f-bdb3-11e9-a369-0a5d188d9934\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount`, + want: "secrets-store-inline", + }, + { + targetPath: "/var/lib/", + want: "", + }, + { + targetPath: "/var/lib/kubelet/pods", + want: "", + }, + { + targetPath: "/opt/new/var/lib/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "secrets-store-inline", + }, + { + targetPath: "data/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~csi/secrets-store-inline/mount", + want: "secrets-store-inline", + }, + { + targetPath: "data/kubelet/pods/456457fc-d980-4191-b5eb-daf70c4ff7c1/volumes/kubernetes.io~pv/secrets-store-inline/mount", + want: "", + }, + } + + for _, tc := range cases { + got := GetVolumeNameFromTargetPath(tc.targetPath) + if got != tc.want { + t.Errorf("GetVolumeNameFromTargetPath(%v) = %v, want %v", tc.targetPath, got, tc.want) + } + } +}
pkg/util/k8sutil/volume.go+41 −0 added@@ -0,0 +1,41 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package k8sutil holds Secrets CSI Driver utilities for dealing with k8s +// types. +package k8sutil + +import ( + v1 "k8s.io/api/core/v1" +) + +// SPCVolume finds the Secret Provider Class volume from a Pod, or returns nil +// if a volume could not be found. +func SPCVolume(pod *v1.Pod, spcName string) *v1.Volume { + for i, vol := range pod.Spec.Volumes { + if vol.CSI == nil { + continue + } + if vol.CSI.Driver != "secrets-store.csi.k8s.io" { + continue + } + if vol.CSI.VolumeAttributes["secretProviderClass"] != spcName { + continue + } + return &pod.Spec.Volumes[i] + } + return nil +}
pkg/util/k8sutil/volume_test.go+168 −0 added@@ -0,0 +1,168 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package k8sutil + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" +) + +func TestSPCVolume(t *testing.T) { + tests := []struct { + name string + pod *v1.Pod + spcName string + want *v1.Volume + }{ + { + name: "No Volume", + pod: &v1.Pod{}, + spcName: "foo", + want: nil, + }, + { + name: "No CSI Volume", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "non-csi-volume", + VolumeSource: v1.VolumeSource{}, + }, + }, + }, + }, + spcName: "foo", + want: nil, + }, + { + name: "CSI volume but wrong driver", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "example-driver.k8s.io", + }, + }, + }, + }, + }, + }, + spcName: "foo", + want: nil, + }, + { + name: "Wrong Volume", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + }, + }, + spcName: "foo", + want: nil, + }, + { + name: "Correct Volume", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + }, + }, + spcName: "spc1", + want: &v1.Volume{ + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + { + name: "Multiple Volumes", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "csi-volume-0", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc0"}, + }, + }, + }, + { + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + }, + }, + spcName: "spc1", + want: &v1.Volume{ + Name: "csi-volume", + VolumeSource: v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + VolumeAttributes: map[string]string{"secretProviderClass": "spc1"}, + }, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := SPCVolume(tc.pod, tc.spcName) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("SPCVolume() mismatch (-want +got):\n%s", diff) + } + }) + } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-5cgx-vhfp-6cf9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-8568ghsaADVISORY
- github.com/kubernetes-sigs/secrets-store-csi-driver/commit/c2cbb19e2eef16638fa0523383788a4bc22231fdghsaWEB
- github.com/kubernetes-sigs/secrets-store-csi-driver/issues/378ghsax_refsource_MISCWEB
- github.com/kubernetes-sigs/secrets-store-csi-driver/pull/371ghsaWEB
- groups.google.com/g/kubernetes-secrets-store-csi-driver/c/Cb9cvymTzl4ghsax_refsource_MISCWEB
- pkg.go.dev/vuln/GO-2022-0629ghsaWEB
News mentions
0No linked articles in our index yet.