Skip to content

Commit b18fef8

Browse files
authored
Secure SMB implementation for Ontap-Nas-Eco
1 parent 23f3d43 commit b18fef8

File tree

4 files changed

+403
-39
lines changed

4 files changed

+403
-39
lines changed

storage_drivers/ontap/ontap_common.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4402,16 +4402,35 @@ func ConstructOntapNASQTreeVolumePath(
44024402
}
44034403
case sa.SMB:
44044404
var smbSharePath string
4405-
if smbShare != "" {
4405+
if smbShare != "" && !volConfig.SecureSMBEnabled {
44064406
smbSharePath = smbShare + tridentconfig.WindowsPathSeparator
44074407
}
4408+
// In Secure SMB mode, Trident creates a unique SMB share for each qtree. Therefore, we use internalName (the qtree name) to construct the SMB path, instead of flexvol.
44084409
if volConfig.ReadOnlyClone {
4409-
completeVolumePath = fmt.Sprintf("\\%s%s\\%s\\%s\\%s", smbSharePath, flexvol,
4410-
volConfig.CloneSourceVolumeInternal, "~snapshot", volConfig.CloneSourceSnapshot)
4410+
if volConfig.SecureSMBEnabled {
4411+
// Secure SMB, read-only clone:
4412+
// Use for SMB client access to a snapshot of a qtree.
4413+
// Example: \qtree1\~snapshot\snap1
4414+
completeVolumePath = fmt.Sprintf("\\%s\\%s\\%s", volConfig.InternalName, "~snapshot", volConfig.CloneSourceSnapshot)
4415+
} else {
4416+
// Non-secure SMB, read-only clone:
4417+
// Use for SMB client access to a snapshot of a qtree via a shared SMB path.
4418+
// Example: \share1\flexvol1\sourceQtree\~snapshot\snap1
4419+
completeVolumePath = fmt.Sprintf("\\%s%s\\%s\\%s\\%s", smbSharePath, flexvol, volConfig.CloneSourceVolumeInternal, "~snapshot", volConfig.CloneSourceSnapshot)
4420+
}
44114421
} else {
4412-
completeVolumePath = fmt.Sprintf("\\%s%s\\%s", smbSharePath, flexvol, volConfig.InternalName)
4422+
if volConfig.SecureSMBEnabled {
4423+
// Secure SMB :
4424+
// Use for SMB client access to a qtree.
4425+
// Example: \qtree1
4426+
completeVolumePath = fmt.Sprintf("\\%s", volConfig.InternalName)
4427+
} else {
4428+
// Non-secure SMB,:
4429+
// Use for SMB client access to a qtree via a shared SMB path.
4430+
// Example: \share1\flexvol1\qtree1
4431+
completeVolumePath = fmt.Sprintf("\\%s%s\\%s", smbSharePath, flexvol, volConfig.InternalName)
4432+
}
44134433
}
4414-
44154434
// Replace unix styled path separator, if exists
44164435
completeVolumePath = strings.Replace(completeVolumePath, tridentconfig.UnixPathSeparator,
44174436
tridentconfig.WindowsPathSeparator,
@@ -5007,3 +5026,11 @@ func purgeRecoveryQueueVolume(ctx context.Context, api api.OntapAPI, volumeName
50075026
volumeName).Errorf("error purging volume from ONTAP recovery queue: %v", err)
50085027
}
50095028
}
5029+
5030+
// getSMBShareNamePath constructs the SMB share name and path based on the provided parameters.
5031+
func getSMBShareNamePath(flexvol, name string, secureSMBEnabled bool) (string, string) {
5032+
if secureSMBEnabled {
5033+
return name, "/" + flexvol + "/" + name
5034+
}
5035+
return flexvol, "/" + flexvol
5036+
}

storage_drivers/ontap/ontap_common_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,6 +2636,82 @@ func TestConstructOntapNASQTreeVolumePath(t *testing.T) {
26362636
}
26372637
}
26382638

