sma: crypto support through bdev_crypto

This patch adds CryptoEngine implementation using bdev_crypto.  Only a
single crypto drvier can be used at a time and it's configured during
startup in a config file, e.g.:

```
crypto:
  name: 'bdev_crypto'
  params:
    driver: 'crypto_aesni_mb'
```

Signed-off-by: Konrad Sztyber <konrad.sztyber@intel.com>
Change-Id: I13f4fd1227a02cf9f1bee61a1686904b43c0fc55
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13872
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-03 04:57:06 +02:00 committed by Ben Walker
parent c16dab7e37
commit 2bd552fbfe
2 changed files with 111 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import logging
from .device import DeviceException from .device import DeviceException
from .volume import VolumeException, VolumeManager from .volume import VolumeException, VolumeManager
from .volume import crypto from .volume import crypto
from .volume import crypto_bdev
from .proto import sma_pb2 as pb2 from .proto import sma_pb2 as pb2
from .proto import sma_pb2_grpc as pb2_grpc from .proto import sma_pb2_grpc as pb2_grpc
@ -142,4 +143,5 @@ class StorageManagementAgent(pb2_grpc.StorageManagementAgentServicer):
crypto.register_crypto_engine(crypto.CryptoEngineNop()) crypto.register_crypto_engine(crypto.CryptoEngineNop())
crypto.register_crypto_engine(crypto_bdev.CryptoEngineBdev())
crypto.set_crypto_engine('nop') crypto.set_crypto_engine('nop')

View File

@ -0,0 +1,109 @@
import grpc
import logging
import uuid
from spdk.rpc.client import JSONRPCException
from . import crypto
from ..common import format_volume_id
from ..proto import sma_pb2
log = logging.getLogger(__name__)
class CryptoEngineBdev(crypto.CryptoEngine):
_ciphers = {sma_pb2.VolumeCryptoParameters.AES_CBC: 'AES_CBC',
sma_pb2.VolumeCryptoParameters.AES_XTS: 'AES_XTS'}
def __init__(self):
super().__init__('bdev_crypto')
def init(self, client, params):
super().init(client, params)
driver = params.get('driver')
if driver is None:
raise ValueError('Crypto driver must be configured for bdev_crypto')
self._driver = driver
def setup(self, volume_id, key, cipher, key2=None):
try:
with self._client() as client:
cipher = self._ciphers.get(cipher)
if cipher is None:
raise crypto.CryptoException(grpc.StatusCode.INVALID_ARGUMENT,
'Invalid volume crypto configuration: bad cipher')
params = {'base_bdev_name': volume_id,
'name': str(uuid.uuid4()),
'crypto_pmd': self._driver,
'key': key,
'cipher': cipher}
if key2 is not None:
params['key2'] = key2
log.info('Creating crypto bdev: {} on volume: {}'.format(
params['name'], volume_id))
client.call('bdev_crypto_create', params)
except JSONRPCException:
raise crypto.CryptoException(grpc.StatusCode.INTERNAL,
f'Failed to setup crypto for volume: {volume_id}')
def cleanup(self, volume_id):
crypto_bdev = self.get_crypto_bdev(volume_id)
# If there's no crypto bdev set up on top of this volume, we're done
if crypto_bdev is None:
return
try:
with self._client() as client:
log.info('Deleting crypto bdev: {} from volume: {}'.format(
crypto_bdev, volume_id))
client.call('bdev_crypto_delete', {'name': crypto_bdev})
except JSONRPCException:
raise crypto.CryptoException(grpc.StatusCode.INTERNAL,
'Failed to delete crypto bdev')
def verify(self, volume_id, key, cipher, key2=None):
crypto_bdev = self._get_crypto_bdev(volume_id)
# Key being None/non-None defines whether we expect a bdev_crypto on top of a given volume
if ((key is None and crypto_bdev is not None) or (key is not None and crypto_bdev is None)):
raise crypto.CryptoException(grpc.StatusCode.INVALID_ARGUMENT,
'Invalid volume crypto configuration')
if key is None:
return
params = crypto_bdev['driver_specific']['crypto']
cipher = self._ciphers.get(cipher)
if cipher is None:
raise crypto.CryptoException(grpc.StatusCode.INVALID_ARGUMENT,
'Invalid volume crypto configuration: bad cipher')
if params['cipher'].lower() != cipher.lower():
raise crypto.CryptoException(grpc.StatusCode.INVALID_ARGUMENT,
'Invalid volume crypto configuration: bad cipher')
if params['key'].lower() != key.lower():
raise crypto.CryptoException(grpc.StatusCode.INVALID_ARGUMENT,
'Invalid volume crypto configuration: bad key')
if key2 is not None and params.get('key2', '').lower() != key2.lower():
raise crypto.CryptoException(grpc.StatusCode.INVALID_ARGUMENT,
'Invalid volume crypto configuration: bad key2')
def _get_crypto_bdev(self, volume_id):
try:
with self._client() as client:
bdevs = client.call('bdev_get_bdevs')
for bdev in [b for b in bdevs if b['product_name'] == 'crypto']:
base_name = bdev['driver_specific']['crypto']['base_bdev_name']
base_bdev = next(filter(lambda b: b['name'] == base_name, bdevs), None)
# Should never really happen, but check it just in case
if base_bdev is None:
raise crypto.CryptoException(
grpc.StatusCode.INTERNAL,
'Unexpected crypto configuration: cannot find base bdev')
if format_volume_id(base_bdev['uuid']) == volume_id:
return bdev
# There's no crypto bdev set up on top of this volume
return None
except JSONRPCException:
raise crypto.CryptoException(grpc.StatusCode.INTERNAL,
f'Failed to get bdev_crypto for volume: {volume_id}')
def get_crypto_bdev(self, volume_id):
bdev = self._get_crypto_bdev(volume_id)
if bdev is not None:
return bdev['name']
return None