Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ jobs:

- name: "INSTALL: pip packages"
run: |
pip install pylint --upgrade
pip install PyGObject --upgrade
pip install pylint
# pip install PyGObject

- name: "BUILD: [libnvme, nvme-stas]"
uses: BSFishy/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion coverage.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ persistent-connections = false
zeroconf-connections-persistence = 1:01

[Controllers]
controller = transport = tcp ; traddr = localhost ; ; ; kato=31; dhchap-ctrl-secret=not-so-secret
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/:
controller=transport=tcp;traddr=1.1.1.1
controller=transport=tcp;traddr=100.100.100.100
controller=transport=tcp;traddr=2607:f8b0:4002:c2c::71
Expand Down
15 changes: 15 additions & 0 deletions doc/standard-conf.xml
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,21 @@
</listitem>
</varlistentry>

<varlistentry id='dhchap-secret'>
<term><varname>dhchap-secret=</varname></term>
<listitem>
<para>
NVMe In-band authentication host secret (i.e. key);
needs to be in ASCII format as specified in NVMe 2.0
section 8.13.5.8 Secret representation. If this
option is not specified, the default is read
from /etc/stas/sys.conf (see the 'key' parameter
under the [Host] section). In-band authentication
is attempted when this is present.
</para>
</listitem>
</varlistentry>

<varlistentry id='dhchap-ctrl-secret'>
<term><varname>dhchap-ctrl-secret=</varname></term>
<listitem>
Expand Down
8 changes: 8 additions & 0 deletions etc/stas/stacd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@
# This forces the connection to be made on a specific interface
# instead of letting the system decide.
#
# dhchap-secret [OPTIONAL]
# NVMe In-band authentication host secret (i.e. key); needs to be
# in ASCII format as specified in NVMe 2.0 section 8.13.5.8 Secret
# representation. If this option is not specified, the default is
# read from /etc/stas/sys.conf (see the 'key' parameter under the
# [Host] section). In-band authentication is attempted when this
# is present.
#
# dhchap-ctrl-secret [OPTIONAL]
# NVMe In-band authentication controller secret (i.e. key) for
# bi-directional authentication; needs to be in ASCII format as
Expand Down
1 change: 1 addition & 0 deletions staslib/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ def get_controllers(self):
'host-traddr': [TRADDR],
'host-iface': [IFACE],
'host-nqn': [NQN],
'dhchap-secret': [KEY],
'dhchap-ctrl-secret': [KEY],
'hdr-digest': [BOOL]
'data-digest': [BOOL]
Expand Down
36 changes: 26 additions & 10 deletions staslib/ctrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,23 +221,39 @@ def _do_connect(self):
host_traddr=self.tid.host_traddr if self.tid.host_traddr else None,
host_iface=host_iface,
)
self._ctrl.discovery_ctrl_set(self._discovery_ctrl)
self._ctrl.discovery_ctrl = self._discovery_ctrl

# Set the DHCHAP key on the controller
# NOTE that this will eventually have to
# Set the DHCHAP host key on the controller
# NOTE that this may eventually have to
# change once we have support for AVE (TP8019)
ctrl_dhchap_key = self.tid.cfg.get('dhchap-ctrl-secret')
if ctrl_dhchap_key and self._nvme_options.dhchap_ctrlkey_supp:
has_dhchap_key = hasattr(self._ctrl, 'dhchap_key')
if not has_dhchap_key:
# This is used for in-band authentication
dhchap_host_key = self.tid.cfg.get('dhchap-secret')
if dhchap_host_key and self._nvme_options.dhchap_hostkey_supp:
try:
self._ctrl.dhchap_host_key = dhchap_host_key
except AttributeError:
logging.warning(
'%s | %s - libnvme-%s does not allow setting the controller DHCHAP key. Please upgrade libnvme.',
'%s | %s - libnvme-%s does not allow setting the host DHCHAP key on the controller. Please upgrade libnvme.',
self.id,
self.device,
defs.LIBNVME_VERSION,
)

