def test_attribute_actions_index_override_fail(default): test = AttributeActions( default_action=default, attribute_actions={"indexed_attribute": CryptoAction.ENCRYPT_AND_SIGN}) with pytest.raises(InvalidArgumentError) as excinfo: test.set_index_keys("indexed_attribute") excinfo.match( r"Cannot overwrite a previously requested action on indexed attribute: *" )
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]
def test_attribute_actions_index_override(default, overrides, expected_result): test = AttributeActions(default_action=default, attribute_actions=overrides) test.set_index_keys("indexed_attribute") assert test.action("indexed_attribute") is expected_result
class KeyStore(ABC): @abstractmethod def _get_item(self, key_id: str): pass @abstractmethod def _put_item(self, key_id: str, encrypted_item): pass @abstractmethod def _delete_item(self, key_id: str) -> None: pass def __init__( self, materials_provider, table_name: str = "key_store_table", key_bytes_generator=key_bytes_generator, ) -> None: self._materials_provider = materials_provider self._table_name = table_name self._key_bytes_generator = key_bytes_generator self._actions = AttributeActions( default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={ "restricted": CryptoAction.DO_NOTHING, "on_hold": CryptoAction.DO_NOTHING, "ttl": CryptoAction.DO_NOTHING, }, ) self._actions.set_index_keys("key_id") def create_main_key(self, key_id: str, key_length=256, key_bytes=None) -> MainKey: index_key = {"key_id": key_id} if key_bytes is None: key_bytes = self._key_bytes_generator(key_length) plaintext_item = { "restricted": False, "on_hold": False, "key": key_bytes, } plaintext_item.update(index_key) encryption_context = EncryptionContext( table_name=self._table_name, partition_key_name="key_id", attributes=dict_to_ddb(index_key), ) crypto_config = CryptoConfig( materials_provider=self._materials_provider, encryption_context=encryption_context, attribute_actions=self._actions, ) encrypted_item = encrypt_python_item(plaintext_item, crypto_config) self._put_item(key_id=key_id, encrypted_item=encrypted_item) return MainKey( key_id=key_id, key_bytes=key_bytes, ) def get_main_key(self, key_id: str) -> MainKey: index_key = {"key_id": key_id} encryption_context = EncryptionContext( table_name=self._table_name, partition_key_name="key_id", attributes=dict_to_ddb(index_key), ) crypto_config = CryptoConfig( materials_provider=self._materials_provider, encryption_context=encryption_context, attribute_actions=self._actions, ) encrypted_item = self._get_item(key_id=key_id) if encrypted_item["restricted"]: raise Exception("Access restricted.") decrypted_item = decrypt_python_item(encrypted_item, crypto_config) return MainKey( key_id=key_id, key_bytes=decrypted_item["key"].value, ) def delete_main_key(self, key_id: str, allow_recovering=False) -> None: encrypted_item = self._get_item(key_id=key_id) if encrypted_item["on_hold"]: raise Exception("Deletion currently not possible.") self._delete_item(key_id=key_id)