Skip to content

Commit 1efaec9

Browse files
author
Martin Belanger
committed
stacd: add authentication support (DHCHAP)
Signed-off-by: Martin Belanger <[email protected]>
1 parent 235027b commit 1efaec9

10 files changed

Lines changed: 135 additions & 22 deletions

File tree

.github/workflows/pylint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ jobs:
5959
6060
- name: "INSTALL: pip packages"
6161
run: |
62-
pip install pylint --upgrade
63-
pip install PyGObject --upgrade
62+
pip install pylint
63+
# pip install PyGObject
6464

6565
- name: "BUILD: [libnvme, nvme-stas]"
6666
uses: BSFishy/[email protected]

coverage.sh.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ persistent-connections = false
377377
zeroconf-connections-persistence = 1:01
378378
379379
[Controllers]
380-
controller = transport = tcp ; traddr = localhost ; ; ; kato=31; dhchap-ctrl-secret=not-so-secret
380+
controller = transport = tcp ; traddr = localhost ; ; ; kato=31; dhchap-ctrl-secret=DHHC-1:00:not-so-secret/not-so-secret/not-so-secret/not-so: ; dhchap-secret=DHHC-1:00:very-secret/very-secret/very-secret/very-secret/:
381381
controller=transport=tcp;traddr=1.1.1.1
382382
controller=transport=tcp;traddr=100.100.100.100
383383
controller=transport=tcp;traddr=2607:f8b0:4002:c2c::71

doc/standard-conf.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,21 @@
376376
</listitem>
377377
</varlistentry>
378378

379+
<varlistentry id='dhchap-secret'>
380+
<term><varname>dhchap-secret=</varname></term>
381+
<listitem>
382+
<para>
383+
NVMe In-band authentication host secret (i.e. key);
384+
needs to be in ASCII format as specified in NVMe 2.0
385+
section 8.13.5.8 Secret representation. If this
386+
option is not specified, the default is read
387+
from /etc/stas/sys.conf (see the 'key' parameter
388+
under the [Host] section). In-band authentication
389+
is attempted when this is present.
390+
</para>
391+
</listitem>
392+
</varlistentry>
393+
379394
<varlistentry id='dhchap-ctrl-secret'>
380395
<term><varname>dhchap-ctrl-secret=</varname></term>
381396
<listitem>

etc/stas/stacd.conf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,14 @@
235235
# This forces the connection to be made on a specific interface
236236
# instead of letting the system decide.
237237
#
238+
# dhchap-secret [OPTIONAL]
239+
# NVMe In-band authentication host secret (i.e. key); needs to be
240+
# in ASCII format as specified in NVMe 2.0 section 8.13.5.8 Secret
241+
# representation. If this option is not specified, the default is
242+
# read from /etc/stas/sys.conf (see the 'key' parameter under the
243+
# [Host] section). In-band authentication is attempted when this
244+
# is present.
245+
#
238246
# dhchap-ctrl-secret [OPTIONAL]
239247
# NVMe In-band authentication controller secret (i.e. key) for
240248
# bi-directional authentication; needs to be in ASCII format as

staslib/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ def get_controllers(self):
341341
'host-traddr': [TRADDR],
342342
'host-iface': [IFACE],
343343
'host-nqn': [NQN],
344+
'dhchap-secret': [KEY],
344345
'dhchap-ctrl-secret': [KEY],
345346
'hdr-digest': [BOOL]
346347
'data-digest': [BOOL]

staslib/ctrl.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -221,23 +221,39 @@ def _do_connect(self):
221221
host_traddr=self.tid.host_traddr if self.tid.host_traddr else None,
222222
host_iface=host_iface,
223223
)
224-
self._ctrl.discovery_ctrl_set(self._discovery_ctrl)
224+
self._ctrl.discovery_ctrl = self._discovery_ctrl
225225

