Skip to content

Commit 89d6be1

Browse files
rozzanhachicha
andauthored
Added option to configure DEK cache lifetime (#1689)
Added the `AutoEncryptionSettings.Builder#keyExpiration` method and `ClientEncryptionSettings.Builder#keyExpiration` method to configure the cache expiration time for data encryption keys. JAVA-5547 --------- Co-authored-by: Nabil Hachicha <nabil.hachicha@gmail.com>
1 parent 390cd0c commit 89d6be1

File tree

12 files changed

+154
-7
lines changed

12 files changed

+154
-7
lines changed

driver-core/src/main/com/mongodb/AutoEncryptionSettings.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import java.util.Collections;
2525
import java.util.HashMap;
2626
import java.util.Map;
27+
import java.util.concurrent.TimeUnit;
2728
import java.util.function.Supplier;
2829

30+
import static com.mongodb.assertions.Assertions.assertTrue;
2931
import static com.mongodb.assertions.Assertions.notNull;
3032
import static java.util.Collections.unmodifiableMap;
3133

@@ -73,6 +75,8 @@ public final class AutoEncryptionSettings {
7375
private final boolean bypassAutoEncryption;
7476
private final Map<String, BsonDocument> encryptedFieldsMap;
7577
private final boolean bypassQueryAnalysis;
78+
@Nullable
79+
private final Long keyExpirationMS;
7680

7781
/**
7882
* A builder for {@code AutoEncryptionSettings} so that {@code AutoEncryptionSettings} can be immutable, and to support easier
@@ -90,6 +94,7 @@ public static final class Builder {
9094
private boolean bypassAutoEncryption;
9195
private Map<String, BsonDocument> encryptedFieldsMap = Collections.emptyMap();
9296
private boolean bypassQueryAnalysis;
97+
@Nullable private Long keyExpirationMS;
9398

9499
/**
95100
* Sets the key vault settings.
@@ -236,6 +241,22 @@ public Builder bypassQueryAnalysis(final boolean bypassQueryAnalysis) {
236241
return this;
237242
}
238243

244+
/**
245+
* The cache expiration time for data encryption keys.
246+
* <p>Defaults to {@code null} which defers to libmongocrypt's default which is currently 60000 ms. Set to 0 to disable key expiration.</p>
247+
*
248+
* @param keyExpiration the cache expiration time in milliseconds or null to use libmongocrypt's default.
249+
* @param timeUnit the time unit
250+
* @return this
251+
* @see #getKeyExpiration(TimeUnit)
252+
* @since 5.5
253+
*/
254+
public Builder keyExpiration(@Nullable final Long keyExpiration, final TimeUnit timeUnit) {
255+
assertTrue(keyExpiration == null || keyExpiration >= 0, "keyExpiration must be >= 0 or null");
256+
this.keyExpirationMS = keyExpiration == null ? null : TimeUnit.MILLISECONDS.convert(keyExpiration, timeUnit);
257+
return this;
258+
}
259+
239260
/**
240261
* Build an instance of {@code AutoEncryptionSettings}.
241262
*
@@ -488,6 +509,21 @@ public boolean isBypassQueryAnalysis() {
488509
return bypassQueryAnalysis;
489510
}
490511

512+
/**
513+
* Returns the cache expiration time for data encryption keys.
514+
*
515+
* <p>Defaults to {@code null} which defers to libmongocrypt's default which is currently {@code 60000 ms}.
516+
* Set to {@code 0} to disable key expiration.</p>
517+
*
518+
* @param timeUnit the time unit, which must not be null
519+
* @return the cache expiration time or null if not set.
520+
* @since 5.5
521+
*/
522+
@Nullable
523+
public Long getKeyExpiration(final TimeUnit timeUnit) {
524+
return keyExpirationMS == null ? null : timeUnit.convert(keyExpirationMS, TimeUnit.MILLISECONDS);
525+
}
526+
491527
private AutoEncryptionSettings(final Builder builder) {
492528
this.keyVaultMongoClientSettings = builder.keyVaultMongoClientSettings;
493529
this.keyVaultNamespace = notNull("keyVaultNamespace", builder.keyVaultNamespace);
@@ -499,6 +535,7 @@ private AutoEncryptionSettings(final Builder builder) {
499535
this.bypassAutoEncryption = builder.bypassAutoEncryption;
500536
this.encryptedFieldsMap = builder.encryptedFieldsMap;
501537
this.bypassQueryAnalysis = builder.bypassQueryAnalysis;
538+
this.keyExpirationMS = builder.keyExpirationMS;
502539
}
503540

504541
@Override

driver-core/src/main/com/mongodb/ClientEncryptionSettings.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.concurrent.TimeUnit;
2828
import java.util.function.Supplier;
2929

30+
import static com.mongodb.assertions.Assertions.assertTrue;
3031
import static com.mongodb.assertions.Assertions.notNull;
3132
import static com.mongodb.internal.TimeoutSettings.convertAndValidateTimeout;
3233
import static java.util.Collections.unmodifiableMap;
@@ -50,6 +51,9 @@ public final class ClientEncryptionSettings {
5051
private final Map<String, SSLContext> kmsProviderSslContextMap;
5152
@Nullable
5253
private final Long timeoutMS;
54+
@Nullable
55+
private final Long keyExpirationMS;
56+
5357
/**
5458
* A builder for {@code ClientEncryptionSettings} so that {@code ClientEncryptionSettings} can be immutable, and to support easier
5559
* construction through chaining.
@@ -63,6 +67,8 @@ public static final class Builder {
6367
private Map<String, SSLContext> kmsProviderSslContextMap = new HashMap<>();
6468
@Nullable
6569
private Long timeoutMS;
70+
@Nullable
71+
private Long keyExpirationMS;
6672

6773
/**
6874
* Sets the {@link MongoClientSettings} that will be used to access the key vault.
@@ -130,6 +136,22 @@ public Builder kmsProviderSslContextMap(final Map<String, SSLContext> kmsProvide
130136
return this;
131137
}
132138

139+
/**
140+
* The cache expiration time for data encryption keys.
141+
* <p>Defaults to {@code null} which defers to libmongocrypt's default which is currently 60000 ms. Set to 0 to disable key expiration.</p>
142+
*
143+
* @param keyExpiration the cache expiration time in milliseconds or null to use libmongocrypt's default.
144+
* @param timeUnit the time unit
145+
* @return this
146+
* @see #getKeyExpiration(TimeUnit)
147+
* @since 5.5
148+
*/
149+
public Builder keyExpiration(@Nullable final Long keyExpiration, final TimeUnit timeUnit) {
150+
assertTrue(keyExpiration == null || keyExpiration >= 0, "keyExpiration must be >= 0 or null");
151+
this.keyExpirationMS = keyExpiration == null ? null : TimeUnit.MILLISECONDS.convert(keyExpiration, timeUnit);
152+
return this;
153+
}
154+
133155
/**
134156
* Sets the time limit for the full execution of an operation.
135157
*
@@ -308,6 +330,21 @@ public Map<String, SSLContext> getKmsProviderSslContextMap() {
308330
return unmodifiableMap(kmsProviderSslContextMap);
309331
}
310332

333+
/**
334+
* Returns the cache expiration time for data encryption keys.
335+
*
336+
* <p>Defaults to {@code null} which defers to libmongocrypt's default which is currently {@code 60000 ms}.
337+
* Set to {@code 0} to disable key expiration.</p>
338+
*
339+
* @param timeUnit the time unit, which may not be null
340+
* @return the cache expiration time or null if not set.
341+
* @since 5.5
342+
*/
343+
@Nullable
344+
public Long getKeyExpiration(final TimeUnit timeUnit) {
345+
return keyExpirationMS == null ? null : timeUnit.convert(keyExpirationMS, TimeUnit.MILLISECONDS);
346+
}
347+
311348
/**
312349
* The time limit for the full execution of an operation.
313350
*
@@ -348,6 +385,7 @@ private ClientEncryptionSettings(final Builder builder) {
348385
this.kmsProviderPropertySuppliers = notNull("kmsProviderPropertySuppliers", builder.kmsProviderPropertySuppliers);
349386
this.kmsProviderSslContextMap = notNull("kmsProviderSslContextMap", builder.kmsProviderSslContextMap);
350387
this.timeoutMS = builder.timeoutMS;
388+
this.keyExpirationMS = builder.keyExpirationMS;
351389
}
352390

353391
}

driver-core/src/main/com/mongodb/internal/capi/MongoCryptHelper.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
public final class MongoCryptHelper {
5454

5555
public static MongoCryptOptions createMongoCryptOptions(final ClientEncryptionSettings settings) {
56-
return createMongoCryptOptions(settings.getKmsProviders(), false, emptyList(), emptyMap(), null, null);
56+
return createMongoCryptOptions(settings.getKmsProviders(), false, emptyList(), emptyMap(), null, null,
57+
settings.getKeyExpiration(TimeUnit.MILLISECONDS));
5758
}
5859

5960
public static MongoCryptOptions createMongoCryptOptions(final AutoEncryptionSettings settings) {
@@ -63,7 +64,8 @@ public static MongoCryptOptions createMongoCryptOptions(final AutoEncryptionSett
6364
settings.isBypassAutoEncryption() ? emptyList() : singletonList("$SYSTEM"),
6465
settings.getExtraOptions(),
6566
settings.getSchemaMap(),
66-
settings.getEncryptedFieldsMap());
67+
settings.getEncryptedFieldsMap(),
68+
settings.getKeyExpiration(TimeUnit.MILLISECONDS));
6769
}
6870

6971
public static void validateRewrapManyDataKeyOptions(final RewrapManyDataKeyOptions options) {
@@ -78,7 +80,8 @@ private static MongoCryptOptions createMongoCryptOptions(
7880
final List<String> searchPaths,
7981
@Nullable final Map<String, Object> extraOptions,
8082
@Nullable final Map<String, BsonDocument> localSchemaMap,
81-
@Nullable final Map<String, BsonDocument> encryptedFieldsMap) {
83+
@Nullable final Map<String, BsonDocument> encryptedFieldsMap,
84+
@Nullable final Long keyExpirationMS) {
8285
MongoCryptOptions.Builder mongoCryptOptionsBuilder = MongoCryptOptions.builder();
8386
mongoCryptOptionsBuilder.kmsProviderOptions(getKmsProvidersAsBsonDocument(kmsProviders));
8487
mongoCryptOptionsBuilder.bypassQueryAnalysis(bypassQueryAnalysis);
@@ -87,6 +90,7 @@ private static MongoCryptOptions createMongoCryptOptions(
8790
mongoCryptOptionsBuilder.localSchemaMap(localSchemaMap);
8891
mongoCryptOptionsBuilder.encryptedFieldsMap(encryptedFieldsMap);
8992
mongoCryptOptionsBuilder.needsKmsCredentialsStateEnabled(true);
93+
mongoCryptOptionsBuilder.keyExpirationMS(keyExpirationMS);
9094
return mongoCryptOptionsBuilder.build();
9195
}
9296
public static BsonDocument fetchCredentials(final Map<String, Map<String, Object>> kmsProviders,

driver-core/src/test/functional/com/mongodb/internal/capi/MongoCryptHelperTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import java.util.HashMap;
2929
import java.util.Map;
30+
import java.util.concurrent.TimeUnit;
3031

3132
import static com.mongodb.internal.capi.MongoCryptHelper.isMongocryptdSpawningDisabled;
3233
import static com.mongodb.internal.capi.MongoCryptHelper.validateRewrapManyDataKeyOptions;
@@ -94,6 +95,11 @@ public void createsExpectedMongoCryptOptionsUsingAutoEncryptionSettings() {
9495

9596
assertMongoCryptOptions(mongoCryptOptionsBuilder.build(), mongoCryptOptions);
9697

98+
// Ensure can set key expiration
99+
autoEncryptionSettingsBuilder.keyExpiration(10L, TimeUnit.SECONDS);
100+
mongoCryptOptions = MongoCryptHelper.createMongoCryptOptions(autoEncryptionSettingsBuilder.build());
101+
assertMongoCryptOptions(mongoCryptOptionsBuilder.keyExpirationMS(10_000L).build(), mongoCryptOptions);
102+
97103
// Ensure search Paths is empty when bypassAutoEncryption is true
98104
autoEncryptionSettingsBuilder.bypassAutoEncryption(true);
99105
mongoCryptOptions = MongoCryptHelper.createMongoCryptOptions(autoEncryptionSettingsBuilder.build());
@@ -143,5 +149,6 @@ void assertMongoCryptOptions(final MongoCryptOptions expected, final MongoCryptO
143149
assertEquals(expected.getSearchPaths(), actual.getSearchPaths(), "SearchPaths not equal");
144150
assertEquals(expected.isBypassQueryAnalysis(), actual.isBypassQueryAnalysis(), "isBypassQueryAnalysis not equal");
145151
assertEquals(expected.isNeedsKmsCredentialsStateEnabled(), actual.isNeedsKmsCredentialsStateEnabled(), "isNeedsKmsCredentialsStateEnabled not equal");
152+
assertEquals(expected.getKeyExpirationMS(), actual.getKeyExpirationMS(), "keyExpirationMS not equal");
146153
}
147154
}

driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ public void setUp() {
135135
description.equals("timeoutMS applied to listCollections to get collection schema"));
136136
assumeFalse("runOn requirements not satisfied", skipTest);
137137
assumeFalse("Skipping count tests", filename.startsWith("count."));
138-
assumeFalse("https://jira.mongodb.org/browse/JAVA-5297", description.equals("Insert with deterministic encryption, then find it"));
139138

140139
assumeFalse(definition.getString("skipReason", new BsonString("")).getValue(), definition.containsKey("skipReason"));
141140

@@ -185,6 +184,8 @@ public void setUp() {
185184
BsonDocument kmsProviders = cryptOptions.getDocument("kmsProviders", new BsonDocument());
186185
boolean bypassAutoEncryption = cryptOptions.getBoolean("bypassAutoEncryption", BsonBoolean.FALSE).getValue();
187186
boolean bypassQueryAnalysis = cryptOptions.getBoolean("bypassQueryAnalysis", BsonBoolean.FALSE).getValue();
187+
Long keyExpirationMS = cryptOptions.containsKey("keyExpirationMS")
188+
? cryptOptions.getNumber("keyExpirationMS").longValue() : null;
188189

189190
Map<String, BsonDocument> namespaceToSchemaMap = new HashMap<>();
190191

@@ -285,6 +286,7 @@ public void setUp() {
285286
.bypassQueryAnalysis(bypassQueryAnalysis)
286287
.bypassAutoEncryption(bypassAutoEncryption)
287288
.extraOptions(extraOptions)
289+
.keyExpiration(keyExpirationMS, TimeUnit.MILLISECONDS)
288290
.build());
289291
}
290292
createMongoClient(mongoClientSettingsBuilder.build());

driver-sync/src/test/functional/com/mongodb/client/JsonPoweredCrudTestHelper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,15 @@ BsonDocument getDatabaseWatchResult(final BsonDocument collectionOptions, final
11981198
}
11991199
}
12001200

1201+
BsonDocument wait(final BsonDocument options, final BsonDocument rawArguments, @Nullable final ClientSession clientSession) {
1202+
try {
1203+
Thread.sleep(rawArguments.getNumber("ms").longValue());
1204+
return new BsonDocument();
1205+
} catch (InterruptedException e) {
1206+
throw new RuntimeException(e);
1207+
}
1208+
}
1209+
12011210
Collation getCollation(final BsonDocument bsonCollation) {
12021211
Collation.Builder builder = Collation.builder();
12031212
if (bsonCollation.containsKey("locale")) {

driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,9 @@ private void initClientEncryption(final BsonDocument entity, final String id,
754754
case "kmsProviders":
755755
builder.kmsProviders(createKmsProvidersMap(entry.getValue().asDocument()));
756756
break;
757+
case "keyExpirationMS":
758+
builder.keyExpiration(entry.getValue().asNumber().longValue(), TimeUnit.MILLISECONDS);
759+
break;
757760
default:
758761
throw new UnsupportedOperationException("Unsupported client encryption option: " + entry.getKey());
759762
}

driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedClientEncryptionHelper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,10 @@ private static void setKmsProviderProperty(final Map<String, Object> kmsProvider
166166
}
167167

168168
if (explicitPropertySupplier == null) {
169-
throw new UnsupportedOperationException("Non-placeholder value is not supported for: " + key + " :: " + kmsProviderOptions.toJson());
169+
kmsProviderMap.put(key, kmsProviderOptions.get(key));
170+
} else {
171+
kmsProviderMap.put(key, explicitPropertySupplier.get());
170172
}
171-
kmsProviderMap.put(key, explicitPropertySupplier.get());
172173
}
173174
}
174175

driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public abstract class UnifiedTest {
109109
private static final Set<String> PRESTART_POOL_ASYNC_WORK_MANAGER_FILE_DESCRIPTIONS = Collections.singleton(
110110
"wait queue timeout errors include details about checked out connections");
111111

112-
private static final String MAX_SUPPORTED_SCHEMA_VERSION = "1.21";
112+
private static final String MAX_SUPPORTED_SCHEMA_VERSION = "1.22";
113113
private static final List<Integer> MAX_SUPPORTED_SCHEMA_VERSION_COMPONENTS = Arrays.stream(MAX_SUPPORTED_SCHEMA_VERSION.split("\\."))
114114
.map(Integer::parseInt)
115115
.collect(Collectors.toList());
@@ -265,6 +265,7 @@ public void setUp(
265265
skips(fileDescription, testDescription);
266266

267267
assumeTrue(isSupportedSchemaVersion(schemaVersion), format("Unsupported schema version %s", schemaVersion));
268+
268269
if (runOnRequirements != null) {
269270
assumeTrue(runOnRequirementsMet(runOnRequirements, getMongoClientSettings(), getServerVersion()),
270271
"Run-on requirements not met");

mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,17 @@ public interface mongocrypt_random_fn extends Callback {
486486
public static native void
487487
mongocrypt_setopt_bypass_query_analysis (mongocrypt_t crypt);
488488

489+
/**
490+
* Set the expiration time for the data encryption key cache. Defaults to 60 seconds if not set.
491+
*
492+
* @param crypt The @ref mongocrypt_t object to update
493+
* @param cache_expiration_ms if 0 the cache never expires
494+
* @return A boolean indicating success. If false, an error status is set.
495+
* @since 5.4
496+
*/
497+
public static native boolean
498+
mongocrypt_setopt_key_expiration (mongocrypt_t crypt, long cache_expiration_ms);
499+
489500
/**
490501
* Opt-into enabling sending multiple collection info documents.
491502
*

mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_crypto_hooks;
6868
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_enable_multiple_collinfo;
6969
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_encrypted_field_config_map;
70+
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_key_expiration;
7071
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_aws;
7172
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_local;
7273
import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_providers;
@@ -194,6 +195,11 @@ class MongoCryptImpl implements MongoCrypt {
194195
mongocrypt_setopt_bypass_query_analysis(wrapped);
195196
}
196197

198+
Long keyExpirationMS = options.getKeyExpirationMS();
199+
if (keyExpirationMS != null) {
200+
configure(() -> mongocrypt_setopt_key_expiration(wrapped, keyExpirationMS));
201+
}
202+
197203
if (options.getEncryptedFieldsMap() != null) {
198204
BsonDocument localEncryptedFieldsMap = new BsonDocument();
199205
localEncryptedFieldsMap.putAll(options.getEncryptedFieldsMap());

0 commit comments

Comments
 (0)