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 <konrad.sztyber@intel.com>
Change-Id: Id942ef876e070816827d7ad1937eb510a85c8f8d
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13869
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: <sebastian.brzezinka@intel.com>
This commit is contained in:
Konrad Sztyber 2022-08-01 08:15:33 +02:00 committed by Ben Walker
parent b30edf643d
commit cc3f842cd1
8 changed files with 215 additions and 24 deletions

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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]