diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml b/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml index b9a47aa703..484e0c0f7e 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml +++ b/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml @@ -16,6 +16,21 @@ + + org.mongodb + mongodb-driver-sync + ${mongodb-driver.version} + + + org.mongodb + mongodb-driver-core + ${mongodb-driver.version} + + + org.mongodb + bson + ${mongodb-driver.version} + org.springframework.boot spring-boot-starter-web @@ -23,6 +38,16 @@ org.springframework.boot spring-boot-starter-data-mongodb + + + org.mongodb + mongodb-driver-sync + + + org.mongodb + mongodb-driver-core + + org.mongodb @@ -37,7 +62,8 @@ - 1.6.1 + 1.7.3 + 4.9.1 diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java index 0ff97eb6c1..1a74340057 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java @@ -1,5 +1,7 @@ package com.baeldung.boot.csfle.config; +import java.io.File; + import org.bson.BsonBinary; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -17,7 +19,13 @@ public class EncryptionConfig { private String keyVaultAlias; @Value("${com.baeldung.csfle.auto-decryption:false}") - private Boolean autoDecryption; + private boolean autoDecryption; + + @Value("${com.baeldung.csfle.auto-encryption:false}") + private boolean autoEncryption; + + @Value("${com.baeldung.csfle.auto-encryption-lib:#{null}}") + private File autoEncryptionLib; private BsonBinary dataKeyId; @@ -41,7 +49,23 @@ public class EncryptionConfig { return masterKeyPath; } - public Boolean getAutoDecryption() { + public boolean isAutoDecryption() { return autoDecryption; } + + public boolean isAutoEncryption() { + return autoEncryption; + } + + public File getAutoEncryptionLib() { + return autoEncryptionLib; + } + + public String dataKeyIdUuid() { + if (dataKeyId == null) + throw new IllegalStateException("data key not initialized"); + + return dataKeyId.asUuid() + .toString(); + } } diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java index 0dff1ec86d..19d0af08b2 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java @@ -1,14 +1,15 @@ package com.baeldung.boot.csfle.config; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import org.bson.BsonBinary; import org.bson.BsonDocument; import org.bson.Document; -import org.bson.conversions.Bson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -51,14 +52,10 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration { @Bean @Override public MongoClient mongoClient() { - MongoClient client; try { - client = MongoClients.create(clientSettings()); - ClientEncryption encryption = clientEncryption(); - encryptionConfig.setDataKeyId(createOrRetrieveDataKey(client, encryption)); - - return client; + encryptionConfig.setDataKeyId(createOrRetrieveDataKey(encryption)); + return MongoClients.create(clientSettings()); } catch (IOException e) { throw new IllegalStateException("unable to create client", e); } @@ -77,19 +74,10 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration { return ClientEncryptions.create(encryptionSettings); } - private BsonBinary createOrRetrieveDataKey(MongoClient client, ClientEncryption encryption) { - MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace()); - MongoCollection keyVault = client.getDatabase(namespace.getDatabaseName()) - .getCollection(namespace.getCollectionName()); - - Bson query = Filters.in("keyAltNames", encryptionConfig.getKeyVaultAlias()); - BsonDocument key = keyVault.withDocumentClass(BsonDocument.class) - .find(query) - .first(); - + private BsonBinary createOrRetrieveDataKey(ClientEncryption encryption) { + BsonDocument key = encryption.getKeyByAltName(encryptionConfig.getKeyVaultAlias()); if (key == null) { - keyVault.createIndex(Indexes.ascending("keyAltNames"), new IndexOptions().unique(true) - .partialFilterExpression(Filters.exists("keyAltNames"))); + createKeyUniqueIndex(); DataKeyOptions options = new DataKeyOptions(); options.keyAltNames(Arrays.asList(encryptionConfig.getKeyVaultAlias())); @@ -99,16 +87,68 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration { } } + private void createKeyUniqueIndex() { + try (MongoClient client = MongoClients.create(MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(uri)) + .build())) { + MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace()); + MongoCollection keyVault = client.getDatabase(namespace.getDatabaseName()) + .getCollection(namespace.getCollectionName()); + + keyVault.createIndex(Indexes.ascending("keyAltNames"), new IndexOptions().unique(true) + .partialFilterExpression(Filters.exists("keyAltNames"))); + } + } + private MongoClientSettings clientSettings() throws FileNotFoundException, IOException { Builder settings = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(uri)); - if (encryptionConfig.getAutoDecryption()) { - settings.autoEncryptionSettings(AutoEncryptionSettings.builder() + if (encryptionConfig.isAutoDecryption()) { + AutoEncryptionSettings.Builder builder = AutoEncryptionSettings.builder() .keyVaultNamespace(encryptionConfig.getKeyVaultNamespace()) - .kmsProviders(LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath())) - .bypassAutoEncryption(true) - .build()); + .kmsProviders(LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath())); + + if (encryptionConfig.isAutoEncryption() && encryptionConfig.getDataKeyId() != null) { + File autoEncryptionLib = encryptionConfig.getAutoEncryptionLib(); + if (!autoEncryptionLib.isFile()) { + throw new IllegalArgumentException("encryption lib must be an existing file"); + } + + Map map = new HashMap<>(); + map.put("cryptSharedLibRequired", true); + map.put("cryptSharedLibPath", autoEncryptionLib.toString()); + builder.extraOptions(map); + + String keyUuid = encryptionConfig.dataKeyIdUuid(); + HashMap schemaMap = new HashMap<>(); + schemaMap.put(getDatabaseName() + ".citizens", + BsonDocument.parse("{" + + " bsonType: \"object\"," + + " encryptMetadata: {" + + " keyId: [UUID(\"" + keyUuid + "\")]" + + " }," + + " properties: {" + + " email: {" + + " encrypt: {" + + " bsonType: \"string\"," + + " algorithm: \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\"" + + " }" + + " }," + + " birthYear: {" + + " encrypt: {" + + " bsonType: \"int\"," + + " algorithm: \"AEAD_AES_256_CBC_HMAC_SHA_512-Random\"" + + " }" + + " }" + + " }" + + "}")); + builder.schemaMap(schemaMap); + } else { + builder.bypassAutoEncryption(true); + } + + settings.autoEncryptionSettings(builder.build()); } return settings.build(); diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java index 6b3c463d0d..c93b00f3f8 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java @@ -35,16 +35,20 @@ public class CitizenService { @Autowired private ClientEncryption clientEncryption; - public EncryptedCitizen save(Citizen citizen) { - EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen); - encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM)); - encryptedCitizen.setBirthYear(encrypt(citizen.getBirthYear(), RANDOM_ALGORITHM)); + public Object save(Citizen citizen) { + if (encryptionConfig.isAutoEncryption()) { + return mongo.save(citizen); + } else { + EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen); + encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM)); + encryptedCitizen.setBirthYear(encrypt(citizen.getBirthYear(), RANDOM_ALGORITHM)); - return mongo.save(encryptedCitizen); + return mongo.save(encryptedCitizen); + } } public List findAll() { - if (!encryptionConfig.getAutoDecryption()) { + if (!encryptionConfig.isAutoDecryption()) { List allEncrypted = mongo.findAll(EncryptedCitizen.class); return allEncrypted.stream() @@ -56,13 +60,20 @@ public class CitizenService { } public Citizen findByEmail(String email) { - Query byEmail = new Query(Criteria.where("email") - .is(encrypt(email, DETERMINISTIC_ALGORITHM))); - if (!encryptionConfig.getAutoDecryption()) { + Criteria emailCriteria = Criteria.where("email"); + if (encryptionConfig.isAutoEncryption()) { + emailCriteria.is(email); + } else { + emailCriteria + .is(encrypt(email, DETERMINISTIC_ALGORITHM)); + } + + Query byEmail = new Query(emailCriteria); + if (encryptionConfig.isAutoDecryption()) { + return mongo.findOne(byEmail, Citizen.class); + } else { EncryptedCitizen encryptedCitizen = mongo.findOne(byEmail, EncryptedCitizen.class); return decrypt(encryptedCitizen); - } else { - return mongo.findOne(byEmail, Citizen.class); } } diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java index d17435fb5e..7a2b2605cd 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java @@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.baeldung.boot.csfle.data.Citizen; -import com.baeldung.boot.csfle.data.EncryptedCitizen; import com.baeldung.boot.csfle.service.CitizenService; @RestController @@ -32,7 +31,7 @@ public class CitizenController { } @PostMapping - public EncryptedCitizen post(@RequestBody Citizen citizen) { + public Object post(@RequestBody Citizen citizen) { return service.save(citizen); } } diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java index 471cb2883a..d57da43751 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java @@ -38,10 +38,14 @@ public class CitizenServiceLiveTest { citizen.setName("Foo"); citizen.setEmail("foo@citizen.com"); - Binary encryptedEmail = service.encrypt(citizen.getEmail(), CitizenService.DETERMINISTIC_ALGORITHM); + Object saved = service.save(citizen); + if (saved instanceof EncryptedCitizen) { + Binary encryptedEmail = service.encrypt(citizen.getEmail(), CitizenService.DETERMINISTIC_ALGORITHM); - EncryptedCitizen saved = service.save(citizen); - assertEquals(encryptedEmail, saved.getEmail()); + assertEquals(encryptedEmail, ((EncryptedCitizen) saved).getEmail()); + } else { + assertEquals(citizen.getEmail(), ((Citizen) saved).getEmail()); + } } @Test diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties index 5325354e55..cd1c1d43c7 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties @@ -1,10 +1,10 @@ spring.mongodb.embedded.version=4.4.9 -spring.data.mongodb.uri=changeit -spring.data.mongodb.database=changeit +#spring.data.mongodb.uri=changeit +#spring.data.mongodb.database=changeit com.baeldung.csfle.kms-provider=local com.baeldung.csfle.key-vault.namespace=encryption._keyVault com.baeldung.csfle.key-vault.alias=master.key -com.baeldung.csfle.master-key-path=/tmp/master.key +#com.baeldung.csfle.master-key-path=/tmp/master.key com.baeldung.csfle.auto-decryption=false