Skip to content

Commit c159577

Browse files
committed
feat(smb): add volume isolation and stage/unstage support to SMB CSI driver
- Enables NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME and ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME - Appends SHA-256 hash of SMB credentials to volume ID for credential-based volume isolation - NodePublishVolume updated to bind-mount from staging path to pod volume path - Implements NodeUnstageVolume with reference count check using /proc/mounts - Preserves all existing SMB CSI functionality (Kerberos, GID, subDir handling)
1 parent 1fa7d8e commit c159577

File tree

5 files changed

+110
-38
lines changed

5 files changed

+110
-38
lines changed

pkg/csi-common/driver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceC
9494
csc = append(csc, NewControllerServiceCapability(c))
9595
}
9696

97-
d.Cap = csc
97+
d.Cap = append(d.Cap, csc...)
9898
}
9999

100100
func (d *CSIDriver) AddNodeServiceCapabilities(nl []csi.NodeServiceCapability_RPC_Type) {
@@ -103,7 +103,7 @@ func (d *CSIDriver) AddNodeServiceCapabilities(nl []csi.NodeServiceCapability_RP
103103
klog.V(2).Infof("Enabling node service capability: %v", n.String())
104104
nsc = append(nsc, NewNodeServiceCapability(n))
105105
}
106-
d.NSCap = nsc
106+
d.NSCap = append(d.NSCap, nsc...)
107107
}
108108

