Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/csi-common/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceC
csc = append(csc, NewControllerServiceCapability(c))
}

d.Cap = csc
d.Cap = append(d.Cap, csc...)
}

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

func (d *CSIDriver) AddVolumeCapabilityAccessModes(vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode {
Expand Down
15 changes: 15 additions & 0 deletions pkg/smb/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package smb

import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"io/fs"
"os"
Expand Down Expand Up @@ -85,6 +87,19 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
}

secrets := req.GetSecrets()
username := strings.TrimSpace(secrets["username"])
password := strings.TrimSpace(secrets["password"])
if username != "" || password != "" {
hashKey := fmt.Sprintf("%s|%s", username, password)
hash := sha256.Sum256([]byte(hashKey))
// Use first 16 bytes (128 bits) for better collision resistance
// Base64 encoding is more compact than hex (22 chars vs 32 chars)
hashStr := base64.URLEncoding.EncodeToString(hash[:16])
smbVol.id = fmt.Sprintf("%s#cred=%s", getVolumeIDFromSmbVol(smbVol), hashStr)
} else {
smbVol.id = getVolumeIDFromSmbVol(smbVol)
}

createSubDir := len(secrets) > 0
if len(smbVol.uuid) > 0 {
klog.V(2).Infof("create subdirectory(%s) if not exists", smbVol.subDir)
Expand Down
7 changes: 6 additions & 1 deletion pkg/smb/controllerserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"

"github.com/container-storage-interface/spec/lib/go/csi"
Expand Down Expand Up @@ -203,7 +204,11 @@ func TestCreateVolume(t *testing.T) {
if !test.expectErr && err != nil {
t.Errorf("test %q failed: %v", test.name, err)
}
if !reflect.DeepEqual(resp, test.resp) {
if !test.expectErr && test.name == "valid defaults" {
if resp.Volume == nil || !strings.HasPrefix(resp.Volume.VolumeId, "test-server/baseDir#test-csi###cred=") {
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=")
}
} else if !reflect.DeepEqual(resp, test.resp) {
t.Errorf("test %q failed: got resp %+v, expected %+v", test.name, resp, test.resp)
}
if !test.expectErr {
Expand Down
163 changes: 82 additions & 81 deletions pkg/smb/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import (
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
)

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

target := req.GetTargetPath()
if len(target) == 0 {
// Strip cred hash suffix if present
cleanID := strings.SplitN(volumeID, "#cred=", 2)[0]

targetPath := req.GetTargetPath()
if len(targetPath) == 0 {
return nil, status.Error(codes.InvalidArgument, "Target path not provided")
}

context := req.GetVolumeContext()
if context != nil && strings.EqualFold(context[ephemeralField], trueValue) {
// ephemeral volume
util.SetKeyValueInMap(context, secretNamespaceField, context[podNamespaceField])
klog.V(2).Infof("NodePublishVolume: ephemeral volume(%s) mount on %s", volumeID, target)
klog.V(2).Infof("NodePublishVolume: ephemeral volume(%s) mount on %s", volumeID, targetPath)
_, err := d.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{
StagingTargetPath: target,
StagingTargetPath: targetPath,
VolumeContext: context,
VolumeCapability: volCap,
VolumeId: volumeID,
VolumeId: cleanID,
})
return &csi.NodePublishVolumeResponse{}, err
}

source := req.GetStagingTargetPath()
if len(source) == 0 {
// Get staging path
stagingPath := req.GetStagingTargetPath()
if len(stagingPath) == 0 {
return nil, status.Error(codes.InvalidArgument, "Staging target not provided")
}

Expand All @@ -80,31 +83,31 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
mountOptions = append(mountOptions, "ro")
}

mnt, err := d.ensureMountPoint(target)
mnt, err := d.ensureMountPoint(targetPath)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not mount target %q: %v", target, err)
return nil, status.Errorf(codes.Internal, "Could not mount target %q: %v", targetPath, err)
}
if mnt {
klog.V(2).Infof("NodePublishVolume: %s is already mounted", target)
klog.V(2).Infof("NodePublishVolume: %s is already mounted", targetPath)
return &csi.NodePublishVolumeResponse{}, nil
}

if err = preparePublishPath(target, d.mounter); err != nil {
return nil, fmt.Errorf("prepare publish failed for %s with error: %v", target, err)
if err = preparePublishPath(targetPath, d.mounter); err != nil {
return nil, fmt.Errorf("prepare publish failed for %s with error: %v", targetPath, err)
}

klog.V(2).Infof("NodePublishVolume: mounting %s at %s with mountOptions: %v volumeID(%s)", source, target, mountOptions, volumeID)
if err := d.mounter.Mount(source, target, "", mountOptions); err != nil {
if removeErr := os.Remove(target); removeErr != nil {
return nil, status.Errorf(codes.Internal, "Could not remove mount target %q: %v", target, removeErr)
klog.V(2).Infof("NodePublishVolume: bind mounting %s to %s with options: %v", stagingPath, targetPath, mountOptions)
if err := d.mounter.Mount(stagingPath, targetPath, "", mountOptions); err != nil {
if removeErr := os.Remove(targetPath); removeErr != nil {
return nil, status.Errorf(codes.Internal, "Could not remove mount target %q: %v", targetPath, removeErr)
}
return nil, status.Errorf(codes.Internal, "Could not mount %q at %q: %v", source, target, err)
return nil, status.Errorf(codes.Internal, "Could not mount %q at %q: %v", stagingPath, targetPath, err)
}
klog.V(2).Infof("NodePublishVolume: mount %s at %s volumeID(%s) successfully", source, target, volumeID)

klog.V(2).Infof("NodePublishVolume: mount %s at %s volumeID(%s) successfully", stagingPath, targetPath, volumeID)
return &csi.NodePublishVolumeResponse{}, nil
}

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

klog.V(2).Infof("NodeUnpublishVolume: unmounting volume %s on %s", volumeID, targetPath)
err := CleanupMountPoint(d.mounter, targetPath, true /*extensiveMountPointCheck*/)
if err != nil {
klog.V(2).Infof("NodeUnpublishVolume: unmounting volume %s from %s", volumeID, targetPath)

notMnt, err := d.mounter.IsLikelyNotMountPoint(targetPath)
if err != nil && !os.IsNotExist(err) {
return nil, status.Errorf(codes.Internal, "failed to check mount point %q: %v", targetPath, err)
}
if notMnt {
klog.V(2).Infof("NodeUnpublishVolume: target %s is already unmounted", targetPath)
if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) {
return nil, status.Errorf(codes.Internal, "failed to remove target path %q: %v", targetPath, err)
}
return &csi.NodeUnpublishVolumeResponse{}, nil
}

if err := d.mounter.Unmount(targetPath); err != nil {
return nil, status.Errorf(codes.Internal, "failed to unmount target %q: %v", targetPath, err)
}
klog.V(2).Infof("NodeUnpublishVolume: unmount volume %s on %s successfully", volumeID, targetPath)
if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) {
return nil, status.Errorf(codes.Internal, "failed to remove target path %q after unmount: %v", targetPath, err)
}

klog.V(2).Infof("NodeUnpublishVolume: successfully unmounted and removed %s for volume %s", targetPath, volumeID)
return &csi.NodeUnpublishVolumeResponse{}, nil
}

Expand Down Expand Up @@ -149,8 +168,8 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
}

context := req.GetVolumeContext()
mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
volumeMountGroup := req.GetVolumeCapability().GetMount().GetVolumeMountGroup()
mountFlags := volumeCapability.GetMount().GetMountFlags()
volumeMountGroup := volumeCapability.GetMount().GetVolumeMountGroup()
secrets := req.GetSecrets()
gidPresent := checkGidPresentInMountFlags(mountFlags)

Expand Down Expand Up @@ -206,7 +225,6 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
mountFlags = strings.Split(ephemeralVolMountOptions, ",")
}

// in guest login, username and password options are not needed
requireUsernamePwdOption := !hasGuestMountOptions(mountFlags)
if ephemeralVol && requireUsernamePwdOption {
klog.V(2).Infof("NodeStageVolume: getting username and password from secret %s in namespace %s", secretName, secretNamespace)
Expand Down Expand Up @@ -275,7 +293,6 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
if subDir != "" {
// replace pv/pvc name namespace metadata in subDir
subDir = replaceWithMap(subDir, subDirReplaceMap)

source = strings.TrimRight(source, "/")
source = fmt.Sprintf("%s/%s", source, subDir)
}
Expand All @@ -292,7 +309,7 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
return &csi.NodeStageVolumeResponse{}, nil
}

// NodeUnstageVolume unmount the volume from the staging path
// NodeUnstageVolume unmounts the volume from the staging path
func (d *Driver) NodeUnstageVolume(_ context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
volumeID := req.GetVolumeId()
if len(volumeID) == 0 {
Expand All @@ -309,16 +326,46 @@ func (d *Driver) NodeUnstageVolume(_ context.Context, req *csi.NodeUnstageVolume
}
defer d.volumeLocks.Release(lockKey)

klog.V(2).Infof("NodeUnstageVolume: CleanupMountPoint on %s with volume %s", stagingTargetPath, volumeID)
if err := CleanupSMBMountPoint(d.mounter, stagingTargetPath, true /*extensiveMountPointCheck*/, volumeID); err != nil {
return nil, status.Errorf(codes.Internal, "failed to unmount staging target %q: %v", stagingTargetPath, err)
inUse, err := HasMountReferences(stagingTargetPath)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to check mount references: %v", err)
}
if inUse {
klog.V(2).Infof("NodeUnstageVolume: staging path %s is still in use by other mounts", stagingTargetPath)
return &csi.NodeUnstageVolumeResponse{}, nil
}

if err := deleteKerberosCache(d.krb5CacheDirectory, volumeID); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete kerberos cache: %v", err)
notMnt, err := d.mounter.IsLikelyNotMountPoint(stagingTargetPath)
if err != nil && !os.IsNotExist(err) {
return nil, status.Errorf(codes.Internal, "failed to check mount point %q: %v", stagingTargetPath, err)
}
if notMnt {
klog.V(2).Infof("NodeUnstageVolume: staging path %s is already unmounted", stagingTargetPath)
if err := os.Remove(stagingTargetPath); err != nil && !os.IsNotExist(err) {
return nil, status.Errorf(codes.Internal, "failed to remove staging path %q: %v", stagingTargetPath, err)
}
return &csi.NodeUnstageVolumeResponse{}, nil
}

klog.V(2).Infof("NodeUnstageVolume: unmount volume %s on %s successfully", volumeID, stagingTargetPath)
// Final safety check: verify no new references appeared right before unmounting
lastCheck, err := HasMountReferences(stagingTargetPath)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed final mount reference check: %v", err)
}
if lastCheck {
klog.V(2).Infof("NodeUnstageVolume: new mount references detected just before unmount, aborting for %s", stagingTargetPath)
return &csi.NodeUnstageVolumeResponse{}, nil
}

klog.V(2).Infof("NodeUnstageVolume: unmounting %s for volume %s", stagingTargetPath, volumeID)
if err := d.mounter.Unmount(stagingTargetPath); err != nil {
return nil, status.Errorf(codes.Internal, "failed to unmount staging path %q: %v", stagingTargetPath, err)
}
if err := os.Remove(stagingTargetPath); err != nil && !os.IsNotExist(err) {
return nil, status.Errorf(codes.Internal, "failed to remove staging path %q after unmount: %v", stagingTargetPath, err)
}

klog.V(2).Infof("NodeUnstageVolume: successfully unmounted and cleaned up %s for volume %s", stagingTargetPath, volumeID)
return &csi.NodeUnstageVolumeResponse{}, nil
}

Expand Down Expand Up @@ -611,52 +658,6 @@ func ensureKerberosCache(krb5CacheDirectory, krb5Prefix, volumeID string, mountF
return false, nil
}

func deleteKerberosCache(krb5CacheDirectory, volumeID string) error {
exists, err := kerberosCacheDirectoryExists(krb5CacheDirectory)
// If not supported, simply return
if !exists {
return nil
}
if err != nil {
return err
}

volumeIDCacheFileName := volumeKerberosCacheName(volumeID)

var volumeIDCacheAbsolutePath = getKerberosFilePath(krb5CacheDirectory, volumeIDCacheFileName)
_, err = os.Stat(volumeIDCacheAbsolutePath)
// Not created or already removed
if os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}

// If file with cache exists, full clean means removing symlinks to the file.
dirEntries, _ := os.ReadDir(krb5CacheDirectory)
for _, dirEntry := range dirEntries {
filePath := getKerberosFilePath(krb5CacheDirectory, dirEntry.Name())
lStat, _ := os.Lstat(filePath)
// If it's a symlink, checking if it's pointing to the volume file in question
if lStat != nil {
target, _ := os.Readlink(filePath)
if target == volumeIDCacheAbsolutePath {
err = os.Remove(filePath)
if err != nil {
klog.Errorf("Error removing symlink to kerberos ticket cache: %s (%v)", filePath, err)
}
}
}
}

err = os.Remove(volumeIDCacheAbsolutePath)
if err != nil {
klog.Errorf("Error removing symlink to kerberos ticket cache: %s (%v)", volumeIDCacheAbsolutePath, err)
}

return nil
}

// Raises RWX bits for group access in the mode arg. If mode is invalid, keep it unchanged.
func enableGroupRWX(mode string) string {
v, e := strconv.ParseInt(mode, 0, 0)
Expand Down
9 changes: 9 additions & 0 deletions pkg/smb/smb_common_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,12 @@ func prepareStagePath(path string, m *mount.SafeFormatAndMount) error {
func Mkdir(m *mount.SafeFormatAndMount, name string, perm os.FileMode) error {
return os.Mkdir(name, perm)
}

// HasMountReferences is stubbed for macOS as bind mount inspection is not implemented.
// Always returns false to allow unmounting, but this limits the race condition protection
// available on macOS compared to Linux.
func HasMountReferences(stagingTargetPath string) (bool, error) {
// macOS implementation could potentially inspect mount points but is not implemented
// This is a known limitation that reduces race condition protection
return false, nil
}
Loading
Loading