Skip to content

Update documentation and warnings related to SaveBehavior. #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 3, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
The **[Amazon DynamoDB][ddb] Client-side Encryption in Java** supports encryption and signing of your data when stored in Amazon DynamoDB.

A typical use of this library is when you are using [DynamoDBMapper][ddbmapper], where transparent protection of all objects serialized through the mapper can be enabled via configuring an [AttributeEncryptor][attrencryptor].
**Please note that it is critically important that you use `SaveBehavior.CLOBBER` when using AttributeEncryptor.**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we compact the wording of the warning for grokkability, and add an expanded explanation?

Perhaps:

Important: use SaveBehavior.CLOBBER with AttributeEncryptor. This is because ...

Should https://github.com/aws/aws-dynamodb-encryption-java/blame/ded8364a9baee731f6f83ddddca8cec5bc614f9c/README.md#L77 be DynamoDBMapperConfig.CLOBBER as well? It looks to my quick read like it is affected, since it's using the AttributeEncryptor.


For more advanced use cases where tighter control over the encryption and signing process is necessary, the low-level [DynamoDBEncryptor][ddbencryptor] can be used directly.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static void encryptRecord(final String cmkArn, final String region) {
// Encryptor creation
final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp);
// Mapper Creation
// Please note the use of SaveBehavior.CLOBBER. Omitting this may result in data-corruption.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/may/can/?

DynamoDBMapperConfig mapperConfig = DynamoDBMapperConfig.builder().withSaveBehavior(SaveBehavior.CLOBBER).build();
DynamoDBMapper mapper = new DynamoDBMapper(ddb, mapperConfig, new AttributeEncryptor(encryptor));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad IntelliJ setting injected an import-*. I'll fix this.