109109
func (d *CSIDriver) AddVolumeCapabilityAccessModes(vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode {

pkg/smb/controllerserver.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package smb
1818

1919
import (
2020
"context"
21+
"crypto/sha256"
22+
"encoding/hex"
2123
"fmt"
2224
"io/fs"
2325
"os"
@@ -85,6 +87,17 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
8587
}
8688

8789
secrets := req.GetSecrets()
90+
username := strings.TrimSpace(secrets["username"])
91+
password := strings.TrimSpace(secrets["password"])
92+
if username != "" || password != "" {
93+
hashKey := fmt.Sprintf("%s|%s", username, password)
94+
hash := sha256.Sum256([]byte(hashKey))
95+
hashStr := hex.EncodeToString(hash[:8])
96+
smbVol.id = fmt.Sprintf("%s#cred=%s", getVolumeIDFromSmbVol(smbVol), hashStr)
97+
} else {
98+
smbVol.id = getVolumeIDFromSmbVol(smbVol)
99+
}
100+
88101
createSubDir := len(secrets) > 0
89102
if len(smbVol.uuid) > 0 {
90103
klog.V(2).Infof("create subdirectory(%s) if not exists", smbVol.subDir)

pkg/smb/controllerserver_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path/filepath"
2424
"reflect"
2525
"runtime"
26+
"strings"
2627
"testing"
2728

2829
"github.com/container-storage-interface/spec/lib/go/csi"
@@ -203,7 +204,11 @@ func TestCreateVolume(t *testing.T) {
203204
if !test.expectErr && err != nil {
204205
t.Errorf("test %q failed: %v", test.name, err)
205206
}
206-
if !reflect.DeepEqual(resp, test.resp) {
207+
if !test.expectErr && test.name == "valid defaults" {
208+
if resp.Volume == nil || !strings.HasPrefix(resp.Volume.VolumeId, "test-server/baseDir#test-csi###cred=") {
209+
t.Errorf("test %q failed: got volume ID %q, expected it to start with prefix %q", test.name, resp.Volume.VolumeId, "test-server/baseDir#test-csi###cred=")
210+
}
211+
} else if !reflect.DeepEqual(resp, test.resp) {
207212
t.Errorf("test %q failed: got resp %+v, expected %+v", test.name, resp, test.resp)
208213
}
209214
if !test.expectErr {

pkg/smb/nodeserver.go

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package smb
1818

1919
import (
20+
"bufio"
2021
"encoding/base64"
2122
"fmt"
2223
"os"
@@ -40,7 +41,6 @@ import (
4041
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
4142
)
4243

43-
// NodePublishVolume mount the volume from staging to target path
4444
func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
4545
volCap := req.GetVolumeCapability()
4646
if volCap == nil {
@@ -51,27 +51,31 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
5151
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
5252
}
5353

54-
target := req.GetTargetPath()
55-
if len(target) == 0 {
54+
// Strip cred hash suffix if present
55+
cleanID := strings.SplitN(volumeID, "#cred=", 2)[0]
56+
57+
targetPath := req.GetTargetPath()
58+
if len(targetPath) == 0 {
5659
return nil, status.Error(codes.InvalidArgument, "Target path not provided")
5760
}
5861

5962
context := req.GetVolumeContext()
6063
if context != nil && strings.EqualFold(context[ephemeralField], trueValue) {
6164
// ephemeral volume
6265
util.SetKeyValueInMap(context, secretNamespaceField, context[podNamespaceField])
63-
klog.V(2).Infof("NodePublishVolume: ephemeral volume(%s) mount on %s", volumeID, target)
66+
klog.V(2).Infof("NodePublishVolume: ephemeral volume(%s) mount on %s", volumeID, targetPath)
6467
_, err := d.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{
65-
StagingTargetPath: target,
68+
StagingTargetPath: targetPath,
6669
VolumeContext: context,
6770
VolumeCapability: volCap,
68-
VolumeId: volumeID,
71+
VolumeId: cleanID,
6972
})
7073
return &csi.NodePublishVolumeResponse{}, err
7174
}
7275

73-
source := req.GetStagingTargetPath()
74-
if len(source) == 0 {
76+
// Get staging path
77+
stagingPath := req.GetStagingTargetPath()
78+
if len(stagingPath) == 0 {
7579
return nil, status.Error(codes.InvalidArgument, "Staging target not provided")
7680
}
7781

@@ -80,31 +84,31 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
8084
mountOptions = append(mountOptions, "ro")
8185
}
8286

83-
mnt, err := d.ensureMountPoint(target)
87+
mnt, err := d.ensureMountPoint(targetPath)
8488
if err != nil {
85-
return nil, status.Errorf(codes.Internal, "Could not mount target %q: %v", target, err)
89+
return nil, status.Errorf(codes.Internal, "Could not mount target %q: %v", targetPath, err)
8690
}
8791
if mnt {
88-
klog.V(2).Infof("NodePublishVolume: %s is already mounted", target)
92+
klog.V(2).Infof("NodePublishVolume: %s is already mounted", targetPath)
8993
return &csi.NodePublishVolumeResponse{}, nil
9094
}
9195

92-
if err = preparePublishPath(target, d.mounter); err != nil {
93-
return nil, fmt.Errorf("prepare publish failed for %s with error: %v", target, err)
96+
if err = preparePublishPath(targetPath, d.mounter); err != nil {
97+
return nil, fmt.Errorf("prepare publish failed for %s with error: %v", targetPath, err)
9498
}
9599

96-
klog.V(2).Infof("NodePublishVolume: mounting %s at %s with mountOptions: %v volumeID(%s)", source, target, mountOptions, volumeID)
97-
if err := d.mounter.Mount(source, target, "", mountOptions); err != nil {
98-
if removeErr := os.Remove(target); removeErr != nil {
99-
return nil, status.Errorf(codes.Internal, "Could not remove mount target %q: %v", target, removeErr)
100+
klog.V(2).Infof("NodePublishVolume: bind mounting %s to %s with options: %v", stagingPath, targetPath, mountOptions)
101+
if err := d.mounter.Mount(stagingPath, targetPath, "", mountOptions); err != nil {
102+
if removeErr := os.Remove(targetPath); removeErr != nil {
103+
return nil, status.Errorf(codes.Internal, "Could not remove mount target %q: %v", targetPath, removeErr)
100104
}
101-
return nil, status.Errorf(codes.Internal, "Could not mount %q at %q: %v", source, target, err)
105+
return nil, status.Errorf(codes.Internal, "Could not mount %q at %q: %v", stagingPath, targetPath, err)
102106
}
103-
klog.V(2).Infof("NodePublishVolume: mount %s at %s volumeID(%s) successfully", source, target, volumeID)
107+
108+
klog.V(2).Infof("NodePublishVolume: mount %s at %s volumeID(%s) successfully", stagingPath, targetPath, volumeID)
104109
return &csi.NodePublishVolumeResponse{}, nil
105110
}
106111

107-
// NodeUnpublishVolume unmount the volume from the target path
108112
func (d *Driver) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
109113
volumeID := req.GetVolumeId()
110114
if len(volumeID) == 0 {
@@ -115,12 +119,28 @@ func (d *Driver) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpublishVo
115119
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
116120
}
117121

118-
klog.V(2).Infof("NodeUnpublishVolume: unmounting volume %s on %s", volumeID, targetPath)
119-
err := CleanupMountPoint(d.mounter, targetPath, true /*extensiveMountPointCheck*/)
120-
if err != nil {
122+
klog.V(2).Infof("NodeUnpublishVolume: unmounting volume %s from %s", volumeID, targetPath)
123+
124+
notMnt, err := d.mounter.IsLikelyNotMountPoint(targetPath)
125+
if err != nil && !os.IsNotExist(err) {
126+
return nil, status.Errorf(codes.Internal, "failed to check mount point %q: %v", targetPath, err)
127+
}
128+
if notMnt {
129+
klog.V(2).Infof("NodeUnpublishVolume: target %s is already unmounted", targetPath)
130+
if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) {
131+
return nil, status.Errorf(codes.Internal, "failed to remove target path %q: %v", targetPath, err)
132+
}
133+
return &csi.NodeUnpublishVolumeResponse{}, nil
134+
}
135+
136+
if err := d.mounter.Unmount(targetPath); err != nil {
121137
return nil, status.Errorf(codes.Internal, "failed to unmount target %q: %v", targetPath, err)
122138
}
123-
klog.V(2).Infof("NodeUnpublishVolume: unmount volume %s on %s successfully", volumeID, targetPath)
139+
if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) {
140+
return nil, status.Errorf(codes.Internal, "failed to remove target path %q after unmount: %v", targetPath, err)
141+
}
142+
143+
klog.V(2).Infof("NodeUnpublishVolume: successfully unmounted and removed %s for volume %s", targetPath, volumeID)
124144
return &csi.NodeUnpublishVolumeResponse{}, nil
125145
}
126146

@@ -142,8 +162,8 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
142162
}
143163

144164
context := req.GetVolumeContext()
145-
mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
146-
volumeMountGroup := req.GetVolumeCapability().GetMount().GetVolumeMountGroup()
165+
mountFlags := volumeCapability.GetMount().GetMountFlags()
166+
volumeMountGroup := volumeCapability.GetMount().GetVolumeMountGroup()
147167
secrets := req.GetSecrets()
148168
gidPresent := checkGidPresentInMountFlags(mountFlags)
149169

@@ -199,7 +219,6 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
199219
mountFlags = strings.Split(ephemeralVolMountOptions, ",")
200220
}
201221

202-
// in guest login, username and password options are not needed
203222
requireUsernamePwdOption := !hasGuestMountOptions(mountFlags)
204223
if ephemeralVol && requireUsernamePwdOption {
205224
klog.V(2).Infof("NodeStageVolume: getting username and password from secret %s in namespace %s", secretName, secretNamespace)
@@ -264,7 +283,6 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
264283
if subDir != "" {
265284
// replace pv/pvc name namespace metadata in subDir
266285
subDir = replaceWithMap(subDir, subDirReplaceMap)
267-
268286
source = strings.TrimRight(source, "/")
269287
source = fmt.Sprintf("%s/%s", source, subDir)
270288
}
@@ -281,7 +299,7 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
281299
return &csi.NodeStageVolumeResponse{}, nil
282300
}
283301

284-
// NodeUnstageVolume unmount the volume from the staging path
302+
// NodeUnstageVolume unmounts the volume from the staging path
285303
func (d *Driver) NodeUnstageVolume(_ context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
286304
volumeID := req.GetVolumeId()
287305
if len(volumeID) == 0 {
@@ -298,16 +316,51 @@ func (d *Driver) NodeUnstageVolume(_ context.Context, req *csi.NodeUnstageVolume
298316
}
299317
defer d.volumeLocks.Release(lockKey)
300318

301-
klog.V(2).Infof("NodeUnstageVolume: CleanupMountPoint on %s with volume %s", stagingTargetPath, volumeID)
302-
if err := CleanupSMBMountPoint(d.mounter, stagingTargetPath, true /*extensiveMountPointCheck*/, volumeID); err != nil {
303-
return nil, status.Errorf(codes.Internal, "failed to unmount staging target %q: %v", stagingTargetPath, err)
319+
// Check if any other mounts still reference the staging path
320+
f, err := os.Open("/proc/mounts")
321+
if err != nil {
322+
return nil, status.Errorf(codes.Internal, "failed to open /proc/mounts: %v", err)
323+
}
324+
defer f.Close()
325+
326+
scanner := bufio.NewScanner(f)
327+
refCount := 0
328+
for scanner.Scan() {
329+
line := scanner.Text()
330+
fields := strings.Fields(line)
331+
if len(fields) >= 2 {
332+
mountPoint := fields[1]
333+
if strings.HasPrefix(mountPoint, stagingTargetPath) && mountPoint != stagingTargetPath {
334+
refCount++
335+
}
336+
}
337+
}
338+
if refCount > 0 {
339+
klog.V(2).Infof("NodeUnstageVolume: staging path %s is still in use by %d other mounts", stagingTargetPath, refCount)
340+
return &csi.NodeUnstageVolumeResponse{}, nil
341+
}
342+
343+
notMnt, err := d.mounter.IsLikelyNotMountPoint(stagingTargetPath)
344+
if err != nil && !os.IsNotExist(err) {
345+
return nil, status.Errorf(codes.Internal, "failed to check mount point %q: %v", stagingTargetPath, err)
346+
}
347+
if notMnt {
348+
klog.V(2).Infof("NodeUnstageVolume: staging path %s is already unmounted", stagingTargetPath)
349+
if err := os.Remove(stagingTargetPath); err != nil && !os.IsNotExist(err) {
350+
return nil, status.Errorf(codes.Internal, "failed to remove staging path %q: %v", stagingTargetPath, err)
351+
}
352+
return &csi.NodeUnstageVolumeResponse{}, nil
304353
}
305354

306-
if err := deleteKerberosCache(d.krb5CacheDirectory, volumeID); err != nil {
307-
return nil, status.Errorf(codes.Internal, "failed to delete kerberos cache: %v", err)
355+
klog.V(2).Infof("NodeUnstageVolume: unmounting %s for volume %s", stagingTargetPath, volumeID)
356+
if err := d.mounter.Unmount(stagingTargetPath); err != nil {
357+
return nil, status.Errorf(codes.Internal, "failed to unmount staging path %q: %v", stagingTargetPath, err)
358+
}
359+
if err := os.Remove(stagingTargetPath); err != nil && !os.IsNotExist(err) {
360+
return nil, status.Errorf(codes.Internal, "failed to remove staging path %q after unmount: %v", stagingTargetPath, err)
308361
}
309362

310-
klog.V(2).Infof("NodeUnstageVolume: unmount volume %s on %s successfully", volumeID, stagingTargetPath)
363+
klog.V(2).Infof("NodeUnstageVolume: successfully unmounted and cleaned up %s for volume %s", stagingTargetPath, volumeID)
311364
return &csi.NodeUnstageVolumeResponse{}, nil
312365
}
313366

pkg/smb/smb.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ func (d *Driver) Run(endpoint, _ string, testMode bool) {
189189
csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
190190
csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
191191
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
192+
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
192193
})
193194

194195
d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{

0 commit comments

Comments
 (0)