Host Authentication and Encryption
Simplyblock protects the NVMe-oF transport between a worker node and a storage node with host access control and DH-HMAC-CHAP authentication. Only a host registered as an allowed host of a storage pool can connect to the NVMe-oF subsystem of a volume in that pool, and every connection is authenticated in both directions with the NVMe standard authentication protocol (TP8018).
On Kubernetes, both are configured declaratively on the StoragePool custom resource and reconciled by the
Simplyblock Operator. No host NQN has to be registered, and no key has to be provisioned by hand.
Transport encryption and volume encryption
TLS/PSK encryption of the NVMe-oF transport is not exposed through the StoragePool. Encryption of the volume
data at rest is an unrelated, per-volume feature and is described in
Volume Encryption.
Enable Host Authentication and Encryption
Security is configured per storage pool and is disabled by default. It is enabled by setting dhchap on the
StoragePool and listing the worker nodes that are allowed to connect to the pool in allowedNodes.
apiVersion: storage.simplyblock.io/v1alpha1
kind: StoragePool
metadata:
name: pool-a
namespace: simplyblock
spec:
clusterName: cluster-a
dhchap: true
allowedNodes:
- worker-1
- worker-2
The DH-HMAC-CHAP keys of the pool are generated as soon as dhchap is set. Authentication is only enforced once
allowedNodes is non-empty.
Both fields belong in the manifest that creates the pool. The StorageClass generated for the pool is only
restricted to the allowed nodes when dhchap is true and allowedNodes is non-empty at the moment the class is
created, and parameters and allowedTopologies cannot be patched afterward. A pool created with dhchap: true and
an empty allowedNodes therefore keeps an unrestricted StorageClass for the rest of its life, even once nodes are
added to the list. Recreating the pool is the only way to correct this.
Reconciliation by the Operator
Once the storage pool is created, host registration and node scheduling are reconciled by the operator:
- Allowed hosts: each node in
allowedNodesis registered as an allowed host of the pool, under a deterministic NQN derived from that node's Kubernetes UID (nqn.2014-08.io.simplyblock:uuid:<node-uid>). - Node labels: each allowed node is labeled
simplyblock.io/pool.<namespace>.<cluster>.<pool>=allowed, and the label is removed again from every node that leaves the list. - First scheduling decision: the generated
StorageClassis restricted to that label throughallowedTopologies, so the firstPodto consume aPersistentVolumeClaimof this pool can only be scheduled onto an allowed node. - Every later scheduling decision: the same label is written into the
nodeAffinityof thePersistentVolumewhen the volume is created, which restricts every scheduling decision on the already-bound volume, including a restart, a recreate, and a drain. - Host NQN: the node's own NQN and the pool's DHCHAP secrets are presented by the CSI node plugin on connect, so no host NQN has to be supplied anywhere in the Kubernetes flow.
Managing Allowed Nodes
dhchap is immutable, because the parameters and allowedTopologies of the generated StorageClass cannot be
patched in the Kubernetes API once it exists. allowedNodes stays mutable. Changing it relabels the nodes and updates
the pool's allowed hosts, and it never rewrites the StorageClass.
A node removed from allowedNodes loses its label, and its NQN is removed from the allowed hosts of the pool and of
every volume in it. The node is rejected on its next connect attempt. A volume already connected on that node is not
disconnected by the removal.
Verifying the Configuration
status.allowedNodes carries the node names last registered on the control plane. A difference to spec.allowedNodes
means the pool has not converged yet.
kubectl get storagepool pool-a -n simplyblock \
-o jsonpath='{.status.allowedNodes}'
The nodes carrying the pool's label are listed through a label selector. The result has to match status.allowedNodes.
kubectl get nodes \
-l simplyblock.io/pool.simplyblock.cluster-a.pool-a=allowed
Whether the generated StorageClass restricts scheduling at all is visible in its allowedTopologies. An empty result
means the class was created while allowedNodes was empty.
kubectl get storageclass simplyblock-simplyblock-cluster-a-pool-a \
-o jsonpath='{.allowedTopologies}'
Pods on a Disallowed Node
allowedTopologies and the nodeAffinity of the PersistentVolume keep a Pod off a node outside allowedNodes.
If one lands there regardless, no nvme connect is ever built. NodeStageVolume derives the host NQN of its own node
and requests the connection information from the control plane, which rejects the unknown NQN with an HTTP 404. The
Pod stays unscheduled with a FailedMount event.
MountVolume.MountDevice failed for volume "pvc-...": rpc error: code = Internal
desc = failed to fetch connection: GET 404: Host NQN
nqn.2014-08.io.simplyblock:uuid:<node-uid> not found in allowed hosts for volume <lvol-id>
The node is either missing from allowedNodes or the pool has not converged yet. Both are checked as described in
Verifying the Configuration.
See the Operator Reference for the full StoragePool field list, and
Storage Class for the dhchap_node_label parameter this generates.
For a detailed explanation of the security mechanisms and configuration, see NVMe-oF Security. The equivalent flow for a plain Linux installation is described in Host Authentication and Encryption (sbctl).