|
| 1 | +# How to Set Up TLS for NVMe-TCP |
| 2 | + |
| 3 | +Enabling TLS for the NVMe-TCP transport requires a few configuration |
| 4 | +steps for both the kernel and userland. |
| 5 | + |
| 6 | +## Kernel Configuration |
| 7 | + |
| 8 | +To support TCP authentication and TLS encryption, enable the following |
| 9 | +kernel options: |
| 10 | + |
| 11 | +- For DHCHAP authentication: |
| 12 | + `CONFIG_NVME_HOST_AUTH` |
| 13 | + |
| 14 | +- For TLS transport encryption: |
| 15 | + `CONFIG_NVME_TCP_TLS` |
| 16 | + |
| 17 | +These configuration option depend on another config option but these |
| 18 | +will be auto selected. |
| 19 | + |
| 20 | +## Userland |
| 21 | + |
| 22 | +For the userland configuration two components need to be configured. |
| 23 | +First, the tlshd TLS handshake daemon needs to be running and TLS keys |
| 24 | +need to be loaded into the kernel keystore. |
| 25 | + |
| 26 | +### Setting Up `tlshd` |
| 27 | + |
| 28 | +For TLS protocol support, which handles authentication and encryption, |
| 29 | +the kernel handles data encryption only, so userland support is required |
| 30 | +for the TLS handshake. The `tlshd` daemon implements the handshake |
| 31 | +process. |
| 32 | + |
| 33 | +#### Requirements |
| 34 | + |
| 35 | +Ensure `tlshd` includes the commit `311d9438b984` ("tlshd: always link |
| 36 | +.nvme default keyring into the session") - likely in `ktls-utils` version |
| 37 | +0.12. Alternatively, you can set the keyring manually in |
| 38 | +`/etc/tlshd.conf`: |
| 39 | + |
| 40 | +```ini |
| 41 | +[authenticate] |
| 42 | +keyrings = .nvme |
| 43 | +``` |
| 44 | + |
| 45 | +#### Enable/start tlshd |
| 46 | + |
| 47 | +No additional configuration is necessary for `tlshd`; simply start it as |
| 48 | +a daemon: |
| 49 | + |
| 50 | +```bash |
| 51 | +systemctl enable --now tlshd |
| 52 | +``` |
| 53 | + |
| 54 | +### Loading Keys on Boot or Module Load |
| 55 | + |
| 56 | +When the kernel is establishing a TCP connection with TLS, the NVMe |
| 57 | +subsystem loads keys from the kernel keystore. This means these keys |
| 58 | +must be available in the keystore before establishing a connection. |
| 59 | + |
| 60 | +nvme-cli provides command line interfaces to create, import and export |
| 61 | +keys into the kernel keystore. Though it's not the only way to |
| 62 | +import/export keys. If there is another system component managing the |
| 63 | +keys, the following steps for creating and making the keys persistent |
| 64 | +over boot cycles are not necessary. |
| 65 | + |
| 66 | +To stress this point, the nvme-cli is explicitly trying to avoid handling |
| 67 | +the keys, the only requirement is that the keys are present in the |
| 68 | +keystore. |
| 69 | + |
| 70 | +#### Creating a New Key |
| 71 | + |
| 72 | +```bash |
| 73 | +nvme gen-tls-key \ |
| 74 | + --hostnqn nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36 \ |
| 75 | + --subsysnqn nqn.io-1 --hmac 1 --identity 1 --insert --keyfile /etc/nvme/tls-keys |
| 76 | +``` |
| 77 | + |
| 78 | +This command creates a new host key, inserts it into the kernel keyring, |
| 79 | +and appends the derived TLS PSK to the keyfile (`/etc/nvme/tls-keys`). |
| 80 | + |
| 81 | +### Inserting an Existing Key |
| 82 | + |
| 83 | +```bash |
| 84 | +nvme check-tls-key \ |
| 85 | + --hostnqn nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36 \ |
| 86 | + --subsysnqn nqn.io-1 --identity 1 \ |
| 87 | + --keydata NVMeTLSkey-1:01:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtVQoZ: \ |
| 88 | + --insert --keyfile /etc/nvme/tls-keys |
| 89 | +``` |
| 90 | + |
| 91 | +This command inserts the configured key (`--keydata`) into the kernel |
| 92 | +keyring and appends the derived TLS PSK to the keyfile. |
| 93 | + |
| 94 | +### Loading Keys on Boot or Module Load |
| 95 | + |
| 96 | +The kernel keyring does not persist keys, so userland must import keys |
| 97 | +into the keyring upon each boot or module load (for NVMe-TCP). The |
| 98 | +nvme-tcp module provides the `psk` type keystore, thus only when the |
| 99 | +nvme-tcp module is available it possible to load keys into the keystore: |
| 100 | + |
| 101 | +```bash |
| 102 | +nvme tls --import --keyfile /etc/nvme/tls-keys |
| 103 | +``` |
| 104 | + |
| 105 | +The `70-nvmf-keys.rules` udev rule |
| 106 | +([source](https://github.com/linux-nvme/nvme-cli/blob/master/nvmf-autoconnect/udev-rules/70-nvmf-keys.rules.in)) |
| 107 | +will load keys from `/etc/nvme/tls-keys` automatically. |
| 108 | + |
| 109 | +```udev |
| 110 | +ACTION=="add", SUBSYSTEM=="module", KERNEL=="nvme_tcp", TEST=="@SYSCONFDIR@/tls-keys", RUN+="@SBINDIR@/nvme tls --import --keyfile @SYSCONFDIR@/tls-keys" |
| 111 | +``` |
| 112 | + |
| 113 | +### Recommendation for Handling TLS Keys |
| 114 | + |
| 115 | +The `nvme connect` command also allows passing a TLS key directly via the |
| 116 | +command line or a JSON config file. Avoid this method in production |
| 117 | +environments, as it may expose keys. |
| 118 | + |
| 119 | +### Establishing a Connection |
| 120 | + |
| 121 | +Once the keys are in the keystore, add the `--tls` option to establish a |
| 122 | +secure connection: |
| 123 | + |
| 124 | +```bash |
| 125 | +nvme connect --transport tcp --traddr 192.168.154.148 --trsvcid 4420 \ |
| 126 | + --hostnqn nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36 \ |
| 127 | + --hostid befdec4c-2234-11b2-a85c-ca77c773af36 \ |
| 128 | + --nqn nqn.io-1 --tls --dump-config --output-format json |
| 129 | +``` |
| 130 | + |
| 131 | +The resulting JSON output can be saved to simplify future connections: |
| 132 | + |
| 133 | +```json |
| 134 | +[ |
| 135 | + { |
| 136 | + "hostnqn": "nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36", |
| 137 | + "hostid": "befdec4c-2234-11b2-a85c-ca77c773af36", |
| 138 | + "subsystems": [ |
| 139 | + { |
| 140 | + "nqn": "nqn.io-1", |
| 141 | + "ports": [ |
| 142 | + { |
| 143 | + "transport": "tcp", |
| 144 | + "traddr": "192.168.154.148", |
| 145 | + "trsvcid": "4420", |
| 146 | + "dhchap_key": "none", |
| 147 | + "tls": true |
| 148 | + } |
| 149 | + ] |
| 150 | + } |
| 151 | + ] |
| 152 | + } |
| 153 | +] |
| 154 | +``` |
| 155 | + |
| 156 | +Using this JSON file, you can connect with: |
| 157 | + |
| 158 | +```bash |
| 159 | +nvme connect --config config.json |
| 160 | +``` |
| 161 | + |
| 162 | +## Setting Up the Target |
| 163 | + |
| 164 | +The same steps for creating keys and importing/exporting keys to/from the |
| 165 | +kernel are necessary for the target as they are for the host (see above). |
| 166 | + |
| 167 | +For the above example, you can use the `nvmetcli` config: |
| 168 | + |
| 169 | +```json |
| 170 | +{ |
| 171 | + "hosts": [ |
| 172 | + { |
| 173 | + "nqn": "nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36" |
| 174 | + } |
| 175 | + ], |
| 176 | + "ports": [ |
| 177 | + { |
| 178 | + "addr": { |
| 179 | + "adrfam": "ipv4", |
| 180 | + "traddr": "0.0.0.0", |
| 181 | + "treq": "not specified", |
| 182 | + "trsvcid": "4420", |
| 183 | + "trtype": "tcp", |
| 184 | + "tsas": "tls1.3" |
| 185 | + }, |
| 186 | + "ana_groups": [ |
| 187 | + { |
| 188 | + "ana": { |
| 189 | + "state": "optimized" |
| 190 | + }, |
| 191 | + "grpid": 1 |
| 192 | + } |
| 193 | + ], |
| 194 | + "param": { |
| 195 | + "inline_data_size": "16384", |
| 196 | + "pi_enable": "0" |
| 197 | + }, |
| 198 | + "portid": 0, |
| 199 | + "referrals": [], |
| 200 | + "subsystems": [ |
| 201 | + "nqn.io-1" |
| 202 | + ] |
| 203 | + } |
| 204 | + ], |
| 205 | + "subsystems": [ |
| 206 | + { |
| 207 | + "allowed_hosts": [ |
| 208 | + "nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36" |
| 209 | + ], |
| 210 | + "attr": { |
| 211 | + "allow_any_host": "0", |
| 212 | + "cntlid_max": "65519", |
| 213 | + "cntlid_min": "1", |
| 214 | + "firmware": "6.8.0-rc", |
| 215 | + "ieee_oui": "0x000000", |
| 216 | + "model": "Linux", |
| 217 | + "pi_enable": "0", |
| 218 | + "qid_max": "128", |
| 219 | + "serial": "0c74361069d9db6c65ef", |
| 220 | + "version": "1.3" |
| 221 | + }, |
| 222 | + "namespaces": [ |
| 223 | + { |
| 224 | + "ana": { |
| 225 | + "grpid": "1" |
| 226 | + }, |
| 227 | + "ana_grpid": 1, |
| 228 | + "device": { |
| 229 | + "nguid": "00000000-0000-0000-0000-000000000000", |
| 230 | + "path": "/dev/vdb", |
| 231 | + "uuid": "91fdba0d-f87b-4c25-b80f-db7be1418b9e" |
| 232 | + }, |
| 233 | + "enable": 1, |
| 234 | + "nsid": 1 |
| 235 | + } |
| 236 | + ], |
| 237 | + "nqn": "nqn.io-1" |
| 238 | + } |
| 239 | + ] |
| 240 | +} |
| 241 | +``` |
| 242 | + |
| 243 | +## Debugging tips |
| 244 | + |
| 245 | +- Increase the debug log output in tlshd: |
| 246 | +```ini |
| 247 | +[debug] |
| 248 | +loglevel=9 |
| 249 | +``` |
| 250 | + |
| 251 | +- To verify if any key is present you can look at the `/proc/keys` output: |
| 252 | +```bash |
| 253 | +cat /proc/keys | grep -i nvme |
| 254 | +``` |
| 255 | + |
| 256 | +- The keys description is the key identifier and is defined in the TCP |
| 257 | +transport specification (see the 'TLS PSK and PSK Identity Derivation' |
| 258 | +section). The format is `NVMe<version>R<hmac> <hostnqn> <subsynqn> <PSK digest>` |
| 259 | + |
| 260 | +- The exported keys in the /etc/nvme/tls-keys file are one per line and |
| 261 | +the lines are formatted as `<identity> <PSK in interchange format>`. The |
| 262 | +`<PSK>` is the derive TLS PSK and not the retained nor the configured PSK. |
| 263 | + |
| 264 | +- If several keys available in the keystore which match up to the `<PSK digest>` |
| 265 | +the first match will be used. If this is the wrong key, it can be revoked by |
| 266 | +```bash |
| 267 | +nvme tls --revoke <identity> |
| 268 | +``` |
| 269 | + |
| 270 | +- It's possible to provide a TLS key directly via the `nvme connect --tls |
| 271 | +--tls-key` command. If only the key is provided, nvme-cli assumes it is a |
| 272 | +configured PSK and thus does all the key transformation and creates the |
| 273 | +identity automatically. If the `--tls-key-identity` is also present |
| 274 | +nvme-cli assumes it is a derived TLS PSK and does not attempt |
| 275 | +transformation on it and inserts the key directly into the keystore. |
| 276 | + |
| 277 | +- When the `nvme connect --tls-key` command is used, the `-vv` options |
| 278 | +will show the connect arguments passed to the kernel, including the key |
| 279 | +id numbers. These are in hex format and match with the output from |
| 280 | +`/proc/keys`. |
0 commit comments