Skip to content

Commit 07c835e

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 e19d2bd commit 07c835e

File tree

2 files changed

+121
-6
lines changed

2 files changed

+121
-6
lines changed

internal/nvmeof/nodeserver/nodeserver.go

Lines changed: 109 additions & 5 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.SecurityKeyNVMEOFManager
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,9 +153,9 @@ 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())
158+
cr, err := util.NewUserCredentialsWithMigration(secrets)
147159
if err != nil {
148160
return nil, status.Error(codes.InvalidArgument, err.Error())
149161
}
@@ -173,10 +185,13 @@ func (ns *NodeServer) NodeStageVolume(
173185
if err != nil {
174186
return nil, status.Errorf(codes.InvalidArgument, "invalid volume context: %v", err)
175187
}
188+
// Get authentication KMS ID from volume context.
189+
// can be empty! it will take default KMS in that case
190+
authKMSID := volumeContext[authenticationKMSID]
176191

177192
// perform the actual staging and if this fails, have undoStagingTransaction
178193
// cleans up for us
179-
txn, err := ns.stageTransaction(ctx, req, connectionInfo)
194+
txn, err := ns.stageTransaction(ctx, req, connectionInfo, cr, secrets, authKMSID)
180195
defer func() {
181196
if err != nil {
182197
ns.undoStagingTransaction(ctx, req, txn)
@@ -426,13 +441,16 @@ func (ns *NodeServer) stageTransaction(
426441
ctx context.Context,
427442
req *csi.NodeStageVolumeRequest,
428443
connectionInfo *NvmeConnectionInfo,
444+
cr *util.Credentials,
445+
secrets map[string]string,
446+
authKMSID string,
429447
) (*stageTransaction, error) {
430448
transaction := &stageTransaction{}
431449

432450
var err error
433451

434452
// perform the actual staging
435-
devicePath, err := ns.connectToSubsystem(ctx, connectionInfo)
453+
devicePath, err := ns.connectToSubsystem(ctx, req.GetVolumeId(), connectionInfo, cr, secrets, authKMSID)
436454
if err != nil {
437455
return transaction, err
438456
}
@@ -579,6 +597,10 @@ func (ns *NodeServer) getNvmeConnection(volumeContext, publishContext map[string
579597
if !ok || hostNQN == "" {
580598
return nil, errors.New("missing host NQN in publish context")
581599
}
600+
dhchapMode, ok := volumeContext[nvmeofdhchapMode]
601+
if !ok || dhchapMode == "" || dhchapMode == "none" {
602+
dhchapMode = ""
603+
}
582604

583605
return &NvmeConnectionInfo{
584606
SubsystemNQN: subsystemNQN,
@@ -587,11 +609,19 @@ func (ns *NodeServer) getNvmeConnection(volumeContext, publishContext map[string
587609
Listeners: listeners,
588610
HostNQN: hostNQN,
589611
Transport: defaultTransport,
612+
DhchapMode: dhchapMode,
590613
}, nil
591614
}
592615

593616
// connectToSubsystem connects to the NVMe-oF subsystem and returns device path.
594-
func (ns *NodeServer) connectToSubsystem(ctx context.Context, info *NvmeConnectionInfo) (string, error) {
617+
func (ns *NodeServer) connectToSubsystem(
618+
ctx context.Context,
619+
volumeID string,
620+
info *NvmeConnectionInfo,
621+
cr *util.Credentials,
622+
secrets map[string]string,
623+
authKMSID string,
624+
) (string, error) {
595625
// Create connect request
596626
connectReq := &nvmeof.ConnectRequest{
597627
SubsystemNQN: info.SubsystemNQN,
@@ -600,6 +630,13 @@ func (ns *NodeServer) connectToSubsystem(ctx context.Context, info *NvmeConnecti
600630
HostNQN: info.HostNQN,
601631
}
602632

633+
// Setup DH-CHAP authentication if required
634+
if info.DhchapMode != nvmeof.DHCHAPEmpty && info.DhchapMode != nvmeof.DHCHAPModeNone {
635+
if err := ns.setupDHCHAPAuth(ctx, volumeID, info, cr, secrets, authKMSID, connectReq); err != nil {
636+
return "", err
637+
}
638+
}
639+
603640
// Connect to subsystem
604641
_, err := ns.initiator.ConnectSubsystem(ctx, connectReq)
605642
if err != nil {
@@ -732,3 +769,70 @@ func (ns *NodeServer) getDeviceFromMount(ctx context.Context, mountPath string)
732769

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

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)