import java.util.concurrent.ConcurrentHashMap;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.SaveBehavior;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingsRegistry.Mapping;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingsRegistry.Mappings;
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DoNotEncrypt;
Expand All @@ -32,13 +29,18 @@
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.TableAadOverride;
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Encrypts all non-key fields prior to storing them in DynamoDB.
* <em>It is critically important that this is only used with @{link SaveBehavior#CLOBBER}. Use of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brevity for grokability?:

<em>This must be used with @{link SaveBehavior#CLOBBER}.</em> Use of any other...

Also, I think June would suggest using "can" rather than "may" for clarity, since it is a possibility, not a permission.

* any other @{code SaveBehavior} may result in data-corruption.</em>
*
* @author Greg Rubin
*/
public class AttributeEncryptor implements AttributeTransformer {
private static final Log LOG = LogFactory.getLog(AttributeEncryptor.class);
private final DynamoDBEncryptor encryptor;
private final Map<Class<?>, ModelClassMetadata> metadataCache = new ConcurrentHashMap<>();

Expand All @@ -58,9 +60,20 @@ public DynamoDBEncryptor getEncryptor() {
public Map<String, AttributeValue> transform(final Parameters<?> parameters) {
// one map of attributeFlags per model class
final ModelClassMetadata metadata = getModelClassMetadata(parameters);

final Map<String, AttributeValue> attributeValues = parameters.getAttributeValues();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a short comment explaining the case being handled here / why this is set to behave this way, for clarity/maintainers?

if (metadata.doNotTouch) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a behavior change, unless I am missing something. Can we cover it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't a behavior change. In a "DoNotTouch" case, we make no changes whatsoever to the underlying data. I'm just short-circuiting that logic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

return attributeValues;
}

if (parameters.isPartialUpdate()) {
LOG.error("Use of AttributeEncryptor without SaveBehavior.CLOBBER is an error and may result in data-corruption. " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/may/can/?

"This occured while trying to save " + parameters.getModelClass());
}

try {
return encryptor.encryptRecord(
parameters.getAttributeValues(),
attributeValues,
metadata.getEncryptionFlags(),
paramsToContext(parameters));
} catch (Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ public class TransformerHolisticTests {
private static final EncryptionMaterialsProvider symWrappedProv;

private AmazonDynamoDB client;
// AttributeEncryptor *must* be used with SaveBehavior.CLOBBER to avoid the risk of data corruption.
private static final DynamoDBMapperConfig CLOBBER_CONFIG =
DynamoDBMapperConfig.builder().withSaveBehavior(SaveBehavior.CLOBBER).build();
private static final BaseClass ENCRYPTED_TEST_VALUE = new BaseClass();
private static final Mixed MIXED_TEST_VALUE = new Mixed();
private static final SignOnly SIGNED_TEST_VALUE = new SignOnly();
Expand Down Expand Up @@ -292,7 +295,7 @@ public void setUp() {

@Test
public void simpleSaveLoad() {
DynamoDBMapper mapper = new DynamoDBMapper(client, DynamoDBMapperConfig.DEFAULT, new AttributeEncryptor(symProv));
DynamoDBMapper mapper = new DynamoDBMapper(client, CLOBBER_CONFIG, new AttributeEncryptor(symProv));
Mixed obj = new Mixed();
obj.setHashKey(0);
obj.setRangeKey(15);
Expand Down Expand Up @@ -322,7 +325,7 @@ public void simpleSaveLoad() {

@Test
public void leadingAndTrailingZeros() {
DynamoDBMapper mapper = new DynamoDBMapper(client, DynamoDBMapperConfig.DEFAULT, new AttributeEncryptor(symProv));
DynamoDBMapper mapper = new DynamoDBMapper(client, CLOBBER_CONFIG, new AttributeEncryptor(symProv));
Mixed obj = new Mixed();
obj.setHashKey(0);
obj.setRangeKey(15);
Expand Down Expand Up @@ -364,7 +367,7 @@ public void leadingAndTrailingZeros() {

@Test
public void simpleSaveLoadAsym() {
DynamoDBMapper mapper = new DynamoDBMapper(client, DynamoDBMapperConfig.DEFAULT, new AttributeEncryptor(asymProv));
DynamoDBMapper mapper = new DynamoDBMapper(client, CLOBBER_CONFIG, new AttributeEncryptor(asymProv));

BaseClass obj = new BaseClass();
obj.setHashKey(0);
Expand Down Expand Up @@ -394,7 +397,7 @@ public void simpleSaveLoadAsym() {

@Test
public void simpleSaveLoadHashOnly() {
DynamoDBMapper mapper = new DynamoDBMapper(client, DynamoDBMapperConfig.DEFAULT, new AttributeEncryptor(
DynamoDBMapper mapper = new DynamoDBMapper(client, CLOBBER_CONFIG, new AttributeEncryptor(
symProv));

HashKeyOnly obj = new HashKeyOnly("");
Expand All @@ -411,7 +414,7 @@ public void simpleSaveLoadHashOnly() {

@Test
public void simpleSaveLoadKeysOnly() {
DynamoDBMapper mapper = new DynamoDBMapper(client, DynamoDBMapperConfig.DEFAULT, new AttributeEncryptor(
DynamoDBMapper mapper = new DynamoDBMapper(client, CLOBBER_CONFIG, new AttributeEncryptor(
asymProv));

KeysOnly obj = new KeysOnly();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@
import com.amazonaws.services.dynamodbv2.model.AttributeValue;

public class FakeParameters<T> {
public static <T> AttributeTransformer.Parameters<T> getInstance(Class<T> clazz,
Map<String, AttributeValue> attribs, DynamoDBMapperConfig config, String tableName,
String hashKeyName, String rangeKeyName) {
return getInstance(clazz, attribs, config, tableName, hashKeyName, rangeKeyName, false);
}

public static <T> AttributeTransformer.Parameters<T> getInstance(Class<T> clazz,
Map<String, AttributeValue> attribs, DynamoDBMapperConfig config, String tableName,
String hashKeyName, String rangeKeyName) {
String hashKeyName, String rangeKeyName, boolean isPartialUpdate) {

// We use this relatively insane proxy setup so that modifications to the Parameters
// interface doesn't break our tests (unless it actually impacts our code).
FakeParameters<T> fakeParams = new FakeParameters<T>(clazz, attribs, config, tableName,
hashKeyName, rangeKeyName);
hashKeyName, rangeKeyName, isPartialUpdate);
@SuppressWarnings("unchecked")
AttributeTransformer.Parameters<T> proxyObject = (AttributeTransformer.Parameters<T>) Proxy
.newProxyInstance(AttributeTransformer.class.getClassLoader(),
Expand Down Expand Up @@ -65,16 +71,19 @@ public Object invoke(Object obj, Method method, Object[] args) throws Throwable
private final String tableName;
private final String hashKeyName;
private final String rangeKeyName;
private final boolean isPartialUpdate;

private FakeParameters(Class<T> clazz, Map<String, AttributeValue> attribs,
DynamoDBMapperConfig config, String tableName, String hashKeyName, String rangeKeyName) {
DynamoDBMapperConfig config, String tableName, String hashKeyName, String rangeKeyName,
boolean isPartialUpdate) {
super();
this.clazz = clazz;
this.attrs = Collections.unmodifiableMap(attribs);
this.config = config;
this.tableName = tableName;
this.hashKeyName = hashKeyName;
this.rangeKeyName = rangeKeyName;
this.isPartialUpdate = isPartialUpdate;
}

public Map<String, AttributeValue> getAttributeValues() {
Expand All @@ -100,4 +109,8 @@ public String getHashKeyName() {
public String getRangeKeyName() {
return rangeKeyName;
}

public boolean isPartialUpdate() {
return isPartialUpdate;
}
}