From cc3f842cd17bdbf846aeca4a1f83021f6a32c80a Mon Sep 17 00:00:00 2001 From: Konrad Sztyber Date: Mon, 1 Aug 2022 08:15:33 +0200 Subject: [PATCH] sma: initial crypto definitions This patch defines the interface for crypto engines, which provide support for configuring crypto on a given volume. Only a single crypto engine can be active at a time and it's selected in the "crypto" section of the config file. Similarly to device managers, external crypto engines can be loaded from plugins. Signed-off-by: Konrad Sztyber Change-Id: Id942ef876e070816827d7ad1937eb510a85c8f8d Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13869 Tested-by: SPDK CI Jenkins Reviewed-by: Ben Walker Reviewed-by: Jim Harris Reviewed-by: --- python/spdk/sma/__init__.py | 17 ++++-- python/spdk/sma/sma.py | 5 ++ python/spdk/sma/volume/__init__.py | 5 ++ python/spdk/sma/volume/crypto.py | 87 ++++++++++++++++++++++++++++ scripts/sma.py | 21 +++++++ test/sma/plugins.sh | 54 ++++++++++++----- test/sma/plugins/plugin1/__init__.py | 25 +++++++- test/sma/plugins/plugin2/__init__.py | 25 +++++++- 8 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 python/spdk/sma/volume/crypto.py diff --git a/python/spdk/sma/__init__.py b/python/spdk/sma/__init__.py index c686aaa75..05b034012 100644 --- a/python/spdk/sma/__init__.py +++ b/python/spdk/sma/__init__.py @@ -4,9 +4,14 @@ import sys # Fix up the import paths for the autogenerated files sys.path.append(os.path.dirname(__file__) + '/proto') -from .sma import StorageManagementAgent # noqa -from .device import DeviceException # noqa -from .device import DeviceManager # noqa -from .device import NvmfTcpDeviceManager # noqa -from .device import VhostBlkDeviceManager # noqa -from .device import NvmfVfioDeviceManager # noqa +from .sma import StorageManagementAgent # noqa +from .device import DeviceException # noqa +from .device import DeviceManager # noqa +from .device import NvmfTcpDeviceManager # noqa +from .device import VhostBlkDeviceManager # noqa +from .device import NvmfVfioDeviceManager # noqa +from .volume import CryptoEngine # noqa +from .volume import CryptoException # noqa +from .volume import set_crypto_engine # noqa +from .volume import get_crypto_engine # noqa +from .volume import register_crypto_engine # noqa diff --git a/python/spdk/sma/sma.py b/python/spdk/sma/sma.py index 5ce6b78cd..23f55e114 100644 --- a/python/spdk/sma/sma.py +++ b/python/spdk/sma/sma.py @@ -5,6 +5,7 @@ import grpc import logging from .device import DeviceException from .volume import VolumeException, VolumeManager +from .volume import crypto from .proto import sma_pb2 as pb2 from .proto import sma_pb2_grpc as pb2_grpc @@ -138,3 +139,7 @@ class StorageManagementAgent(pb2_grpc.StorageManagementAgentServicer): context.set_details(ex.message) context.set_code(ex.code) return response + + +crypto.register_crypto_engine(crypto.CryptoEngineNop()) +crypto.set_crypto_engine('nop') diff --git a/python/spdk/sma/volume/__init__.py b/python/spdk/sma/volume/__init__.py index e21ebe6c5..ce34cd053 100644 --- a/python/spdk/sma/volume/__init__.py +++ b/python/spdk/sma/volume/__init__.py @@ -1,2 +1,7 @@ from .volume import VolumeException from .volume import VolumeManager +from .crypto import CryptoEngine +from .crypto import CryptoException +from .crypto import set_crypto_engine +from .crypto import get_crypto_engine +from .crypto import register_crypto_engine diff --git a/python/spdk/sma/volume/crypto.py b/python/spdk/sma/volume/crypto.py new file mode 100644 index 000000000..f564fa18e --- /dev/null +++ b/python/spdk/sma/volume/crypto.py @@ -0,0 +1,87 @@ +import grpc +import logging + + +log = logging.getLogger(__name__) + + +class CryptoException(Exception): + def __init__(self, code, message): + self.code = code + self.message = message + + +class CryptoEngine: + def __init__(self, name): + self.name = name + + def init(self, client, params): + """Initialize crypto engine""" + self._client = client + + def setup(self, volume_id, key, cipher, key2=None): + """Set up crypto on a given volume""" + raise NotImplementedError() + + def cleanup(self, volume_id): + """ + Disable crypto on a given volume. If crypto was not configured on that volume, this method + is a no-op and shouldn't raise any exceptions. + """ + raise NotImplementedError() + + def verify(self, volume_id, key, cipher, key2=None): + """ + Verify that specified crypto parameters match those that are currently deployed on a given + volume. If key is None, this mehtod ensures that the volume doesn't use crypto. If + something is wrong (e.g. keys don't match, different cipher is used, etc.), this method + raises CryptoException. + """ + raise NotImplementedError() + + def get_crypto_bdev(self, volume_id): + """ + Return the name of a crypto bdev on a given volume. This method might return volume_id if + crypto engine doesn't create a separate crypto bdev to set up crypto. If crypto is + disabled on a given volue, this method returns None. + """ + raise NotImplementedError() + + +class CryptoEngineNop(CryptoEngine): + def __init__(self): + super().__init__('nop') + + def setup(self, volume_id, key, cipher, key2=None): + raise CryptoException(grpc.StatusCode.INVALID_ARGUMENT, 'Crypto is disabled') + + def cleanup(self, volume_id): + pass + + def verify(self, volume_id, key, cipher, key2=None): + pass + + def get_crypto_bdev(self, volume_id): + return None + + +_crypto_engine = None +_crypto_engines = {} + + +def get_crypto_engine(): + return _crypto_engine + + +def set_crypto_engine(name): + global _crypto_engine + engine = _crypto_engines.get(name) + if engine is None: + raise ValueError(f'Unknown crypto engine: {name}') + log.info(f'Setting crypto engine: {name}') + _crypto_engine = engine + + +def register_crypto_engine(engine): + global _crypto_engines + _crypto_engines[engine.name] = engine diff --git a/scripts/sma.py b/scripts/sma.py index 39f390d9e..aa3d654a8 100755 --- a/scripts/sma.py +++ b/scripts/sma.py @@ -67,6 +67,22 @@ def register_devices(agent, devices, config): agent.register_device(device_manager) +def init_crypto(config, client): + crypto_config = config.get('crypto') + if crypto_config is None: + return + name = crypto_config.get('name') + if name is None: + logging.error('Crypto engine name is missing') + sys.exit(1) + try: + sma.set_crypto_engine(name) + sma.get_crypto_engine().init(client, crypto_config.get('params', {})) + except ValueError: + logging.error(f'Invalid crypto engine: {name}') + sys.exit(1) + + def load_plugins(plugins, client): devices = [] for plugin in plugins: @@ -74,6 +90,10 @@ def load_plugins(plugins, client): for device in getattr(module, 'devices', []): logging.debug(f'Loading external device: {plugin}.{device.__name__}') devices.append(device(client)) + for engine_class in getattr(module, 'crypto_engines', []): + engine = engine_class() + logging.debug(f'Loading external crypto engine: {plugin}.{engine.name}') + sma.register_crypto_engine(engine) return devices @@ -125,5 +145,6 @@ if __name__ == '__main__': devices += load_plugins(config.get('plugins') or [], client) devices += load_plugins(filter(None, os.environ.get('SMA_PLUGINS', '').split(':')), client) + init_crypto(config, client) register_devices(agent, devices, config) run(agent) diff --git a/test/sma/plugins.sh b/test/sma/plugins.sh index 893b0a1d3..48e2dfee3 100755 --- a/test/sma/plugins.sh +++ b/test/sma/plugins.sh @@ -41,8 +41,8 @@ smapid=$! # Wait for a while to make sure SMA starts listening sma_waitforlisten -[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1' ]] -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin1-device2' ]] +[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1:nop' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin1-device2:nop' ]] killprocess $smapid @@ -58,7 +58,7 @@ PYTHONPATH=$testdir/plugins $rootdir/scripts/sma.py -c <( smapid=$! sma_waitforlisten -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin1-device2' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin1-device2:nop' ]] NOT create_device nvme killprocess $smapid @@ -77,8 +77,8 @@ PYTHONPATH=$testdir/plugins $rootdir/scripts/sma.py -c <( smapid=$! sma_waitforlisten -[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1' ]] -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin1-device2' ]] +[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1:nop' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin1-device2:nop' ]] killprocess $smapid @@ -96,8 +96,8 @@ PYTHONPATH=$testdir/plugins $rootdir/scripts/sma.py -c <( smapid=$! sma_waitforlisten -[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin2-device1' ]] -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2' ]] +[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin2-device1:nop' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2:nop' ]] killprocess $smapid @@ -115,8 +115,8 @@ PYTHONPATH=$testdir/plugins $rootdir/scripts/sma.py -c <( smapid=$! sma_waitforlisten -[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1' ]] -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2' ]] +[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1:nop' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2:nop' ]] killprocess $smapid @@ -131,12 +131,12 @@ PYTHONPATH=$testdir/plugins SMA_PLUGINS=plugin1:plugin2 $rootdir/scripts/sma.py smapid=$! sma_waitforlisten -[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1' ]] -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2' ]] +[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1:nop' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2:nop' ]] killprocess $smapid -# Finally, register one plugin in a config and the other through env var +# Register one plugin in a config and the other through env var PYTHONPATH=$testdir/plugins SMA_PLUGINS=plugin1 $rootdir/scripts/sma.py -c <( cat <<- EOF plugins: @@ -149,8 +149,34 @@ PYTHONPATH=$testdir/plugins SMA_PLUGINS=plugin1 $rootdir/scripts/sma.py -c <( smapid=$! sma_waitforlisten -[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1' ]] -[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2' ]] +[[ $(create_device nvme | jq -r '.handle') == 'nvme:plugin1-device1:nop' ]] +[[ $(create_device nvmf_tcp | jq -r '.handle') == 'nvmf_tcp:plugin2-device2:nop' ]] + +killprocess $smapid + +# Check registering external crypto engines +crypto_engines=(crypto-plugin1 crypto-plugin2) +for crypto in "${crypto_engines[@]}"; do + PYTHONPATH=$testdir/plugins $rootdir/scripts/sma.py -c <( + cat <<- EOF + plugins: + - 'plugin1' + - 'plugin2' + devices: + - name: 'plugin1-device1' + - name: 'plugin2-device2' + crypto: + name: '$crypto' + EOF + ) & + smapid=$! + sma_waitforlisten + + [[ $(create_device nvme | jq -r '.handle') == nvme:plugin1-device1:$crypto ]] + [[ $(create_device nvmf_tcp | jq -r '.handle') == nvmf_tcp:plugin2-device2:$crypto ]] + + killprocess $smapid +done cleanup trap - SIGINT SIGTERM EXIT diff --git a/test/sma/plugins/plugin1/__init__.py b/test/sma/plugins/plugin1/__init__.py index d5daf5365..fbd657778 100644 --- a/test/sma/plugins/plugin1/__init__.py +++ b/test/sma/plugins/plugin1/__init__.py @@ -1,13 +1,32 @@ from spdk.sma import DeviceManager +from spdk.sma import CryptoEngine, get_crypto_engine from spdk.sma.proto import sma_pb2 +class TestCryptoEngine(CryptoEngine): + def __init__(self): + super().__init__('crypto-plugin1') + + def setup(self, volume_id, key, cipher, key2=None): + pass + + def cleanup(self, volume_id): + pass + + def verify(self, volume_id, key, cipher, key2=None): + pass + + def get_crypto_bdev(self, volume_id): + return volume_id + + class TestDeviceManager1(DeviceManager): def __init__(self, client): super().__init__('plugin1-device1', 'nvme', client) def create_device(self, request): - return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}') + crypto = get_crypto_engine().name + return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}:{crypto}') class TestDeviceManager2(DeviceManager): @@ -15,7 +34,9 @@ class TestDeviceManager2(DeviceManager): super().__init__('plugin1-device2', 'nvmf_tcp', client) def create_device(self, request): - return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}') + crypto = get_crypto_engine().name + return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}:{crypto}') devices = [TestDeviceManager1, TestDeviceManager2] +crypto_engines = [TestCryptoEngine] diff --git a/test/sma/plugins/plugin2/__init__.py b/test/sma/plugins/plugin2/__init__.py index 8ff4165d5..83c6b52d3 100644 --- a/test/sma/plugins/plugin2/__init__.py +++ b/test/sma/plugins/plugin2/__init__.py @@ -1,13 +1,32 @@ from spdk.sma import DeviceManager +from spdk.sma import CryptoEngine, get_crypto_engine from spdk.sma.proto import sma_pb2 +class TestCryptoEngine(CryptoEngine): + def __init__(self): + super().__init__('crypto-plugin2') + + def setup(self, volume_id, key, cipher, key2=None): + pass + + def cleanup(self, volume_id): + pass + + def verify(self, volume_id, key, cipher, key2=None): + pass + + def get_crypto_bdev(self, volume_id): + return volume_id + + class TestDeviceManager1(DeviceManager): def __init__(self, client): super().__init__('plugin2-device1', 'nvme', client) def create_device(self, request): - return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}') + crypto = get_crypto_engine().name + return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}:{crypto}') class TestDeviceManager2(DeviceManager): @@ -15,7 +34,9 @@ class TestDeviceManager2(DeviceManager): super().__init__('plugin2-device2', 'nvmf_tcp', client) def create_device(self, request): - return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}') + crypto = get_crypto_engine().name + return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{self.name}:{crypto}') devices = [TestDeviceManager1, TestDeviceManager2] +crypto_engines = [TestCryptoEngine]