# Set the DHCHAP controller key on the controller
# NOTE that this may eventually have to
# change once we have support for AVE (TP8019)
# This is used for bidirectional authentication
dhchap_ctrl_key = self.tid.cfg.get('dhchap-ctrl-secret')
if dhchap_ctrl_key and self._nvme_options.dhchap_ctrlkey_supp:
try:
self._ctrl.dhchap_key = dhchap_ctrl_key
except AttributeError:
logging.warning(
'%s | %s - libnvme-%s does not allow setting the controller DHCHAP key on the controller. Please upgrade libnvme.',
self.id,
self.device,
defs.LIBNVME_VERSION,
)
else:
self._ctrl.dhchap_key = ctrl_dhchap_key

# Audit existing nvme devices. If we find a match, then
# we'll just borrow that device instead of creating a new one.
Expand Down
2 changes: 1 addition & 1 deletion staslib/gutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def resolve_ctrl_async(self, cancellable, controllers_in: list, callback):
The callback @callback will be called once all hostnames have
been resolved.

@param controllers: List of trid.TID
@param controllers_in: List of trid.TID
'''
pending_resolution_count = 0
controllers_out = []
Expand Down
1 change: 1 addition & 0 deletions staslib/trid.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(self, cid: dict):
'host-nqn': str, # [optional]

# Connection parameters
'dhchap-secret': str, # [optional]
'dhchap-ctrl-secret': str, # [optional]
'hdr-digest': str, # [optional]
'data-digest': str, # [optional]
Expand Down
34 changes: 34 additions & 0 deletions utils/nvmet/auth.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Config file format: Python, i.e. dict(), list(), int, str, etc...
# port ids (id) are integers 0...N
# namespaces are integers 0..N
# subsysnqn can be integers or strings
{
'ports': [
{
'id': 1,
#'adrfam': 'ipv6',
#'traddr': '::',
'adrfam': 'ipv4',
'traddr': '0.0.0.0',
'trsvcid': 4420,
'trtype': 'tcp',
}
],

'subsystems': [
{
'subsysnqn': 'nqn.1988-11.com.dell:PowerSANxxx:01:20210225100113-454f73093ceb4847a7bdfc6e34ae8e28',
'port': 1,
'namespaces': [1],
'allowed_hosts': [
{
# Must match with the NQN and key configured on the host
# Key was generated with:
# nvme gen-dhchap-key ...
'nqn': 'nqn.2014-08.org.nvmexpress:uuid:46ba5037-7ce5-41fa-9452-48477bf00080',
'key': 'DHHC-1:00:2kx1hDTUPdvwtxHYUXFRl8pzn5hYZH7K3Z77IYM4hNN6/fQT:',
},
],
},
]
}
54 changes: 46 additions & 8 deletions utils/nvmet/nvmet.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,26 @@ def _get_loaded_nvmet_modules():
return output


def _runcmd(cmd: list, quiet=False):
def _runcmd(cmd: list, quiet=False, capture_output=False):
if not quiet:
print(' '.join(cmd))
if args.dry_run:
return
subprocess.run(cmd)

try:
cp = subprocess.run(cmd, capture_output=capture_output, text=True)
except TypeError:
# For older Python versions that don't support "capture_output" or "text"
cp = subprocess.run(cmd, stdout=subprocess.PIPE, universal_newlines=True)

return cp.stdout if capture_output else None


def _modprobe(module: str, args: list = None, quiet=False):
cmd = ['/usr/sbin/modprobe', module]
if args:
cmd.extend(args)
_runcmd(cmd, quiet)
_runcmd(cmd, quiet=quiet)


def _mkdir(dname: str):
Expand Down Expand Up @@ -93,12 +100,32 @@ def _symlink(port: str, subsysnqn: str):
link.symlink_to(target)


def _create_subsystem(subsysnqn: str) -> str:
def _symlink_allowed_hosts(hostnqn: str, subsysnqn: str):
print(
f'$( cd "/sys/kernel/config/nvmet/subsystems/{subsysnqn}/allowed_hosts" && ln -s "../../../hosts/{hostnqn}" "{hostnqn}" )'
)
if args.dry_run:
return
target = os.path.join('/sys/kernel/config/nvmet/hosts', hostnqn)
link = pathlib.Path(os.path.join('/sys/kernel/config/nvmet/subsystems', subsysnqn, 'allowed_hosts', hostnqn))
link.symlink_to(target)


def _create_subsystem(subsysnqn: str, allowed_hosts: list) -> str:
print(f'###{Fore.GREEN} Create subsystem: {subsysnqn}{Style.RESET_ALL}')
dname = os.path.join('/sys/kernel/config/nvmet/subsystems/', subsysnqn)
_mkdir(dname)
_echo(1, os.path.join(dname, 'attr_allow_any_host'))
return dname
_echo(0 if allowed_hosts else 1, os.path.join(dname, 'attr_allow_any_host'))

# Configure all the hosts that are allowed to access this subsystem
for host in allowed_hosts:
hostnqn = host.get('nqn')
hostkey = host.get('key')
if all([hostnqn, hostkey]):
dname = os.path.join('/sys/kernel/config/nvmet/hosts/', hostnqn)
_mkdir(dname)
_echo(hostkey, os.path.join(dname, 'dhchap_key'))
_symlink_allowed_hosts(hostnqn, subsysnqn)


def _create_namespace(subsysnqn: str, id: str, node: str) -> str:
Expand All @@ -107,7 +134,6 @@ def _create_namespace(subsysnqn: str, id: str, node: str) -> str:
_mkdir(dname)
_echo(node, os.path.join(dname, 'device_path'))
_echo(1, os.path.join(dname, 'enable'))
return dname


def _args_valid(id, traddr, trsvcid, trtype, adrfam):
Expand Down Expand Up @@ -215,8 +241,9 @@ def create(args):
str(subsystem.get('port')),
subsystem.get('namespaces'),
)

if None not in (subsysnqn, port, namespaces):
_create_subsystem(subsysnqn)
_create_subsystem(subsysnqn, subsystem.get('allowed_hosts', []))
for id in namespaces:
_create_namespace(subsysnqn, str(id), dev_node)
else:
Expand All @@ -235,10 +262,16 @@ def clean(args):
if not args.dry_run and os.geteuid() != 0:
sys.exit(f'Permission denied. You need root privileges to run {os.path.basename(__file__)}.')

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

print('rm -f /sys/kernel/config/nvmet/subsystems/*/allowed_hosts/*')
for dname in pathlib.Path('/sys/kernel/config/nvmet/subsystems').glob('*/allowed_hosts/*'):
_runcmd(['rm', '-f', str(dname)], quiet=True)

print(f'###{Fore.GREEN} 2nd) Remove directories{Style.RESET_ALL}')
print('rmdir /sys/kernel/config/nvmet/ports/*')
for dname in pathlib.Path('/sys/kernel/config/nvmet/ports').glob('*'):
_runcmd(['rmdir', str(dname)], quiet=True)
Expand All @@ -251,6 +284,11 @@ def clean(args):
for dname in pathlib.Path('/sys/kernel/config/nvmet/subsystems').glob('*'):
_runcmd(['rmdir', str(dname)], quiet=True)

print('rmdir /sys/kernel/config/nvmet/hosts/*')
for dname in pathlib.Path('/sys/kernel/config/nvmet/hosts').glob('*'):
_runcmd(['rmdir', str(dname)], quiet=True)

print(f'###{Fore.GREEN} 3rd) Unload kernel modules{Style.RESET_ALL}')
for module in _get_loaded_nvmet_modules():
_modprobe(module, ['--remove'])

Expand Down