Commit 2bd552fb authored by Konrad Sztyber's avatar Konrad Sztyber Committed by Ben Walker
Browse files

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: default avatarKonrad Sztyber <konrad.sztyber@intel.com>
Change-Id: I13f4fd1227a02cf9f1bee61a1686904b43c0fc55
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13872


Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: default avatarBen Walker <benjamin.walker@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
Reviewed-by: default avatar <sebastian.brzezinka@intel.com>
parent c16dab7e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import logging
from .device import DeviceException
from .volume import VolumeException, VolumeManager
from .volume import crypto
from .volume import crypto_bdev
from .proto import sma_pb2 as pb2
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_bdev.CryptoEngineBdev())
crypto.set_crypto_engine('nop')
+109 −0
Original line number Diff line number Diff line
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