Skip to content

Commit 34076a5

Browse files
committed
nvmeof: add DH-CHAP authentication support in NodeStageVolume
Implement DH-CHAP authentication for NVMe-oF initiator connections with support for both unidirectional and bidirectional modes. Changes: - setupDHCHAPAuth(): Retrieve host/subsystem keys from KMS and configure ConnectRequest with authentication parameters - Pass dhchapMode from volume context through connection flow - Add --dhchap-secret and --dhchap-ctrl-secret to nvme connect command - Support RBD metadata DEKStore (testing) and Vault KMS (production) Authentication flow: 1. NodeStageVolume receives dhchapMode from volume context 2. Initialize SecurityKeyNVMEOFManager with same KMS as controller 3. Retrieve existing DH-CHAP keys (generated during ControllerPublishVolume) 4. For unidirectional: Add host key to nvme connect 5. For bidirectional: Add both host and subsystem keys Signed-off-by: gadi-didi <gadi.didi@ibm.com>
1 parent a441aa1 commit 34076a5

File tree

2 files changed

+123
-10
lines changed

2 files changed

+123
-10
lines changed

internal/nvmeof/nodeserver/nodeserver.go

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232

3333
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
3434
"github.com/ceph/ceph-csi/internal/nvmeof"
35+
rbdutil "github.com/ceph/ceph-csi/internal/rbd"
3536
"github.com/ceph/ceph-csi/internal/util"
3637
"github.com/ceph/ceph-csi/internal/util/log"
3738
)
@@ -45,6 +46,12 @@ type NodeServer struct {
4546
volumeLocks *util.IDLocker
4647

4748
initiator nvmeof.NVMeInitiator
49+
50+
// securityKeys manages DH-CHAP and PSK\TLS keys
51+
securityKeys nvmeof.SecurityKeyManager
52+
53+
// node ID of this node server
54+
nodeID string
4855
}
4956

5057
// ConnectionInfo holds NVMe-oF connection details.
@@ -55,6 +62,7 @@ type NvmeConnectionInfo struct {
5562
Listeners []nvmeof.GatewayAddress `json:"listeners"`
5663
HostNQN string `json:"hostNQN,omitempty"`
5764
Transport string `json:"transport"`
65+
DhchapMode string `json:"dhchapMode,omitempty"`
5866
}
5967

6068
// stageTransaction struct represents the state a transaction was when it either completed
@@ -76,6 +84,8 @@ const (
7684
nvmeofListeners = "listeners"
7785
nvmeofHostNQN = "HostNQN"
7886
defaultTransport = "tcp"
87+
nvmeofdhchapMode = "dhchapMode"
88+
authenticationKMSID = "authenticationKMSID"
7989
)
8090

8191
// NewNodeServer initialize a node server for ceph CSI driver.
@@ -90,6 +100,8 @@ func NewNodeServer(
90100
DefaultNodeServer: *csicommon.NewDefaultNodeServer(d, t, "", map[string]string{}, map[string]string{}),
91101
initiator: nvmeInitiator,
92102
volumeLocks: util.NewIDLocker(),
103+
securityKeys: nil, // Initialize lazily when needed
104+
nodeID: nodeID,
93105
}
94106