226-
# Set the DHCHAP key on the controller
227-
# NOTE that this will eventually have to
226+
# Set the DHCHAP host key on the controller
227+
# NOTE that this may eventually have to
228228
# change once we have support for AVE (TP8019)
229-
ctrl_dhchap_key = self.tid.cfg.get('dhchap-ctrl-secret')
230-
if ctrl_dhchap_key and self._nvme_options.dhchap_ctrlkey_supp:
231-
has_dhchap_key = hasattr(self._ctrl, 'dhchap_key')
232-
if not has_dhchap_key:
229+
# This is used for in-band authentication
230+
dhchap_host_key = self.tid.cfg.get('dhchap-secret')
231+
if dhchap_host_key and self._nvme_options.dhchap_hostkey_supp:
232+
try:
233+
self._ctrl.dhchap_host_key = dhchap_host_key
234+
except AttributeError:
233235
logging.warning(
234-
'%s | %s - libnvme-%s does not allow setting the controller DHCHAP key. Please upgrade libnvme.',
236+
'%s | %s - libnvme-%s does not allow setting the host DHCHAP key on the controller. Please upgrade libnvme.',
237+
self.id,
238+
self.device,
239+
defs.LIBNVME_VERSION,
240+
)
241+
242+
# Set the DHCHAP controller key on the controller
243+
# NOTE that this may eventually have to
244+
# change once we have support for AVE (TP8019)
245+
# This is used for bidirectional authentication
246+
dhchap_ctrl_key = self.tid.cfg.get('dhchap-ctrl-secret')
247+
if dhchap_ctrl_key and self._nvme_options.dhchap_ctrlkey_supp:
248+
try:
249+
self._ctrl.dhchap_key = dhchap_ctrl_key
250+
except AttributeError:
251+
logging.warning(
252+
'%s | %s - libnvme-%s does not allow setting the controller DHCHAP key on the controller. Please upgrade libnvme.',
235253
self.id,
236254
self.device,
237255
defs.LIBNVME_VERSION,
238256
)
239-
else:
240-
self._ctrl.dhchap_key = ctrl_dhchap_key
241257

242258
# Audit existing nvme devices. If we find a match, then
243259
# we'll just borrow that device instead of creating a new one.

