diff --git a/docs/stratis.txt b/docs/stratis.txt index a60dd7cce..4d1729af9 100644 --- a/docs/stratis.txt +++ b/docs/stratis.txt @@ -41,10 +41,14 @@ pool create [--key-desc ] [--clevis <(nbde|tang|tpm2)> [--tang-url |--name )>:: Stop a pool, specifying the pool by its UUID or by its name. Tear down the storage stack but leave all metadata intact. -pool start <(--uuid |--name )> --unlock-method <(keyring | clevis)>:: +pool start [--keyfile-path KEYFILE_PATH | --capture-key] --unlock-method <(any | clevis | keyring)> <(--uuid |--name )>:: Start a pool, specifying the pool by its UUID or by its name. Use the --unlock-method option to specify method of unlocking the pool if it is - encrypted. + encrypted. If an unlock method of any is specified the pool will be + unlocked with any facility available that allows the pool to be unlocked. + If --keyfile-path or --capture-key is specified, then the pool will use + only the passphrase and method specified, so that the pool will not be + unlocked unless the passphrase is correct. pool list [--stopped] [(--uuid |--name )]:: List pools. If the --stopped option is used, list only stopped pools. Otherwise, list only started pools. If a UUID or name is specified, print diff --git a/src/stratis_cli/_actions/_pool.py b/src/stratis_cli/_actions/_pool.py index 7e3fe343f..4c14f0730 100644 --- a/src/stratis_cli/_actions/_pool.py +++ b/src/stratis_cli/_actions/_pool.py @@ -25,7 +25,7 @@ # isort: THIRDPARTY from justbytes import Range -from .._constants import Id, IdType +from .._constants import Id, IdType, UnlockMethod from .._error_codes import PoolErrorCode from .._errors import ( StratisCliEngineError, @@ -46,7 +46,7 @@ from ._constants import TOP_OBJECT from ._formatting import get_property, get_uuid_formatter from ._list_pool import list_pools -from ._utils import ClevisInfo, PoolSelector +from ._utils import ClevisInfo, PoolSelector, get_passphrase_fd def _generate_pools_to_blockdevs(managed_objects, to_be_added, tier): @@ -270,20 +270,41 @@ def start_pool(namespace): else (namespace.name, "name") ) + if namespace.capture_key or namespace.keyfile_path is not None: + fd_argument, fd_to_close = get_passphrase_fd( + keyfile_path=namespace.keyfile_path + ) + key_fd_arg = (True, fd_argument) + unlock_method = ( + True, + str( + UnlockMethod.ANY + if namespace.unlock_method is None + else namespace.unlock_method + ), + ) + else: + fd_to_close = None + key_fd_arg = (False, 0) + unlock_method = ( + (False, "") + if namespace.unlock_method is None + else (True, str(namespace.unlock_method)) + ) + ((started, _), return_code, message) = Manager.Methods.StartPool( proxy, { "id": pool_id, "id_type": id_type, - "unlock_method": ( - (False, "") - if namespace.unlock_method is None - else (True, str(namespace.unlock_method)) - ), - "key_fd": (False, 0), + "unlock_method": unlock_method, + "key_fd": key_fd_arg, }, ) + if fd_to_close is not None: + os.close(fd_to_close) + if return_code != StratisdErrors.OK: raise StratisCliEngineError(return_code, message) diff --git a/src/stratis_cli/_constants.py b/src/stratis_cli/_constants.py index e682b5bc7..b8f80e0a8 100644 --- a/src/stratis_cli/_constants.py +++ b/src/stratis_cli/_constants.py @@ -92,6 +92,19 @@ def __str__(self): return self.value +class UnlockMethod(Enum): + """ + Unlock method. + """ + + ANY = "any" + CLEVIS = "clevis" + KEYRING = "keyring" + + def __str__(self): + return self.value + + class Clevis(Enum): """ Clevis encryption methods. diff --git a/src/stratis_cli/_parser/_pool.py b/src/stratis_cli/_parser/_pool.py index c276727d4..c9ef1f2d4 100644 --- a/src/stratis_cli/_parser/_pool.py +++ b/src/stratis_cli/_parser/_pool.py @@ -21,7 +21,7 @@ from uuid import UUID from .._actions import BindActions, PoolActions -from .._constants import Clevis, EncryptionMethod, YesOrNo +from .._constants import Clevis, EncryptionMethod, UnlockMethod, YesOrNo from .._error_codes import PoolErrorCode from ._bind import BIND_SUBCMDS, REBIND_SUBCMDS from ._debug import POOL_DEBUG_SUBCMDS @@ -227,6 +227,32 @@ def _ensure_nat(arg): "start", { "help": "Start a pool.", + "groups": [ + ( + "Key Specification", + { + "description": "Arguments to allow specifying a key", + "mut_ex_args": [ + ( + False, + [ + ( + "--keyfile-path", + {"help": "Path to a key file containing a key"}, + ), + ( + "--capture-key", + { + "action": "store_true", + "help": "Read key from stdin", + }, + ), + ], + ), + ], + }, + ) + ], "mut_ex_args": [ ( True, @@ -243,15 +269,15 @@ def _ensure_nat(arg): {"help": "name of the pool to start"}, ), ], - ) + ), ], "args": [ ( "--unlock-method", { - "choices": list(EncryptionMethod), + "choices": list(UnlockMethod), "help": "Method to use to unlock the pool if encrypted.", - "type": EncryptionMethod, + "type": UnlockMethod, }, ), ], diff --git a/tests/whitebox/integration/pool/test_start.py b/tests/whitebox/integration/pool/test_start.py index 23ba48c25..de6eb94f6 100644 --- a/tests/whitebox/integration/pool/test_start.py +++ b/tests/whitebox/integration/pool/test_start.py @@ -80,3 +80,14 @@ def test_good_name(self): RUNNER(command_line) self.check_error(StratisCliNoChangeError, command_line, _ERROR) + + def test_capture_key(self): + """ + Test trying to start an unencrypted pool with a verified passphrase. + """ + command_line = ["pool", "stop", f"--name={self._POOLNAME}"] + RUNNER(command_line) + command_line = self._MENU + [f"--name={self._POOLNAME}", "--capture-key"] + self.check_error( + StratisCliEngineError, command_line, _ERROR, stdin="password\npassword\n" + )