diff options
Diffstat (limited to 'stasadm.py')
-rwxr-xr-x | stasadm.py | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/stasadm.py b/stasadm.py new file mode 100755 index 0000000..294fdde --- /dev/null +++ b/stasadm.py @@ -0,0 +1,202 @@ +#!/usr/bin/python3 +# Copyright (c) 2021, Dell Inc. or its subsidiaries. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# See the LICENSE file for details. +# +# This file is part of NVMe STorage Appliance Services (nvme-stas). +# +# Authors: Martin Belanger <Martin.Belanger@dell.com> +# +''' STorage Appliance Services Admin Tool ''' +import os +import sys +import uuid +import configparser +from argparse import ArgumentParser +from staslib import defs + +try: + import hmac + import hashlib +except (ImportError, ModuleNotFoundError): + hmac = None + hashlib = None + + +def read_from_file(fname, size): # pylint: disable=missing-function-docstring + try: + with open(fname) as f: # pylint: disable=unspecified-encoding + data = f.read(size) + if len(data) == size: + return data + except FileNotFoundError: + pass + + return None + + +def get_machine_app_specific(app_id): + '''@brief Get a machine ID specific to an application. We use the + value retrieved from /etc/machine-id. The documentation states that + /etc/machine-id: + "should be considered "confidential", and must not be exposed in + untrusted environments, in particular on the network. If a stable + unique identifier that is tied to the machine is needed for some + application, the machine ID or any part of it must not be used + directly. Instead the machine ID should be hashed with a crypto- + graphic, keyed hash function, using a fixed, application-specific + key. That way the ID will be properly unique, and derived in a + constant way from the machine ID but there will be no way to + retrieve the original machine ID from the application-specific one" + + @note systemd's C function sd_id128_get_machine_app_specific() was the + inspiration for this code. + + @ref https://www.freedesktop.org/software/systemd/man/machine-id.html + ''' + if not hmac: + return None + + data = read_from_file('/etc/machine-id', 32) + if not data: + return None + + hmac_obj = hmac.new(app_id, uuid.UUID(data).bytes, hashlib.sha256) + id128_bytes = hmac_obj.digest()[0:16] + return str(uuid.UUID(bytes=id128_bytes, version=4)) + + +def get_uuid_from_system(): + '''@brief Try to find system UUID in the following order: + 1) /etc/machine-id + 2) /sys/class/dmi/id/product_uuid + 3) /proc/device-tree/ibm,partition-uuid + ''' + uuid_str = get_machine_app_specific(b'$nvmexpress.org$') + if uuid_str: + return uuid_str + + # The following files are only readable by root + if os.geteuid() != 0: + sys.exit('Permission denied. Root privileges required.') + + id128 = read_from_file('/sys/class/dmi/id/product_uuid', 36) + if id128: + # Swap little-endian to network order per + # DMTF SMBIOS 3.0 Section 7.2.1 System UUID. + swapped = ''.join([id128[x] for x in (6, 7, 4, 5, 2, 3, 0, 1, 8, 11, 12, 9, 10, 13, 16, 17, 14, 15)]) + return swapped + id128[18:] + + return read_from_file('/proc/device-tree/ibm,partition-uuid', 36) + + +def save(section, option, string, conf_file, fname): + '''@brief Save configuration + + @param section: section in @conf_file where @option will be added + @param option: option to be added under @section in @conf_file + @param string: Text to be saved to @fname + @param conf_file: Configuration file name + @param fname: Optional file where @string will be saved + ''' + if fname and string is not None: + with open(fname, 'w') as f: # pylint: disable=unspecified-encoding + print(string, file=f) + + if conf_file: + config = configparser.ConfigParser( + default_section=None, allow_no_value=True, delimiters=('='), interpolation=None, strict=False + ) + if os.path.isfile(conf_file): + config.read(conf_file) + + try: + config.add_section(section) + except configparser.DuplicateSectionError: + pass + + if fname: + string = 'file://' + fname + + if string is not None: + config.set(section, option, string) + else: + config.remove_option(section, option) + + with open(conf_file, 'w') as f: # pylint: disable=unspecified-encoding + config.write(f) + + +def hostnqn(args): + '''@brief Configure the host NQN''' + uuid_str = get_uuid_from_system() or str(uuid.uuid4()) + uuid_str = f'nqn.2014-08.org.nvmexpress:uuid:{uuid_str}' + save('Host', 'nqn', uuid_str, args.conf_file, args.file) + + +def hostid(args): + '''@brief Configure the host ID''' + save('Host', 'id', str(uuid.uuid4()), args.conf_file, args.file) + + +def set_symname(args): + '''@brief Define the host Symbolic Name''' + save('Host', 'symname', args.symname, args.conf_file, args.file) + + +def clr_symname(args): + '''@brief Undefine the host NQN''' + save('Host', 'symname', None, args.conf_file, None) + + +def get_parser(): # pylint: disable=missing-function-docstring + parser = ArgumentParser(description='Configuration utility for STAS.') + parser.add_argument('-v', '--version', action='store_true', help='Print version, then exit', default=False) + parser.add_argument( + '-c', + '--conf-file', + action='store', + help='Configuration file. Default %(default)s.', + default=defs.SYS_CONF_FILE, + type=str, + metavar='FILE', + ) + + subparser = parser.add_subparsers(title='Commands') + + prsr = subparser.add_parser('hostnqn', help='Configure the host NQN. The NQN is auto-generated.') + prsr.add_argument( + '-f', '--file', action='store', help='Optional file where to save the NQN.', type=str, metavar='FILE' + ) + prsr.set_defaults(cmd=hostnqn) + + prsr = subparser.add_parser('hostid', help='Configure the host ID. The ID is auto-generated.') + prsr.add_argument( + '-f', '--file', action='store', help='Optional file where to save the ID.', type=str, metavar='FILE' + ) + prsr.set_defaults(cmd=hostid) + + prsr = subparser.add_parser('set-symname', help='Set the host symbolic') + prsr.add_argument( + '-f', '--file', action='store', help='Optional file where to save the symbolic name.', type=str, metavar='FILE' + ) + prsr.add_argument('symname', action='store', help='Symbolic name', default=None, metavar='SYMNAME') + prsr.set_defaults(cmd=set_symname) + + prsr = subparser.add_parser('clear-symname', help='Clear the host symbolic') + prsr.set_defaults(cmd=clr_symname) + + return parser + + +PARSER = get_parser() +ARGS = PARSER.parse_args() +if ARGS.version: + print(f'nvme-stas {defs.VERSION}') + sys.exit(0) + +try: + ARGS.cmd(ARGS) +except AttributeError as ex: + print(str(ex)) + PARSER.print_usage() |