示例#1
0
def encrypt_item(table_name, aws_cmk_id):
    """Demonstrate use of EncryptedTable to transparently encrypt an item."""
    index_key = {
        'partition_attribute': 'is this',
        'sort_attribute': 55
    }
    plaintext_item = {
        'example': 'data',
        'some numbers': 99,
        'and some binary': Binary(b'\x00\x01\x02'),
        'leave me': 'alone'  # We want to ignore this attribute
    }
    # Collect all of the attributes that will be encrypted (used later).
    encrypted_attributes = set(plaintext_item.keys())
    encrypted_attributes.remove('leave me')
    # Collect all of the attributes that will not be encrypted (used later).
    unencrypted_attributes = set(index_key.keys())
    unencrypted_attributes.add('leave me')
    # Add the index pairs to the item.
    plaintext_item.update(index_key)

    # Create a normal table resource.
    table = boto3.resource('dynamodb').Table(table_name)

    # Use the TableInfo helper to collect information about the indexes.
    table_info = TableInfo(name=table_name)
    table_info.refresh_indexed_attributes(table.meta.client)

    # Create a crypto materials provider using the specified AWS KMS key.
    aws_kms_cmp = AwsKmsCryptographicMaterialsProvider(key_id=aws_cmk_id)

    encryption_context = EncryptionContext(
        table_name=table_name,
        partition_key_name=table_info.primary_index.partition,
        sort_key_name=table_info.primary_index.sort,
        # The only attributes that are used by the AWS KMS cryptographic materials providers
        # are the primary index attributes.
        # These attributes need to be in the form of a DynamoDB JSON structure, so first
        # convert the standard dictionary.
        attributes=dict_to_ddb(index_key)
    )

    # Create attribute actions that tells the encrypted table to encrypt all attributes,
    # only sign the primary index attributes, and ignore the one identified attribute to
    # ignore.
    actions = AttributeActions(
        default_action=CryptoAction.ENCRYPT_AND_SIGN,
        attribute_actions={'leave me': CryptoAction.DO_NOTHING}
    )
    actions.set_index_keys(*table_info.protected_index_keys())

    # Build the crypto config to use for this item.
    # When using the higher-level helpers, this is handled for you.
    crypto_config = CryptoConfig(
        materials_provider=aws_kms_cmp,
        encryption_context=encryption_context,
        attribute_actions=actions
    )

    # Encrypt the plaintext item directly
    encrypted_item = encrypt_python_item(plaintext_item, crypto_config)

    # You could now put the encrypted item to DynamoDB just as you would any other item.
    # table.put_item(Item=encrypted_item)
    # We will skip this for the purposes of this example.

    # Decrypt the encrypted item directly
    decrypted_item = decrypt_python_item(encrypted_item, crypto_config)

    # Verify that all of the attributes are different in the encrypted item
    for name in encrypted_attributes:
        assert encrypted_item[name] != plaintext_item[name]
        assert decrypted_item[name] == plaintext_item[name]

    # Verify that all of the attributes that should not be encrypted were not.
    for name in unencrypted_attributes:
        assert decrypted_item[name] == encrypted_item[name] == plaintext_item[name]
示例#2
0
class EncryptedTable(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.table import EncryptedTable
    >>> from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider
    >>> table = boto3.resource('dynamodb').Table('my_table')
    >>> aws_kms_cmp = AwsKmsCryptographicMaterialsProvider('alias/MyKmsAlias')
    >>> encrypted_table = EncryptedTable(
    ...     table=table,
    ...     materials_provider=aws_kms_cmp
    ... )

    .. note::

        This class provides a superset of the boto3 DynamoDB Table API, so should work as
        a drop-in replacement once configured.

        https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#table

        If you want to provide per-request cryptographic details, the ``put_item``, ``get_item``,
        ``query``, and ``scan`` methods will also accept a ``crypto_config`` parameter, defining
        a custom :class:`CryptoConfig` instance for this request.

    .. warning::

        We do not currently support the ``update_item`` method.

    :param table: Pre-configured boto3 DynamoDB Table object
    :type table: boto3.resources.base.ServiceResource
    :param CryptographicMaterialsProvider materials_provider: Cryptographic materials provider
        to use
    :param TableInfo table_info: Information about the target DynamoDB table
    :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)
    """

    _table = attr.ib(validator=attr.validators.instance_of(ServiceResource))
    _materials_provider = attr.ib(
        validator=attr.validators.instance_of(CryptographicMaterialsProvider))
    _table_info = attr.ib(validator=attr.validators.optional(
        attr.validators.instance_of(TableInfo)),
                          default=None)
    _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,
            table,  # type: ServiceResource
            materials_provider,  # type: CryptographicMaterialsProvider
            table_info=None,  # type: Optional[TableInfo]
            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._table = table
        self._materials_provider = materials_provider
        self._table_info = table_info
        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):
        """Prepare table info is it was not set and set up translation methods."""
        if self._table_info is None:
            self._table_info = TableInfo(name=self._table.name)

        if self._auto_refresh_table_indexes:
            self._table_info.refresh_indexed_attributes(
                self._table.meta.client)

        # Clone the attribute actions before we modify them
        self._attribute_actions = self._attribute_actions.copy()
        self._attribute_actions.set_index_keys(
            *self._table_info.protected_index_keys())

        self._crypto_config = partial(  # attrs confuses pylint: disable=attribute-defined-outside-init
            crypto_config_from_kwargs,
            partial(crypto_config_from_table_info, self._materials_provider,
                    self._attribute_actions, self._table_info),
        )
        self.get_item = partial(  # attrs confuses pylint: disable=attribute-defined-outside-init
            decrypt_get_item, decrypt_python_item, self._crypto_config,
            self._table.get_item)
        self.put_item = partial(  # attrs confuses pylint: disable=attribute-defined-outside-init
            encrypt_put_item, encrypt_python_item, self._crypto_config,
            self._table.put_item)
        self.query = partial(  # attrs confuses pylint: disable=attribute-defined-outside-init
            decrypt_multi_get, decrypt_python_item, self._crypto_config,
            self._table.query)
        self.scan = partial(  # attrs confuses pylint: disable=attribute-defined-outside-init
            decrypt_multi_get, decrypt_python_item, self._crypto_config,
            self._table.scan)

    def __getattr__(self, name):
        """Catch any method/attribute lookups that are not defined in this class and try
        to find them on the provided bridge object.

        :param str name: Attribute name
        :returns: Result of asking the provided table object for that attribute name
        :raises AttributeError: if attribute is not found on provided bridge object
        """
        return getattr(self._table, name)

    def update_item(self, **kwargs):
        """Update item is not yet supported."""
        raise NotImplementedError('"update_item" is not yet implemented')

    def batch_writer(self, overwrite_by_pkeys=None):
        """Create a batch writer object.

        https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Table.batch_writer

        :type overwrite_by_pkeys: list(string)
        :param overwrite_by_pkeys: De-duplicate request items in buffer if match new request
            item on specified primary keys. i.e ``["partition_key1", "sort_key2", "sort_key3"]``
        """
        encrypted_client = EncryptedClient(
            client=self._table.meta.client,
            materials_provider=self._materials_provider,
            attribute_actions=self._attribute_actions,
            auto_refresh_table_indexes=self._auto_refresh_table_indexes,
            expect_standard_dictionaries=True,
        )
        return BatchWriter(table_name=self._table.name,
                           client=encrypted_client,
                           overwrite_by_pkeys=overwrite_by_pkeys)