staslib/gutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def resolve_ctrl_async(self, cancellable, controllers_in: list, callback):
123123
The callback @callback will be called once all hostnames have
124124
been resolved.
125125
126-
@param controllers: List of trid.TID
126+
@param controllers_in: List of trid.TID
127127
'''
128128
pending_resolution_count = 0
129129
controllers_out = []

staslib/trid.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def __init__(self, cid: dict):
3333
'host-nqn': str, # [optional]
3434
3535
# Connection parameters
36+
'dhchap-secret': str, # [optional]
3637
'dhchap-ctrl-secret': str, # [optional]
3738
'hdr-digest': str, # [optional]
3839
'data-digest': str, # [optional]

utils/nvmet/auth.conf

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Config file format: Python, i.e. dict(), list(), int, str, etc...
2+
# port ids (id) are integers 0...N
3+
# namespaces are integers 0..N
4+
# subsysnqn can be integers or strings
5+
{
6+
'ports': [
7+
{
8+
'id': 1,
9+
#'adrfam': 'ipv6',
10+
#'traddr': '::',
11+
'adrfam': 'ipv4',
12+
'traddr': '0.0.0.0',
13+
'trsvcid': 4420,
14+
'trtype': 'tcp',
15+
}
16+
],
17+
18+
'subsystems': [
19+
{
20+
'subsysnqn': 'nqn.1988-11.com.dell:PowerSANxxx:01:20210225100113-454f73093ceb4847a7bdfc6e34ae8e28',
21+
'port': 1,
22+
'namespaces': [1],
23+
'allowed_hosts': [
24+
{
25+
# Must match with the NQN and key configured on the host
26+
# Key was generated with:
27+
# nvme gen-dhchap-key ...
28+
'nqn': 'nqn.2014-08.org.nvmexpress:uuid:46ba5037-7ce5-41fa-9452-48477bf00080',
29+
'key': 'DHHC-1:00:2kx1hDTUPdvwtxHYUXFRl8pzn5hYZH7K3Z77IYM4hNN6/fQT:',
30+
},
31+
],
32+
},
33+
]
34+
}

utils/nvmet/nvmet.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,26 @@ def _get_loaded_nvmet_modules():
5252
return output
5353

5454

55-
def _runcmd(cmd: list, quiet=False):
55+
def _runcmd(cmd: list, quiet=False, capture_output=False):
5656
if not quiet:
5757
print(' '.join(cmd))
5858
if args.dry_run:
5959
return
60-
subprocess.run(cmd)
60+
61+
try:
62+
cp = subprocess.run(cmd, capture_output=capture_output, text=True)
63+
except TypeError:
64+
# For older Python versions that don't support "capture_output" or "text"
65+
cp = subprocess.run(cmd, stdout=subprocess.PIPE, universal_newlines=True)
66+
67+
return cp.stdout if capture_output else None
6168

6269

6370
def _modprobe(module: str, args: list = None, quiet=False):
6471
cmd = ['/usr/sbin/modprobe', module]
6572
if args:
6673
cmd.extend(args)
67-
_runcmd(cmd, quiet)
74+
_runcmd(cmd, quiet=quiet)
6875

6976

7077
def _mkdir(dname: str):
@@ -93,12 +100,32 @@ def _symlink(port: str, subsysnqn: str):
93100
link.symlink_to(target)
94101

95102

96-
def _create_subsystem(subsysnqn: str) -> str:
103+
def _symlink_allowed_hosts(hostnqn: str, subsysnqn: str):
104+
print(
105+
f'$( cd "/sys/kernel/config/nvmet/subsystems/{subsysnqn}/allowed_hosts" && ln -s "../../../hosts/{hostnqn}" "{hostnqn}" )'
106+
)
107+
if args.dry_run:
108+
return
109+
target = os.path.join('/sys/kernel/config/nvmet/hosts', hostnqn)
110+
link = pathlib.Path(os.path.join('/sys/kernel/config/nvmet/subsystems', subsysnqn, 'allowed_hosts', hostnqn))
111+
link.symlink_to(target)
112+
113+
114+
def _create_subsystem(subsysnqn: str, allowed_hosts: list) -> str:
97115
print(f'###{Fore.GREEN} Create subsystem: {subsysnqn}{Style.RESET_ALL}')
98116
dname = os.path.join('/sys/kernel/config/nvmet/subsystems/', subsysnqn)
99117
_mkdir(dname)
100-
_echo(1, os.path.join(dname, 'attr_allow_any_host'))
101-
return dname
118+
_echo(0 if allowed_hosts else 1, os.path.join(dname, 'attr_allow_any_host'))
119+
120+
# Configure all the hosts that are allowed to access this subsystem
121+
for host in allowed_hosts:
122+
hostnqn = host.get('nqn')
123+
hostkey = host.get('key')
124+
if all([hostnqn, hostkey]):
125+
dname = os.path.join('/sys/kernel/config/nvmet/hosts/', hostnqn)
126+
_mkdir(dname)
127+
_echo(hostkey, os.path.join(dname, 'dhchap_key'))
128+
_symlink_allowed_hosts(hostnqn, subsysnqn)
102129

103130

104131
def _create_namespace(subsysnqn: str, id: str, node: str) -> str:
@@ -107,7 +134,6 @@ def _create_namespace(subsysnqn: str, id: str, node: str) -> str:
107134
_mkdir(dname)
108135
_echo(node, os.path.join(dname, 'device_path'))
109136
_echo(1, os.path.join(dname, 'enable'))
110-
return dname
111137

112138

113139
def _args_valid(id, traddr, trsvcid, trtype, adrfam):
@@ -215,8 +241,9 @@ def create(args):
215241
str(subsystem.get('port')),
216242
subsystem.get('namespaces'),
217243
)
244+
218245
if None not in (subsysnqn, port, namespaces):
219-
_create_subsystem(subsysnqn)
246+
_create_subsystem(subsysnqn, subsystem.get('allowed_hosts', []))
220247
for id in namespaces:
221248
_create_namespace(subsysnqn, str(id), dev_node)
222249
else:
@@ -235,10 +262,16 @@ def clean(args):
235262
if not args.dry_run and os.geteuid() != 0:
236263
sys.exit(f'Permission denied. You need root privileges to run {os.path.basename(__file__)}.')
237264

265+
print(f'###{Fore.GREEN} 1st) Remove the symlinks{Style.RESET_ALL}')
238266
print('rm -f /sys/kernel/config/nvmet/ports/*/subsystems/*')
239267
for dname in pathlib.Path('/sys/kernel/config/nvmet/ports').glob('*/subsystems/*'):
240268
_runcmd(['rm', '-f', str(dname)], quiet=True)
241269

270+
print('rm -f /sys/kernel/config/nvmet/subsystems/*/allowed_hosts/*')
271+
for dname in pathlib.Path('/sys/kernel/config/nvmet/subsystems').glob('*/allowed_hosts/*'):
272+
_runcmd(['rm', '-f', str(dname)], quiet=True)
273+
274+
print(f'###{Fore.GREEN} 2nd) Remove directories{Style.RESET_ALL}')
242275
print('rmdir /sys/kernel/config/nvmet/ports/*')
243276
for dname in pathlib.Path('/sys/kernel/config/nvmet/ports').glob('*'):
244277
_runcmd(['rmdir', str(dname)], quiet=True)
@@ -251,6 +284,11 @@ def clean(args):
251284
for dname in pathlib.Path('/sys/kernel/config/nvmet/subsystems').glob('*'):
252285
_runcmd(['rmdir', str(dname)], quiet=True)
253286

287+
print('rmdir /sys/kernel/config/nvmet/hosts/*')
288+
for dname in pathlib.Path('/sys/kernel/config/nvmet/hosts').glob('*'):
289+
_runcmd(['rmdir', str(dname)], quiet=True)
290+
291+
print(f'###{Fore.GREEN} 3rd) Unload kernel modules{Style.RESET_ALL}')
254292
for module in _get_loaded_nvmet_modules():
255293
_modprobe(module, ['--remove'])
256294

0 commit comments

Comments
 (0)