2639+
func TestConstructOntapNASQTreeVolumePath_SecureSMBEnabled(t *testing.T) {
2640+
ctx := context.Background()
2641+
2642+
tests := []struct {
2643+
smbShare string
2644+
flexvol string
2645+
volConfig *storage.VolumeConfig
2646+
protocol string
2647+
expectedPath string
2648+
}{
2649+
{
2650+
"test_share",
2651+
"flex-vol",
2652+
&storage.VolumeConfig{
2653+
Name: "pvc-vol",
2654+
InternalName: "trident_pvc_vol",
2655+
CloneSourceVolumeInternal: "cloneSourceInternal",
2656+
CloneSourceSnapshot: "sourceSnapShot",
2657+
ReadOnlyClone: false,
2658+
SecureSMBEnabled: true,
2659+
},
2660+
sa.SMB,
2661+
"\\trident_pvc_vol",
2662+
},
2663+
{
2664+
"",
2665+
"flex-vol",
2666+
&storage.VolumeConfig{
2667+
Name: "volumeConfig",
2668+
InternalName: "trident_pvc_vol",
2669+
CloneSourceVolumeInternal: "cloneSourceInternal",
2670+
CloneSourceSnapshot: "sourceSnapShot",
2671+
ReadOnlyClone: false,
2672+
SecureSMBEnabled: true,
2673+
},
2674+
sa.SMB,
2675+
"\\trident_pvc_vol",
2676+
},
2677+
{
2678+
"test_share",
2679+
"flex-vol",
2680+
&storage.VolumeConfig{
2681+
Name: "volumeConfig",
2682+
InternalName: "trident_pvc_vol",
2683+
CloneSourceVolumeInternal: "cloneSourceInternal",
2684+
CloneSourceSnapshot: "sourceSnapShot",
2685+
ReadOnlyClone: true,
2686+
SecureSMBEnabled: true,
2687+
},
2688+
sa.SMB,
2689+
"\\trident_pvc_vol\\~snapshot\\sourceSnapShot",
2690+
},
2691+
{
2692+
"",
2693+
"flex-vol",
2694+
&storage.VolumeConfig{
2695+
Name: "volumeConfig",
2696+
InternalName: "trident_pvc_vol",
2697+
CloneSourceVolumeInternal: "cloneSourceInternal",
2698+
CloneSourceSnapshot: "sourceSnapShot",
2699+
ReadOnlyClone: true,
2700+
SecureSMBEnabled: true,
2701+
},
2702+
sa.SMB,
2703+
"\\trident_pvc_vol\\~snapshot\\sourceSnapShot",
2704+
},
2705+
}
2706+
2707+
for _, test := range tests {
2708+
t.Run(test.smbShare, func(t *testing.T) {
2709+
result := ConstructOntapNASQTreeVolumePath(ctx, test.smbShare, "flex-vol", test.volConfig, test.protocol)
2710+
assert.Equal(t, test.expectedPath, result, "unable to construct Ontap-NAS-QTree SMB volume path")
2711+
})
2712+
}
2713+
}
2714+
26392715
func TestEnsureNodeAccess(t *testing.T) {
26402716
// Test 1 - Positive flow
26412717

@@ -9530,3 +9606,41 @@ func TestCloneASAvol(t *testing.T) {
95309606
})
95319607
}
95329608
}
9609+
9610+
func TestGetSMBShareNamePath(t *testing.T) {
9611+
// Define test cases
9612+
tests := []struct {
9613+
name string
9614+
flexvol string
9615+
inputName string
9616+
secureSMBEnabled bool
9617+
expectedName string
9618+
expectedPath string
9619+
}{
9620+
{
9621+
name: "secureSMB disabled, valid flexvol",
9622+
flexvol: "vol1",
9623+
inputName: "share1",
9624+
secureSMBEnabled: false,
9625+
expectedName: "vol1",
9626+
expectedPath: "/vol1",
9627+
},
9628+
{
9629+
name: "secureSMB enabled, valid flexvol and name",
9630+
flexvol: "vol1",
9631+
inputName: "share1",
9632+
secureSMBEnabled: true,
9633+
expectedName: "share1",
9634+
expectedPath: "/vol1/share1",
9635+
},
9636+
}
9637+
9638+
// Run test cases
9639+
for _, tt := range tests {
9640+
t.Run(tt.name, func(t *testing.T) {
9641+
shareName, sharePath := getSMBShareNamePath(tt.flexvol, tt.inputName, tt.secureSMBEnabled)
9642+
assert.Equal(t, tt.expectedName, shareName, "shareName mismatch for flexvol=%q, name=%q, secureSMBEnabled=%v", tt.flexvol, tt.inputName, tt.secureSMBEnabled)
9643+
assert.Equal(t, tt.expectedPath, sharePath, "sharePath mismatch for flexvol=%q, name=%q, secureSMBEnabled=%v", tt.flexvol, tt.inputName, tt.secureSMBEnabled)
9644+
})
9645+
}
9646+
}