95107
// Load nvme kernel modules
@@ -141,13 +153,8 @@ func (ns *NodeServer) NodeStageVolume(
141153
if err = util.ValidateNodeStageVolumeRequest(req); err != nil {
142154
return nil, err
143155
}
144-
156+
secrets := req.GetSecrets()
145157
volumeID := req.GetVolumeId()
146-
cr, err := util.NewUserCredentialsWithMigration(req.GetSecrets())
147-
if err != nil {
148-
return nil, status.Error(codes.InvalidArgument, err.Error())
149-
}
150-
defer cr.DeleteCredentials()
151158
if acquired := ns.volumeLocks.TryAcquire(volumeID); !acquired {
152159
log.ErrorLog(ctx, util.VolumeOperationAlreadyExistsFmt, volumeID)
153160

@@ -173,10 +180,13 @@ func (ns *NodeServer) NodeStageVolume(
173180
if err != nil {
174181
return nil, status.Errorf(codes.InvalidArgument, "invalid volume context: %v", err)
175182
}
183+
// Get authentication KMS ID from volume context.
184+
// can be empty! it will take default KMS in that case
185+
authKMSID := volumeContext[authenticationKMSID]
176186

177187
// perform the actual staging and if this fails, have undoStagingTransaction
178188
// cleans up for us
179-
txn, err := ns.stageTransaction(ctx, req, connectionInfo)
189+
txn, err := ns.stageTransaction(ctx, req, connectionInfo, secrets, authKMSID)
180190
defer func() {
181191
if err != nil {
182192
ns.undoStagingTransaction(ctx, req, txn)
@@ -426,13 +436,15 @@ func (ns *NodeServer) stageTransaction(
426436
ctx context.Context,
427437
req *csi.NodeStageVolumeRequest,
428438
connectionInfo *NvmeConnectionInfo,
439+
secrets map[string]string,
440+
authKMSID string,
429441
) (*stageTransaction, error) {
430442
transaction := &stageTransaction{}
431443

432444
var err error
433445

434446
// perform the actual staging
435-
devicePath, err := ns.connectToSubsystem(ctx, connectionInfo)
447+
devicePath, err := ns.connectToSubsystem(ctx, req.GetVolumeId(), connectionInfo, secrets, authKMSID)
436448
if err != nil {
437449
return transaction, err
438450
}
@@ -579,6 +591,10 @@ func (ns *NodeServer) getNvmeConnection(volumeContext, publishContext map[string
579591
if !ok || hostNQN == "" {
580592
return nil, errors.New("missing host NQN in publish context")
581593
}
594+
dhchapMode, ok := volumeContext[nvmeofdhchapMode]
595+
if !ok || dhchapMode == "" || dhchapMode == "none" {
596+
dhchapMode = ""
597+
}
582598

583599
return &NvmeConnectionInfo{
584600
SubsystemNQN: subsystemNQN,
@@ -587,11 +603,18 @@ func (ns *NodeServer) getNvmeConnection(volumeContext, publishContext map[string
587603
Listeners: listeners,
588604
HostNQN: hostNQN,
589605
Transport: defaultTransport,
606+
DhchapMode: dhchapMode,
590607
}, nil
591608
}
592609

593610
// connectToSubsystem connects to the NVMe-oF subsystem and returns device path.
594-
func (ns *NodeServer) connectToSubsystem(ctx context.Context, info *NvmeConnectionInfo) (string, error) {
611+
func (ns *NodeServer) connectToSubsystem(
612+
ctx context.Context,
613+
volumeID string,
614+
info *NvmeConnectionInfo,
615+
secrets map[string]string,
616+
authKMSID string,
617+
) (string, error) {
595618
// Create connect request
596619
connectReq := &nvmeof.ConnectRequest{
597620
SubsystemNQN: info.SubsystemNQN,
@@ -600,6 +623,13 @@ func (ns *NodeServer) connectToSubsystem(ctx context.Context, info *NvmeConnecti
600623
HostNQN: info.HostNQN,
601624
}
602625

626+
// Setup DH-CHAP authentication if required
627+
if info.DhchapMode != nvmeof.DHCHAPEmpty && info.DhchapMode != nvmeof.DHCHAPModeNone {
628+
if err := ns.setupDHCHAPAuth(ctx, volumeID, info, secrets, authKMSID, connectReq); err != nil {
629+
return "", err
630+
}
631+
}
632+
603633
// Connect to subsystem
604634
_, err := ns.initiator.ConnectSubsystem(ctx, connectReq)
605635
if err != nil {
@@ -732,3 +762,75 @@ func (ns *NodeServer) getDeviceFromMount(ctx context.Context, mountPath string)
732762

733763
return "", fmt.Errorf("no mount found for path %s", mountPath)
734764
}
765+
766+
func (cs *NodeServer) getOrInitSecurityKeys(
767+
ctx context.Context,
768+
kmsID string,
769+
credentials map[string]string,
770+
) (nvmeof.SecurityKeyManager, error) {
771+
if cs.securityKeys != nil {
772+
return cs.securityKeys, nil
773+
}
774+
775+
var err error
776+
securityKeys, err := nvmeof.InitSecurityKeyManager(ctx, kmsID, credentials)
777+
778+
// Only cache if it doesn't need external DEKStore (like Vault)
779+
// (RBD Metadata KMS needs fresh RBD volume each call)
780+
if !errors.Is(err, nvmeof.ErrDEKStoreNeeded) {
781+
cs.securityKeys = securityKeys
782+
}
783+
784+
return securityKeys, err
785+
}
786+
787+
// setupDHCHAPAuth configures DH-CHAP authentication for the connection.
788+
func (ns *NodeServer) setupDHCHAPAuth(
789+
ctx context.Context,
790+
volumeID string,
791+
info *NvmeConnectionInfo,
792+
secrets map[string]string,
793+
authKMSID string,
794+
connectReq *nvmeof.ConnectRequest,
795+
) error {
796+
// Initialize security key manager
797+
securityKeys, err := ns.getOrInitSecurityKeys(ctx, authKMSID, secrets)
798+
799+
// Setup DEK store if needed (for metadata KMS)
800+
if errors.Is(err, nvmeof.ErrDEKStoreNeeded) {
801+
cr, err := util.NewUserCredentialsWithMigration(secrets)
802+
if err != nil {
803+
return fmt.Errorf("failed to get user credentials: %w", err)
804+
}
805+
defer cr.DeleteCredentials()
806+
807+
rbdVol, err := rbdutil.GenVolFromVolID(ctx, volumeID, cr, secrets)
808+
if err != nil {
809+
return fmt.Errorf("failed to get volume: %w", err)
810+
}
811+
defer rbdVol.Destroy(ctx)
812+
813+
dekStore := nvmeof.NewRBDVolumeDEKStore(rbdVol)
814+
securityKeys.SetDEKStore(dekStore)
815+
} else if err != nil {
816+
return fmt.Errorf("failed to initialize security key manager: %w", err)
817+
}
818+
819+
// Get host key
820+
hostKey, err := nvmeof.GetOrCreateDHCHAPHostKey(ctx, securityKeys, ns.nodeID, info.SubsystemNQN)
821+
if err != nil {
822+
return fmt.Errorf("failed to get DHCHAP host key: %w", err)
823+
}
824+
connectReq.HostDhchapKey = hostKey
825+
826+
// Get subsystem key for bidirectional mode
827+
if info.DhchapMode == nvmeof.DHCHAPModeBiDirectional {
828+
subsystemKey, err := nvmeof.GetOrCreateDHCHAPSubsystemKey(ctx, securityKeys, ns.nodeID, info.SubsystemNQN)
829+
if err != nil {
830+
return fmt.Errorf("failed to get DH-CHAP subsystem key: %w", err)
831+
}
832+
connectReq.SubsystemDhchapKey = subsystemKey
833+
}
834+
835+
return nil
836+
}

internal/nvmeof/nvmeof_initiator.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ type ConnectRequest struct {
6161
Listeners []GatewayAddress
6262
Transport string // "tcp"
6363
HostNQN string // Optional - empty means use system default
64+
// Optional - In-band authentication controller secret for bi-directional authentication.
65+
SubsystemDhchapKey string
66+
// Optional - In-band authentication secret for uni-directional authentication
67+
HostDhchapKey string
6468
}
6569

6670
// nvmeInitiator implements NVMeInitiator interface.
@@ -185,7 +189,14 @@ func (ni *nvmeInitiator) ConnectSubsystem(ctx context.Context, req *ConnectReque
185189
if req.HostNQN != "" {
186190
args = append(args, "--hostnqn", req.HostNQN)
187191
}
188-
192+
// if Host DH-CHAP key is provided, add it to the command
193+
if req.HostDhchapKey != "" {
194+
args = append(args, "--dhchap-secret", req.HostDhchapKey)
195+
}
196+
// if Subsystem DH-CHAP key is provided, add it to the command (for bi-directional auth)
197+
if req.SubsystemDhchapKey != "" {
198+
args = append(args, "--dhchap-ctrl-secret", req.SubsystemDhchapKey)
199+
}
189200
stdout, stderr, err := util.ExecCommandWithTimeout(ctx, connectTimeout, "nvme", args...)
190201
// Execute connection
191202
if err != nil {

0 commit comments

Comments
 (0)