Source code for dynamodb_encryption_sdk.encrypted.resource

# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""High-level helper class to provide a familiar interface to encrypted tables."""
from functools import partial

import attr
from boto3.resources.base import ServiceResource
from boto3.resources.collection import CollectionManager

from dynamodb_encryption_sdk.internal.utils import (
    TableInfoCache,
    crypto_config_from_cache,
    decrypt_batch_get_item,
    encrypt_batch_write_item,
)
from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider
from dynamodb_encryption_sdk.structures import AttributeActions

from .item import decrypt_python_item, encrypt_python_item
from .table import EncryptedTable

try:  # Python 3.5.0 and 3.5.1 have incompatible typing modules
    from typing import Optional  # noqa pylint: disable=unused-import
except ImportError:  # pragma: no cover
    # We only actually need these imports when running the mypy checks
    pass


__all__ = ("EncryptedResource", "EncryptedTablesCollectionManager")


[docs]@attr.s(init=False) class EncryptedTablesCollectionManager(object): # pylint: disable=too-few-public-methods,too-many-instance-attributes """Tables collection manager that provides :class:`EncryptedTable` objects. https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.ServiceResource.tables :param collection: Pre-configured boto3 DynamoDB table collection manager :type collection: boto3.resources.collection.CollectionManager :param CryptographicMaterialsProvider materials_provider: Cryptographic materials provider to use :param AttributeActions attribute_actions: Table-level configuration of how to encrypt/sign attributes :param TableInfoCache table_info_cache: Local cache from which to obtain TableInfo data """ _collection = attr.ib(validator=attr.validators.instance_of(CollectionManager)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib(validator=attr.validators.instance_of(AttributeActions)) _table_info_cache = attr.ib(validator=attr.validators.instance_of(TableInfoCache)) def __init__( self, collection, # type: CollectionManager materials_provider, # type: CryptographicMaterialsProvider attribute_actions, # type: AttributeActions table_info_cache, # type: TableInfoCache ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 # https://github.com/python-attrs/attrs/issues/215 self._collection = collection self._materials_provider = materials_provider self._attribute_actions = attribute_actions self._table_info_cache = table_info_cache attr.validate(self) self.__attrs_post_init__() def __attrs_post_init__(self): """Set up the translation methods.""" self.all = partial( # attrs confuses pylint: disable=attribute-defined-outside-init self._transform_table, self._collection.all ) self.filter = partial( # attrs confuses pylint: disable=attribute-defined-outside-init self._transform_table, self._collection.filter ) self.limit = partial( # attrs confuses pylint: disable=attribute-defined-outside-init self._transform_table, self._collection.limit ) self.page_size = partial( # attrs confuses pylint: disable=attribute-defined-outside-init self._transform_table, self._collection.page_size ) def __getattr__(self, name): """Catch any method/attribute lookups that are not defined in this class and try to find them on the provided collection object. :param str name: Attribute name :returns: Result of asking the provided collection object for that attribute name :raises AttributeError: if attribute is not found on provided collection object """ return getattr(self._collection, name) def _transform_table(self, method, **kwargs): """Transform a Table from the underlying collection manager to an EncryptedTable. :param method: Method on underlying collection manager to call :type method: callable :param **kwargs: Keyword arguments to pass to ``method`` """ for table in method(**kwargs): yield EncryptedTable( table=table, materials_provider=self._materials_provider, table_info=self._table_info_cache.table_info(table.name), attribute_actions=self._attribute_actions, )
[docs]@attr.s(init=False) class EncryptedResource(object): # pylint: disable=too-few-public-methods,too-many-instance-attributes """High-level helper class to provide a familiar interface to encrypted tables. >>> import boto3 >>> from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource >>> from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider >>> resource = boto3.resource('dynamodb') >>> aws_kms_cmp = AwsKmsCryptographicMaterialsProvider('alias/MyKmsAlias') >>> encrypted_resource = EncryptedResource( ... resource=resource, ... materials_provider=aws_kms_cmp ... ) .. note:: This class provides a superset of the boto3 DynamoDB service resource API, so should work as a drop-in replacement once configured. https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#service-resource If you want to provide per-request cryptographic details, the ``batch_write_item`` and ``batch_get_item`` methods will also accept a ``crypto_config`` parameter, defining a custom :class:`CryptoConfig` instance for this request. :param resource: Pre-configured boto3 DynamoDB service resource object :type resource: boto3.resources.base.ServiceResource :param CryptographicMaterialsProvider materials_provider: Cryptographic materials provider to use :param AttributeActions attribute_actions: Table-level configuration of how to encrypt/sign attributes :param bool auto_refresh_table_indexes: Should we attempt to refresh information about table indexes? Requires ``dynamodb:DescribeTable`` permissions on each table. (default: True) """ _resource = attr.ib(validator=attr.validators.instance_of(ServiceResource)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib( validator=attr.validators.instance_of(AttributeActions), default=attr.Factory(AttributeActions) ) _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool), default=True) def __init__( self, resource, # type: ServiceResource materials_provider, # type: CryptographicMaterialsProvider attribute_actions=None, # type: Optional[AttributeActions] auto_refresh_table_indexes=True, # type: Optional[bool] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 # https://github.com/python-attrs/attrs/issues/215 if attribute_actions is None: attribute_actions = AttributeActions() self._resource = resource self._materials_provider = materials_provider self._attribute_actions = attribute_actions self._auto_refresh_table_indexes = auto_refresh_table_indexes attr.validate(self) self.__attrs_post_init__() def __attrs_post_init__(self): """Set up the table info cache, encrypted tables collection manager, and translation methods.""" self._table_info_cache = TableInfoCache( # attrs confuses pylint: disable=attribute-defined-outside-init client=self._resource.meta.client, auto_refresh_table_indexes=self._auto_refresh_table_indexes ) self._crypto_config = partial( # attrs confuses pylint: disable=attribute-defined-outside-init crypto_config_from_cache, self._materials_provider, self._attribute_actions, self._table_info_cache ) self.tables = EncryptedTablesCollectionManager( # attrs confuses pylint: disable=attribute-defined-outside-init collection=self._resource.tables, materials_provider=self._materials_provider, attribute_actions=self._attribute_actions, table_info_cache=self._table_info_cache, ) self.batch_get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init decrypt_batch_get_item, decrypt_python_item, self._crypto_config, self._resource.batch_get_item ) self.batch_write_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init encrypt_batch_write_item, encrypt_python_item, self._crypto_config, self._resource.batch_write_item ) def __getattr__(self, name): """Catch any method/attribute lookups that are not defined in this class and try to find them on the provided resource object. :param str name: Attribute name :returns: Result of asking the provided resource object for that attribute name :raises AttributeError: if attribute is not found on provided resource object """ return getattr(self._resource, name)
[docs] def Table(self, name, **kwargs): # naming chosen to align with boto3 resource name, so pylint: disable=invalid-name """Creates an EncryptedTable resource. If any of the optional configuration values are not provided, the corresponding values for this ``EncryptedResource`` will be used. https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.ServiceResource.Table :param name: The table name. :param CryptographicMaterialsProvider materials_provider: Cryptographic materials provider to use (optional) :param TableInfo table_info: Information about the target DynamoDB table (optional) :param AttributeActions attribute_actions: Table-level configuration of how to encrypt/sign attributes (optional) """ table_kwargs = dict( table=self._resource.Table(name), materials_provider=kwargs.get("materials_provider", self._materials_provider), attribute_actions=kwargs.get("attribute_actions", self._attribute_actions), auto_refresh_table_indexes=kwargs.get("auto_refresh_table_indexes", self._auto_refresh_table_indexes), table_info=self._table_info_cache.table_info(name), ) return EncryptedTable(**table_kwargs)