storage_drivers/ontap/ontap_nas_qtree.go

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ func (d *NASQtreeStorageDriver) Create(
391391
spaceReserve = collection.GetV(opts, "spaceReserve", storagePool.InternalAttributes()[SpaceReserve])
392392
snapshotPolicy = collection.GetV(opts, "snapshotPolicy", storagePool.InternalAttributes()[SnapshotPolicy])
393393
snapshotReserve = storagePool.InternalAttributes()[SnapshotReserve]
394+
adAdminUser = collection.GetV(opts, "adAdminUser", storagePool.InternalAttributes()[ADAdminUser])
394395
snapshotDir = collection.GetV(opts, "snapshotDir", storagePool.InternalAttributes()[SnapshotDir])
395396
encryption = collection.GetV(opts, "encryption", storagePool.InternalAttributes()[Encryption])
396397

@@ -499,7 +500,14 @@ func (d *NASQtreeStorageDriver) Create(
499500
}
500501

501502
if d.Config.NASType == sa.SMB {
502-
if err = d.EnsureSMBShare(ctx, flexvol); err != nil {
503+
if adAdminUser != "" {
504+
if _, exists := volConfig.SMBShareACL[adAdminUser]; !exists {
505+
volConfig.SMBShareACL[adAdminUser] = ADAdminUserPermission
506+
}
507+
}
508+
509+
shareName, sharePath := getSMBShareNamePath(flexvol, name, volConfig.SecureSMBEnabled)
510+
if err = d.EnsureSMBShare(ctx, shareName, sharePath, volConfig.SMBShareACL, volConfig.SecureSMBEnabled); err != nil {
503511
return err
504512
}
505513
}
@@ -513,7 +521,7 @@ func (d *NASQtreeStorageDriver) Create(
513521

514522
// CreateClone creates a volume clone
515523
func (d *NASQtreeStorageDriver) CreateClone(
516-
ctx context.Context, sourceVolConfig, cloneVolConfig *storage.VolumeConfig, _ storage.Pool,
524+
ctx context.Context, sourceVolConfig, cloneVolConfig *storage.VolumeConfig, storagePool storage.Pool,
517525
) error {
518526
name := cloneVolConfig.InternalName
519527
source := cloneVolConfig.CloneSourceVolumeInternal
@@ -548,6 +556,32 @@ func (d *NASQtreeStorageDriver) CreateClone(
548556
return fmt.Errorf("snapshot directory access is set to %t and readOnly clone is set to %t ",
549557
*storageVolume.SnapshotDir, cloneVolConfig.ReadOnlyClone)
550558
}
559+
560+
// If the NAS type is SMB and Secure SMB is enabled, configure SMB share and ACLs for the clone.
561+
if d.Config.NASType == sa.SMB && cloneVolConfig.SecureSMBEnabled {
562+
adAdminUser := d.Config.ADAdminUser
563+
564+
// If a storage pool is set, prefer its AD admin user if available.
565+
if !storage.IsStoragePoolUnset(storagePool) {
566+
if spAdminUser := storagePool.InternalAttributes()[ADAdminUser]; spAdminUser != "" {
567+
adAdminUser = spAdminUser
568+
}
569+
}
570+
571+
// Add the AD admin user to the SMB share ACL with the necessary permissions if not already present.
572+
if adAdminUser != "" {
573+
if _, exists := cloneVolConfig.SMBShareACL[adAdminUser]; !exists {
574+
cloneVolConfig.SMBShareACL[adAdminUser] = ADAdminUserPermission
575+
}
576+
}
577+
578+
// Determine the SMB share path to create new SMB Share.
579+
_, sharePath := getSMBShareNamePath(flexvol, source, cloneVolConfig.SecureSMBEnabled)
580+
if err := d.EnsureSMBShare(ctx, name, sharePath, cloneVolConfig.SMBShareACL,
581+
cloneVolConfig.SecureSMBEnabled); err != nil {
582+
return err
583+
}
584+
}
551585
} else {
552586
return fmt.Errorf("cloning is not supported by backend type %s", d.Name())
553587
}
@@ -575,14 +609,19 @@ func (d *NASQtreeStorageDriver) Destroy(ctx context.Context, volConfig *storage.
575609
"name": name,
576610
}
577611

578-
// If it's a RO clone, no need to delete the volume
579-
if volConfig.ReadOnlyClone {
580-
return nil
581-
}
582-
583612
Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> Destroy")
584613
defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< Destroy")
585614

615+
// Handle ReadOnlyClone case early
616+
if volConfig.ReadOnlyClone {
617+
if d.Config.NASType == sa.SMB && volConfig.SecureSMBEnabled {
618+
if err := d.DestroySMBShare(ctx, name); err != nil {
619+
return err
620+
}
621+
}
622+
return nil // Return early for all ReadOnlyClone cases
623+
}
624+
586625
// Ensure the deleted qtree reaping job doesn't interfere with this workflow
587626
locks.Lock(ctx, "destroy", d.sharedLockID)
588627
defer locks.Unlock(ctx, "destroy", d.sharedLockID)
@@ -639,6 +678,12 @@ func (d *NASQtreeStorageDriver) Destroy(ctx context.Context, volConfig *storage.
639678
return deleteError
640679
}
641680

681+
if d.Config.NASType == sa.SMB && volConfig.SecureSMBEnabled {
682+
if err := d.DestroySMBShare(ctx, name); err != nil {
683+
return err
684+
}
685+
}
686+
642687
return nil
643688
}
644689

@@ -2542,37 +2587,58 @@ func (d NASQtreeStorageDriver) getQtreesInPool(
25422587
}
25432588

25442589
// EnsureSMBShare ensures that required SMB share is made available.
2545-
func (d *NASQtreeStorageDriver) EnsureSMBShare(
2546-
ctx context.Context, name string,
2590+
func (d NASQtreeStorageDriver) EnsureSMBShare(
2591+
ctx context.Context, name, path string, smbShareACL map[string]string, secureSMBEnabled bool,
25472592
) error {
2548-
if d.Config.SMBShare != "" {
2549-
// If user did specify SMB share, and it does not exist, create an SMB share with the specified name.
2550-
share, err := d.API.SMBShareExists(ctx, d.Config.SMBShare)
2551-
if err != nil {
2593+
shareName := name
2594+
sharePath := path
2595+
2596+
// Determine share name and path based on secureSMBEnabled and configuration
2597+
if !secureSMBEnabled && d.Config.SMBShare != "" {
2598+
shareName = d.Config.SMBShare
2599+
sharePath = "/"
2600+
}
2601+
2602+
// Check if the share exists
2603+
if shareExists, err := d.API.SMBShareExists(ctx, shareName); err != nil {
2604+
return err
2605+
} else if !shareExists {
2606+
// Create the share if it does not exist
2607+
if err = d.API.SMBShareCreate(ctx, shareName, sharePath); err != nil {
25522608
return err
25532609
}
2610+
}
25542611

2555-
// If share is not present create it.
2556-
if !share {
2557-
if err := d.API.SMBShareCreate(ctx, d.Config.SMBShare, "/"); err != nil {
2558-
return err
2559-
}
2560-
}
2561-
} else {
2562-
// If user did not specify SMB share in backend configuration, create an SMB share with the name passed.
2563-
share, err := d.API.SMBShareExists(ctx, name)
2564-
if err != nil {
2612+
// If secure SMB is enabled, configure access control
2613+
if secureSMBEnabled {
2614+
// Delete the Everyone Access Control created by default during share creation by ONTAP
2615+
if err := d.API.SMBShareAccessControlDelete(ctx, shareName, smbShareDeleteACL); err != nil {
25652616
return err
25662617
}
25672618

2568-
// If share is not present create it.
2569-
if !share {
2570-
if err := d.API.SMBShareCreate(ctx, name, "/"+name); err != nil {
2571-
return err
2572-
}
2619+
if err := d.API.SMBShareAccessControlCreate(ctx, shareName, smbShareACL); err != nil {
2620+
return err
25732621
}
25742622
}
2623+
return nil
2624+
}
25752625

2626+
// DestroySMBShare destroys an SMB share
2627+
func (d NASQtreeStorageDriver) DestroySMBShare(
2628+
ctx context.Context, name string,
2629+
) error {
2630+
// If the share being deleted matches with the backend config, Trident will not delete the SMB share.
2631+
if d.Config.SMBShare == name {
2632+
return nil
2633+
}
2634+
2635+
if shareExists, err := d.API.SMBShareExists(ctx, name); err != nil {
2636+
return err
2637+
} else if shareExists {
2638+
if err := d.API.SMBShareDestroy(ctx, name); err != nil {
2639+
return err
2640+
}
2641+
}
25762642
return nil
25772643
}
25782644

0 commit comments

Comments
 (0)