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:
parent
b30edf643d
commit
cc3f842cd1
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
87
python/spdk/sma/volume/crypto.py
Normal file
87
python/spdk/sma/volume/crypto.py
Normal 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user