From 7443cc4b4d6b0aaa370cb0c79f3b3d6060c35cd9 Mon Sep 17 00:00:00 2001 From: Michael Moen Allport Date: Wed, 22 Oct 2025 15:30:51 +0200 Subject: [PATCH 1/3] Cache KMS keys --- Makefile | 7 ++- pom.xml | 6 +++ .../ssb/dlp/pseudo/core/func/PseudoFuncs.java | 49 ++++++++++++++----- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index dad8103..b3f92c6 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,12 @@ release-dryrun: ## Simulate a release in order to detect any issues .PHONY: release release: ## Release a new version. Update POMs and tag the new version in git - git push origin master:release + @set -e ; \ + git checkout master && \ + git pull && \ + git checkout release && \ + git merge master && \ + git push .PHONY: help help: diff --git a/pom.xml b/pom.xml index 39839d3..a4ad948 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ 1.8.0 2.9.1 2.11.5 + 3.2.2 2.1.4 @@ -152,6 +153,11 @@ univocity-parsers ${univocity-parsers.version} + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + ch.qos.logback logback-classic diff --git a/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java b/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java index f790462..41aac85 100644 --- a/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java +++ b/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java @@ -1,12 +1,11 @@ package no.ssb.dlp.pseudo.core.func; -import com.google.crypto.tink.DeterministicAead; -import com.google.crypto.tink.JsonKeysetReader; -import com.google.crypto.tink.KeysetHandle; -import com.google.crypto.tink.KmsClients; +import com.github.benmanes.caffeine.cache.Cache; +import com.google.crypto.tink.*; import no.ssb.crypto.tink.fpe.Fpe; import no.ssb.dapla.dlp.pseudo.func.PseudoFunc; import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig; +import no.ssb.dapla.dlp.pseudo.func.PseudoFuncException; import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory; import no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFunc; import no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFuncConfig; @@ -23,7 +22,10 @@ import no.ssb.dlp.pseudo.core.field.FieldDescriptor; import no.ssb.dlp.pseudo.core.tink.model.EncryptedKeysetWrapper; import no.ssb.dlp.pseudo.core.util.Json; +import com.github.benmanes.caffeine.cache.Caffeine; +import java.security.GeneralSecurityException; +import java.time.Duration; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -31,6 +33,11 @@ public class PseudoFuncs { private final Map ruleToFuncMap = new LinkedHashMap<>(); + private final Cache aeadCache = + Caffeine.newBuilder() + .maximumSize(2000) + .expireAfterWrite(Duration.ofMinutes(2)) + .build(); //TODO: Validate that all required secrets are available @@ -41,7 +48,7 @@ public PseudoFuncs(Collection rules, Collection ps } // TODO: Move these init functions elsewhere? - static Map initPseudoFuncConfigs(Collection pseudoRules, + Map initPseudoFuncConfigs(Collection pseudoRules, Collection pseudoSecrets, Collection pseudoKeysets) { @@ -70,7 +77,7 @@ static Map initPseudoFuncConfigs(Collection pseudoKeysetMap, Map pseudoSecretsMap, Collection pseudoSecrets) { @@ -86,7 +93,7 @@ private static void enrichMapAndEncryptFunc(PseudoFuncConfig funcConfig, } } - private static void enrichLegacyFpeFuncConfig(PseudoFuncConfig funcConfig, Map pseudoSecretsMap) { + private void enrichLegacyFpeFuncConfig(PseudoFuncConfig funcConfig, Map pseudoSecretsMap) { String secretId = funcConfig.getRequired(FpeFuncConfig.Param.KEY_ID, String.class); if (! pseudoSecretsMap.containsKey(secretId)) { throw new PseudoException("No secret found for FPE pseudo func with " + FpeFuncConfig.Param.KEY_ID + "=" + secretId); @@ -94,7 +101,7 @@ private static void enrichLegacyFpeFuncConfig(PseudoFuncConfig funcConfig, Map keysetMap, Collection pseudoSecrets) { + private void enrichTinkDaeadFuncConfig(PseudoFuncConfig funcConfig, Map keysetMap, Collection pseudoSecrets) { String dekId = funcConfig.getRequired(TinkDaeadFuncConfig.Param.KEY_ID, String.class); // TODO: Support creating new key material instead of failing if none found? @@ -116,9 +123,19 @@ private static void enrichTinkDaeadFuncConfig(PseudoFuncConfig funcConfig, Map { + try { + return KmsClients.get(keyUri).getAead(keyUri); + } catch (GeneralSecurityException e) { + throw new PseudoFuncConfigException("Error fetching key from KMS:", e); + } + } + )).orElseThrow(() -> new PseudoFuncException("Key material with URI " + keyUri + " not found in cache")); + KeysetHandle keysetHandle = KeysetHandle.read( JsonKeysetReader.withString(keyset.toJson()), - KmsClients.get(keyUri).getAead(keyUri) + masterKey ); DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class); @@ -129,7 +146,7 @@ private static void enrichTinkDaeadFuncConfig(PseudoFuncConfig funcConfig, Map keysetMap, Collection pseudoSecrets) { + private void enrichTinkFpeFuncConfig(PseudoFuncConfig funcConfig, Map keysetMap, Collection pseudoSecrets) { String dekId = funcConfig.getRequired(TinkFpeFuncConfig.Param.KEY_ID, String.class); // TODO: Support creating new key material instead of failing if none found? @@ -151,9 +168,19 @@ private static void enrichTinkFpeFuncConfig(PseudoFuncConfig funcConfig, Map { + try { + return KmsClients.get(keyUri).getAead(keyUri); + } catch (GeneralSecurityException e) { + throw new PseudoFuncConfigException("Error fetching key from KMS:", e); + } + } + )).orElseThrow(() -> new PseudoFuncException("Key material with URI " + keyUri + " not found in cache")); + KeysetHandle keysetHandle = KeysetHandle.read( JsonKeysetReader.withString(keyset.toJson()), - KmsClients.get(keyUri).getAead(keyUri) + masterKey ); Fpe fpe = keysetHandle.getPrimitive(Fpe.class); From 448b25ab4ceb9a759c79d833de6d60501678600d Mon Sep 17 00:00:00 2001 From: Michael Moen Allport Date: Wed, 22 Oct 2025 15:30:51 +0200 Subject: [PATCH 2/3] Cache KMS keys From 30e0a02a7949825681c7e46e9b00f4853899bb0c Mon Sep 17 00:00:00 2001 From: Michael Moen Allport Date: Mon, 27 Oct 2025 12:11:59 +0100 Subject: [PATCH 3/3] Use cache --- pom.xml | 4 +-- .../no/ssb/dlp/pseudo/core/PseudoSecret.java | 2 +- .../pseudo/core/field/FieldPseudonymizer.java | 13 ++++++-- .../ssb/dlp/pseudo/core/func/PseudoFuncs.java | 31 +++++-------------- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index a4ad948..379fa2e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 no.ssb.dapla.dlp.pseudo dapla-dlp-pseudo-core - 2.0.5-SNAPSHOT + 2.0.7-SNAPSHOT 21 @@ -14,7 +14,7 @@ statisticsnorway/dapla-dlp-pseudo-core - 1.3.2 + 1.3.3 32.0.0-jre 1.5.1 1.4.6 diff --git a/src/main/java/no/ssb/dlp/pseudo/core/PseudoSecret.java b/src/main/java/no/ssb/dlp/pseudo/core/PseudoSecret.java index 0933ad2..c4d6f5c 100644 --- a/src/main/java/no/ssb/dlp/pseudo/core/PseudoSecret.java +++ b/src/main/java/no/ssb/dlp/pseudo/core/PseudoSecret.java @@ -16,6 +16,7 @@ @Builder public class PseudoSecret implements Serializable { private String name; + private String id; private String version; private byte[] content; @@ -48,5 +49,4 @@ private static byte[] base64DecodedContentOf(byte[] base64EncodedContent) { throw new PseudoException("Invalid secret. Content must be base64 encoded."); } } - } diff --git a/src/main/java/no/ssb/dlp/pseudo/core/field/FieldPseudonymizer.java b/src/main/java/no/ssb/dlp/pseudo/core/field/FieldPseudonymizer.java index f63ed96..03f6ca6 100644 --- a/src/main/java/no/ssb/dlp/pseudo/core/field/FieldPseudonymizer.java +++ b/src/main/java/no/ssb/dlp/pseudo/core/field/FieldPseudonymizer.java @@ -1,5 +1,8 @@ package no.ssb.dlp.pseudo.core.field; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.google.crypto.tink.Aead; import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput; import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput; import no.ssb.dapla.dlp.pseudo.func.TransformDirection; @@ -71,7 +74,7 @@ private PseudoFuncOutput process(PseudoOperation operation, FieldDescriptor fiel public static class Builder { private Collection secrets; private Collection rules; - + private LoadingCache aeadCache; private Collection keysets; public Builder secrets(Collection secrets) { @@ -84,6 +87,11 @@ public Builder rules(Collection rules) { return this; } + public Builder aeadCache(LoadingCache aeadCache) { + this.aeadCache = aeadCache; + return this; + } + public Builder keysets(Collection keysets) { this.keysets = keysets; return this; @@ -92,7 +100,8 @@ public Builder keysets(Collection keysets) { public FieldPseudonymizer build() { Objects.requireNonNull(secrets, "PseudoSecrets can't be null"); Objects.requireNonNull(rules, "PseudoFuncRule collection can't be null"); - return new FieldPseudonymizer(new PseudoFuncs(rules, secrets, keysets)); + Objects.requireNonNull(aeadCache, "AeadCache can't be null"); + return new FieldPseudonymizer(new PseudoFuncs(rules, secrets, keysets, aeadCache)); } } } diff --git a/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java b/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java index 41aac85..a95d062 100644 --- a/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java +++ b/src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java @@ -1,6 +1,7 @@ package no.ssb.dlp.pseudo.core.func; import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.crypto.tink.*; import no.ssb.crypto.tink.fpe.Fpe; import no.ssb.dapla.dlp.pseudo.func.PseudoFunc; @@ -22,7 +23,6 @@ import no.ssb.dlp.pseudo.core.field.FieldDescriptor; import no.ssb.dlp.pseudo.core.tink.model.EncryptedKeysetWrapper; import no.ssb.dlp.pseudo.core.util.Json; -import com.github.benmanes.caffeine.cache.Caffeine; import java.security.GeneralSecurityException; import java.time.Duration; @@ -33,16 +33,13 @@ public class PseudoFuncs { private final Map ruleToFuncMap = new LinkedHashMap<>(); - private final Cache aeadCache = - Caffeine.newBuilder() - .maximumSize(2000) - .expireAfterWrite(Duration.ofMinutes(2)) - .build(); + private final LoadingCache aeadCache; //TODO: Validate that all required secrets are available public PseudoFuncs(Collection rules, Collection pseudoSecrets, - Collection keysets) { + Collection keysets, LoadingCache aeadCache) { + this.aeadCache = aeadCache; Map ruleToPseudoFuncConfigs = initPseudoFuncConfigs(rules, pseudoSecrets, keysets); rules.forEach(rule -> ruleToFuncMap.put(rule, PseudoFuncFactory.create(ruleToPseudoFuncConfigs.get(rule)))); } @@ -124,14 +121,8 @@ private void enrichTinkDaeadFuncConfig(PseudoFuncConfig funcConfig, Map { - try { - return KmsClients.get(keyUri).getAead(keyUri); - } catch (GeneralSecurityException e) { - throw new PseudoFuncConfigException("Error fetching key from KMS:", e); - } - } - )).orElseThrow(() -> new PseudoFuncException("Key material with URI " + keyUri + " not found in cache")); + Aead masterKey = Optional.ofNullable(this.aeadCache.get(keyUri)) + .orElseThrow(() -> new PseudoFuncException("Key material with URI " + keyUri + " not found in cache")); KeysetHandle keysetHandle = KeysetHandle.read( JsonKeysetReader.withString(keyset.toJson()), @@ -169,14 +160,8 @@ private void enrichTinkFpeFuncConfig(PseudoFuncConfig funcConfig, Map { - try { - return KmsClients.get(keyUri).getAead(keyUri); - } catch (GeneralSecurityException e) { - throw new PseudoFuncConfigException("Error fetching key from KMS:", e); - } - } - )).orElseThrow(() -> new PseudoFuncException("Key material with URI " + keyUri + " not found in cache")); + Aead masterKey = Optional.ofNullable(this.aeadCache.get(keyUri)) + .orElseThrow(() -> new PseudoFuncException("Key material with URI " + keyUri + " not found in cache")); KeysetHandle keysetHandle = KeysetHandle.read( JsonKeysetReader.withString(keyset.toJson()),