-
Notifications
You must be signed in to change notification settings - Fork 111
Experiment with how to make YAML tests (and SQL more generally) able to encrypt records at rest #3557
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
base: main
Are you sure you want to change the base?
Experiment with how to make YAML tests (and SQL more generally) able to encrypt records at rest #3557
Changes from all commits
fd082c0
396777b
448a651
1ff759c
ccc924e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,36 +24,26 @@ | |
|
||
import com.apple.foundationdb.record.IndexState; | ||
import com.apple.foundationdb.record.provider.common.RecordSerializer; | ||
import com.apple.foundationdb.record.provider.common.TransformedRecordSerializer; | ||
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; | ||
|
||
import com.apple.foundationdb.record.provider.foundationdb.FormatVersion; | ||
import com.apple.foundationdb.relational.recordlayer.storage.StoreConfig; | ||
import com.google.protobuf.Message; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.zip.Deflater; | ||
|
||
/** | ||
* Holder object for RecordLayer-specific stuff that isn't directly tied to an actual FDB StorageCluster. | ||
*/ | ||
@API(API.Status.EXPERIMENTAL) | ||
public final class RecordLayerConfig { | ||
private final FDBRecordStoreBase.UserVersionChecker userVersionChecker; | ||
private final RecordSerializer<Message> serializer; | ||
private final FormatVersion formatVersion; | ||
private final Map<String, IndexState> indexStateMap; | ||
|
||
private static final RecordSerializer<Message> DEFAULT_RELATIONAL_SERIALIZER = TransformedRecordSerializer.newDefaultBuilder() | ||
.setEncryptWhenSerializing(false) | ||
.setCompressWhenSerializing(true) | ||
.setCompressionLevel(Deflater.DEFAULT_COMPRESSION) | ||
.setWriteValidationRatio(0.0) | ||
.build(); | ||
|
||
private RecordLayerConfig(RecordLayerConfigBuilder builder) { | ||
this.userVersionChecker = builder.userVersionChecker; | ||
this.serializer = builder.serializer; | ||
this.formatVersion = builder.formatVersion; | ||
this.indexStateMap = builder.indexStateMap; | ||
} | ||
|
@@ -62,10 +52,6 @@ public FDBRecordStoreBase.UserVersionChecker getUserVersionChecker() { | |
return userVersionChecker; | ||
} | ||
|
||
public RecordSerializer<Message> getSerializer() { | ||
return serializer; | ||
} | ||
|
||
public FormatVersion getFormatVersion() { | ||
return formatVersion; | ||
} | ||
|
@@ -86,7 +72,7 @@ public static class RecordLayerConfigBuilder { | |
|
||
public RecordLayerConfigBuilder() { | ||
this.userVersionChecker = (oldUserVersion, oldMetaDataVersion, metaData) -> CompletableFuture.completedFuture(oldUserVersion); | ||
this.serializer = DEFAULT_RELATIONAL_SERIALIZER; | ||
this.serializer = StoreConfig.DEFAULT_RELATIONAL_SERIALIZER; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can probably be removed? |
||
this.formatVersion = FormatVersion.getDefaultFormatVersion(); | ||
this.indexStateMap = Map.of(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,42 +21,63 @@ | |
package com.apple.foundationdb.relational.recordlayer.storage; | ||
|
||
import com.apple.foundationdb.annotation.API; | ||
|
||
import com.apple.foundationdb.record.RecordCoreException; | ||
import com.apple.foundationdb.record.RecordMetaDataProvider; | ||
import com.apple.foundationdb.record.metadata.MetaDataException; | ||
import com.apple.foundationdb.record.provider.common.RecordSerializer; | ||
import com.apple.foundationdb.record.provider.common.TransformedRecordSerializer; | ||
import com.apple.foundationdb.record.provider.common.TransformedRecordSerializerJCE; | ||
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; | ||
import com.apple.foundationdb.record.provider.foundationdb.FormatVersion; | ||
import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; | ||
import com.apple.foundationdb.record.provider.foundationdb.keyspace.NoSuchDirectoryException; | ||
import com.apple.foundationdb.relational.api.Options; | ||
import com.apple.foundationdb.relational.api.Transaction; | ||
import com.apple.foundationdb.relational.api.exceptions.ErrorCode; | ||
import com.apple.foundationdb.relational.api.exceptions.RelationalException; | ||
import com.apple.foundationdb.relational.recordlayer.RecordLayerConfig; | ||
import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; | ||
import com.apple.foundationdb.relational.recordlayer.catalog.RecordMetaDataStore; | ||
import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; | ||
|
||
import com.google.protobuf.Message; | ||
|
||
import javax.crypto.KeyGenerator; | ||
import javax.crypto.SecretKey; | ||
import javax.crypto.SecretKeyFactory; | ||
import javax.crypto.spec.PBEKeySpec; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import java.net.URI; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.spec.InvalidKeySpecException; | ||
import java.security.spec.KeySpec; | ||
import java.util.Base64; | ||
import java.util.zip.Deflater; | ||
|
||
@API(API.Status.EXPERIMENTAL) | ||
public final class StoreConfig { | ||
public static final RecordSerializer<Message> DEFAULT_RELATIONAL_SERIALIZER = TransformedRecordSerializer.newDefaultBuilder() | ||
.setEncryptWhenSerializing(false) | ||
.setCompressWhenSerializing(true) | ||
.setCompressionLevel(Deflater.DEFAULT_COMPRESSION) | ||
.setWriteValidationRatio(0.0) | ||
.build(); | ||
private final RecordLayerConfig recordLayerConfig; | ||
private final String schemaName; | ||
private final KeySpacePath storePath; | ||
private final RecordMetaDataProvider metaDataProvider; | ||
private final RecordSerializer<Message> serializer; | ||
|
||
private StoreConfig(RecordLayerConfig recordLayerConfig, | ||
String schemaName, | ||
KeySpacePath storePath, | ||
RecordMetaDataProvider metaDataProvider) { | ||
RecordMetaDataProvider metaDataProvider, | ||
RecordSerializer<Message> serializer) { | ||
ohadzeliger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.recordLayerConfig = recordLayerConfig; | ||
this.schemaName = schemaName; | ||
this.storePath = storePath; | ||
this.metaDataProvider = metaDataProvider; | ||
this.serializer = serializer; | ||
} | ||
|
||
public String getSchemaName() { | ||
|
@@ -72,7 +93,7 @@ | |
} | ||
|
||
public RecordSerializer<Message> getSerializer() { | ||
return recordLayerConfig.getSerializer(); | ||
return serializer; | ||
} | ||
|
||
public FDBRecordStoreBase.UserVersionChecker getUserVersionChecker() { | ||
|
@@ -87,7 +108,8 @@ | |
String schemaName, | ||
RelationalKeyspaceProvider.RelationalDatabasePath databasePath, | ||
RecordMetaDataStore metaDataStore, | ||
Transaction transaction) throws RelationalException { | ||
Transaction transaction, | ||
Options options) throws RelationalException { | ||
//TODO(bfines) error handling if this store doesn't exist | ||
|
||
RelationalKeyspaceProvider.RelationalSchemaPath schemaPath; | ||
|
@@ -104,6 +126,53 @@ | |
URI dbUri = databasePath.toUri(); | ||
RecordMetaDataProvider metaDataProvider = metaDataStore.loadMetaData(transaction, dbUri, schemaName); | ||
|
||
return new StoreConfig(recordLayerConfig, schemaName, schemaPath, metaDataProvider); | ||
RecordSerializer<Message> serializer = serializerFromOptions(options); | ||
|
||
return new StoreConfig(recordLayerConfig, schemaName, schemaPath, metaDataProvider, serializer); | ||
} | ||
|
||
static RecordSerializer<Message> serializerFromOptions(Options options) throws RelationalException { | ||
final boolean encrypted = options.getOption(Options.Name.ENCRYPT_WHEN_SERIALIZING); | ||
if (!encrypted) { | ||
return DEFAULT_RELATIONAL_SERIALIZER; | ||
} | ||
final SecretKey key; | ||
final String keyBase64 = options.getOption(Options.Name.ENCRYPTION_KEY); | ||
if (keyBase64 != null) { | ||
key = new SecretKeySpec(Base64.getDecoder().decode(keyBase64), "AES"); | ||
} else { | ||
// TODO: Is there a way to make this only available in YAML test framework? | ||
Check warning on line 144 in fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/storage/StoreConfig.java
|
||
final String keyPassword = options.getOption(Options.Name.ENCRYPTION_PASSWORD); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a better testing strategy is to check in a key to the repo and have the test harness read the file and pass it in? |
||
if (keyPassword != null) { | ||
SecretKeyFactory kdf; | ||
try { | ||
kdf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); | ||
} catch (NoSuchAlgorithmException ex) { | ||
throw new RelationalException("KDF not found", ErrorCode.UNSUPPORTED_OPERATION, ex); | ||
} | ||
KeySpec ks = new PBEKeySpec(keyPassword.toCharArray(), "YAML-salt".getBytes(StandardCharsets.UTF_8), 1, 128); | ||
Check warning on line 153 in fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/storage/StoreConfig.java
|
||
try { | ||
key = new SecretKeySpec(kdf.generateSecret(ks).getEncoded(), "AES"); | ||
} catch (InvalidKeySpecException ex) { | ||
throw new RelationalException("Key derivation failed", ErrorCode.UNSUPPORTED_OPERATION, ex); | ||
} | ||
} else { | ||
KeyGenerator keyGen; | ||
try { | ||
keyGen = KeyGenerator.getInstance("AES"); | ||
ohadzeliger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} catch (NoSuchAlgorithmException ex) { | ||
throw new RelationalException("Key generator not found", ErrorCode.UNSUPPORTED_OPERATION, ex); | ||
} | ||
keyGen.init(128); | ||
key = keyGen.generateKey(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the end goal of this code path? A one-time key that cannot be reused? |
||
} | ||
} | ||
return TransformedRecordSerializerJCE.newDefaultBuilder() | ||
.setEncryptWhenSerializing(true) | ||
.setEncryptionKey(key) | ||
.setCompressWhenSerializing(true) | ||
.setCompressionLevel(Deflater.DEFAULT_COMPRESSION) | ||
.setWriteValidationRatio(0.0) | ||
.build(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be used for testing only? Maybe document it as such?