Add support for explicit field encryption.

We now support explicit field encryption using mapped entities through the `@ExplicitEncrypted` annotation.

class Person {
  ObjectId id;

  @ExplicitEncrypted(algorithm = AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic, altKeyName = "my-secret-key")
  String socialSecurityNumber;
}

Encryption is applied transparently to all mapped entities leveraging the existing converter infrastructure.

Original pull request: #4302
Closes: #4284
This commit is contained in:
Christoph Strobl
2022-11-11 09:09:05 +01:00
committed by Mark Paluch
parent 3b7b1ace8b
commit 3b33f90e5c
27 changed files with 2414 additions and 48 deletions

View File

@@ -32,6 +32,7 @@ include::{spring-data-commons-docs}/auditing.adoc[leveloffset=+1]
include::reference/mongo-auditing.adoc[leveloffset=+1]
include::reference/mapping.adoc[leveloffset=+1]
include::reference/sharding.adoc[leveloffset=+1]
include::reference/mongo-encryption.adoc[leveloffset=+1]
include::reference/kotlin.adoc[leveloffset=+1]
include::reference/jmx.adoc[leveloffset=+1]

View File

@@ -0,0 +1,156 @@
[[mongo.encryption]]
= Client Side Field Level Encryption (CSFLE)
Client Side Encryption is a feature that encrypts data in your application before it is sent to MongoDB.
Please make sure to read the https://www.mongodb.com/docs/manual/core/csfle/[MongoDB Documentation] to learn more about its capabilities and restrictions.
[NOTE]
====
Make sure to set the drivers `com.mongodb.AutoEncryptionSettings` to use client-side encryption.
MongoDB does not support encryption for all field types.
Specific data types require deterministic encryption to preserve equality comparison functionality.
====
[[mongo.encryption.automatic]]
== Automatic Encryption
MongoDB supports https://www.mongodb.com/docs/manual/core/csfle/[Client-Side Field Level Encryption] out of the box using the MongoDB driver with its Automatic Encryption feature.
Automatic Encryption requires a <<mongo.jsonSchema,JSON Schema>> that allows to perform encrypted read and write operations without the need to provide an explicit en-/decryption step.
Please refer to the <<mongo.jsonSchema.encrypted-fields,JSON Schema>> section for more information on defining a JSON Schema that holds encryption information.
To make use of a the `MongoJsonSchema` it needs to be combined with `AutoEncryptionSettings` which can be done eg. via a `MongoClientSettingsBuilderCustomizer`.
[source,java]
----
@Bean
MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) {
return (builder) -> {
// ... keyVaultCollection, kmsProvider, ...
MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
MongoJsonSchema patientSchema = schemaCreator
.filter(MongoJsonSchemaCreator.encryptedOnly())
.createSchemaFor(Patient.class);
AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder()
.keyVaultNamespace(keyVaultCollection)
.kmsProviders(kmsProviders)
.extraOptions(extraOpts)
.schemaMap(Collections.singletonMap("db.patient", patientSchema.schemaDocument().toBsonDocument()))
.build();
builder.autoEncryptionSettings(autoEncryptionSettings);
};
}
----
[[mongo.encryption.explicit]]
== Explicit Encryption
Explicit encryption uses the MongoDB driver's encryption library (`org.mongodb:mongodb-crypt`) to perform en-/decryption tasks.
The `@ExplicitlyEncrypted` annotation is a combination of the `@Encrypted` annotation used for <<mongo.jsonSchema.encrypted-fields,JSON Schema creation>> and a <<mongo.property-converters, Property Converter>>.
In other words, `@ExplicitlyEncrypted` uses existing building blocks and combines them to provide simplified support for explicit encryption.
[NOTE]
====
Fields annotated with `@ExplicitlyEncrypted` are always encrypted entirely as outlined in below.
[source,java]
----
@ExplicitlyEncrypted(...)
String simpleValue; <1>
@ExplicitlyEncrypted(...)
Address address; <2>
@ExplicitlyEncrypted(...)
List<...> list; <3>
@ExplicitlyEncrypted(...)
Map<..., ...> mapOfString; <3>
----
<1> Encrypts the value of the simple type eg. a `String` if not `null`.
<2> Encrypts the entire `Address` object and all its nested fields. To only encrypt parts of the `Address`, like `Address#street` the `street` field needs to be annotated.
<3> `Collection` like fields are encrypted entirely and not a value by value basis.
<4> `Map` like fields are encrypted entirely and not on a key/value basis.
====
Depending on the encryption algorithm MongoDB supports certain operations on an encrypted field using its https://www.mongodb.com/docs/manual/core/queryable-encryption/[Queryable Encryption] feature.
To pick a certain algorithm use `@ExplicitlyEncrypted(algorithm = ... )` and choose the required one via `EncryptionAlgorithms`.
Please read the https://www.mongodb.com/docs/manual/core/csfle/fundamentals/encryption-algorithms[Encryption Types] manual for more information on algorithms and their usage.
To perform the actual encryption we do also need a Data Encryption Key (DEK).
Please refer to the https://www.mongodb.com/docs/manual/core/csfle/quick-start/#create-a-data-encryption-key[MongoDB Documentation] for more information on how to set up key management and create a Data Encryption Key.
The DEK can be referenced directly via its `id` or a defined _alternative name_.
The `@EncryptedField` annotation only allows referencing a DEK via an alternative name.
It is possible to provide an `EncryptionKeyResolver`, which will be discussed later, to any DEK.
.Reference the Data Encryption Key
====
[source,java]
----
@EncryptedField(algorithm = ..., altKeyName = "secret-key") <1>
String ssn;
----
[source,java]
----
@EncryptedField(algorithm = ..., altKeyName = "/name") <2>
String ssn;
----
<1> Use the DEK stored with the alternative name `secret-key`.
<2> Uses a field reference that will read the actual field value and use that for key lookup. Always requires the full document to be present for save operations. Fields cannot be used in queries/aggregations.
====
By default the `@ExplicitlyEncrypted(value=...)` attribute will reference a `MongoEncryptionConverter`.
It is possible to change the default implementation and exchange it with any `PropertyValueConverter` implementation by providing the according type reference.
To learn more about custom `PropertyValueConverters` and the required configuration, please refer to the <<mongo.property-converters>> section.
[[mongo.encryption.explicit-setup]]
=== MongoEncryptionConverter Setup
The default `MongoEncryptionConverter` needs to be registered within the `ApplicationContext`.
To do so we need to 1st setup the `Bean` and 2nd use a `BeanFactoryAwarePropertyValueConverterFactory` in the converter configuration.
The converter itself needs to know about the actual `Encryption` that is capable of en-/decrypting `BsonValue` to/from `BsonBinary` as well as a `EncryptionKeyResolver`.
`MongoClientEncryption` is the default implementation delegating en-/decryption to `com.mongodb.client.vault.ClientEncryption`.
The `EncryptionKeyResolver` provides the DEK to be used for encrypting the field.
Since the `@ExplicitlyEncrypted` annotation does not need to specify an alt key name the `EncryptionKeyResolver` receives the current `EncryptionContext` that provides access to the field for dynamic DEK resolution.
`EncryptionKeyResolver.annotationBased(...)` offers an implementation that will lookup values from the `@ExplicitlyEncrypted` annotation before falling back to the context based resolution.
.Sample MongoEncryptionConverter Configuration
====
[source,java]
----
class Config extends AbstractMongoClientConfiguration {
// ...
@Autowired ApplicationContext appContext;
@Bean
MongoEncryptionConverter encryptingConverter() {
ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder()
// ...
Encryption<BsonValue, BsonBinary> encryption = MongoClientEncryption.just(ClientEncryptions.create(encryptionSettings)) <1>
EncryptionKeyResolver keyResolver = EncryptionKeyResolver.annotationBased((ctx) -> ...); <2>
return new MongoEncryptionConverter(encryption, keyResolver); <3>
}
@Override
protected void configureConverters(MongoConverterConfigurationAdapter adapter) {
adapter
.registerPropertyValueConverterFactory(PropertyValueConverterFactory.beanFactoryAware(appContext)); <4>
}
}
----
<1> Set up a `com.mongodb.client.vault.ClientEncryption` specific `Encryption` engine.
<2> Read the `EncryptionKey` from annotations on the field.
<3> Create the `MongoEncryptionConverter`.
<4> Enable for a `PropertyValueConverter` within the `BeanFactory`.
====

View File

@@ -396,37 +396,8 @@ public class EncryptionExtension implements EvaluationContextExtension {
}
}
----
To combine derived encryption settings with `AutoEncryptionSettings` in a Spring Boot application use the `MongoClientSettingsBuilderCustomizer`.
[source,java]
----
@Bean
MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) {
return (builder) -> {
// ... keyVaultCollection, kmsProvider, ...
MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
MongoJsonSchema patientSchema = schemaCreator
.filter(MongoJsonSchemaCreator.encryptedOnly())
.createSchemaFor(Patient.class);
AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder()
.keyVaultNamespace(keyVaultCollection)
.kmsProviders(kmsProviders)
.extraOptions(extraOpts)
.schemaMap(Collections.singletonMap("db.patient", patientSchema.schemaDocument().toBsonDocument()))
.build();
builder.autoEncryptionSettings(autoEncryptionSettings);
};
}
----
====
NOTE: Make sure to set the drivers `com.mongodb.AutoEncryptionSettings` to use client-side encryption. MongoDB does not support encryption for all field types. Specific data types require deterministic encryption to preserve equality comparison functionality.
[[mongo.jsonSchema.types]]
==== JSON Schema Types