From 71a98558c6980ce7d53a246482cff74fee0bb546 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 29 Sep 2022 18:43:07 +0100 Subject: [PATCH 1/8] KeyStore ConfigSource --- .../AbstractLocationConfigSourceLoader.java | 8 +- pom.xml | 1 + sources/keystore/pom.xml | 37 ++++ .../source/keystore/KeyStoreConfig.java | 31 ++++ .../keystore/KeyStoreConfigSourceFactory.java | 161 ++++++++++++++++++ .../io.smallrye.config.ConfigSourceFactory | 1 + .../keystore/KeyStoreConfigSourceTest.java | 31 ++++ sources/keystore/src/test/resources/keystore | Bin 0 -> 378 bytes 8 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 sources/keystore/pom.xml create mode 100644 sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java create mode 100644 sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java create mode 100644 sources/keystore/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory create mode 100644 sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java create mode 100644 sources/keystore/src/test/resources/keystore diff --git a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java index 2345b51fe..e36e15c5e 100644 --- a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java +++ b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java @@ -257,7 +257,13 @@ private boolean validExtension(final Path fileName) { } private boolean validExtension(final String resourceName) { - for (String s : getFileExtensions()) { + String[] fileExtensions = getFileExtensions(); + + if (fileExtensions.length == 0) { + return true; + } + + for (String s : fileExtensions) { if (resourceName.endsWith(s)) { return true; } diff --git a/pom.xml b/pom.xml index 00269b0bb..06e334516 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ sources/file-system sources/yaml sources/zookeeper + sources/keystore converters/json utils/events utils/cdi-provider diff --git a/sources/keystore/pom.xml b/sources/keystore/pom.xml new file mode 100644 index 000000000..c3435360c --- /dev/null +++ b/sources/keystore/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + smallrye-config-parent + io.smallrye.config + 2.12.2-SNAPSHOT + ../../pom.xml + + + smallrye-config-source-keystore + + SmallRye: MicroProfile Config Source - KeyStore + + + + io.smallrye.config + smallrye-config + + + + + org.junit.jupiter + junit-jupiter + + + io.smallrye.testing + smallrye-testing-utilities + + + jakarta.annotation + jakarta.annotation-api + test + + + + diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java new file mode 100644 index 000000000..a1c3cc630 --- /dev/null +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java @@ -0,0 +1,31 @@ +package io.smallrye.config.source.keystore; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithParentName; + +import java.util.Map; +import java.util.Optional; + +@ConfigMapping(prefix = "io.smallrye.config.source.keystore") +public interface KeyStoreConfig { + @WithParentName + Map keystores(); + + interface KeyStore { + String path(); + + @WithDefault("PKCS12") + String type(); + + String password(); + + Map aliases(); + + interface Alias { + Optional name(); + + Optional password(); + } + } +} diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java new file mode 100644 index 000000000..feb8abf82 --- /dev/null +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java @@ -0,0 +1,161 @@ +package io.smallrye.config.source.keystore; + +import io.smallrye.config.AbstractLocationConfigSourceFactory; +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigSourceFactory; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.ConfigurableConfigSource; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.source.keystore.KeyStoreConfig.KeyStore.Alias; +import org.eclipse.microprofile.config.spi.ConfigSource; + +import java.io.IOException; +import java.net.URL; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class KeyStoreConfigSourceFactory implements ConfigSourceFactory { + @Override + public Iterable getConfigSources(final ConfigSourceContext context) { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(new ContextConfigSource(context)) + .withMapping(KeyStoreConfig.class) + .build(); + + KeyStoreConfig keyStoreConfig = config.getConfigMapping(KeyStoreConfig.class); + + List keyStoreSources = new ArrayList<>(); + for (Map.Entry keyStoreEntry : keyStoreConfig.keystores().entrySet()) { + KeyStoreConfig.KeyStore keyStore = keyStoreEntry.getValue(); + + keyStoreSources.add(new ConfigurableConfigSource(new AbstractLocationConfigSourceFactory() { + @Override + protected String[] getFileExtensions() { + return new String[0]; + } + + @Override + protected ConfigSource loadConfigSource(final URL url, final int ordinal) { + return new UrlKeyStoreConfigSource(url, ordinal).loadKeyStore(keyStore); + } + + @Override + public Iterable getConfigSources(final ConfigSourceContext context) { + return loadConfigSources(keyStore.path(), 100); + } + })); + } + + return keyStoreSources; + } + + private static class ContextConfigSource implements ConfigSource { + private final ConfigSourceContext context; + + public ContextConfigSource(final ConfigSourceContext context) { + this.context = context; + } + + @Override + public Set getPropertyNames() { + Set names = new HashSet<>(); + Iterator namesIterator = context.iterateNames(); + while (namesIterator.hasNext()) { + names.add(namesIterator.next()); + } + return names; + } + + @Override + public String getValue(final String propertyName) { + ConfigValue value = context.getValue(propertyName); + return value != null && value.getValue() != null ? value.getValue() : null; + } + + @Override + public String getName() { + return ContextConfigSource.class.getName(); + } + } + + private static class UrlKeyStoreConfigSource implements ConfigSource { + private final URL url; + private final int ordinal; + + UrlKeyStoreConfigSource(final URL url, final int ordinal) { + this.url = url; + this.ordinal = ordinal; + } + + ConfigSource loadKeyStore(KeyStoreConfig.KeyStore keyStoreConfig) { + try { + KeyStore keyStore = KeyStore.getInstance(keyStoreConfig.type()); + keyStore.load(url.openStream(), keyStoreConfig.password().toCharArray()); + + Map properties = new HashMap<>(); + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + Alias aliasConfig = keyStoreConfig.aliases().getOrDefault(alias, new Alias() { + @Override + public Optional name() { + return Optional.of(alias); + } + + @Override + public Optional password() { + return Optional.of(keyStoreConfig.password()); + } + }); + + if (keyStore.isKeyEntry(alias)) { + Key key = keyStore.getKey(alias, aliasConfig.password().orElse(keyStoreConfig.password()).toCharArray()); + properties.put(aliasConfig.name().orElse(alias), new String(key.getEncoded(), UTF_8)); + } else if (keyStore.isCertificateEntry(alias)) { + // TODO + } + } + return new PropertiesConfigSource(properties, this.getName(), this.getOrdinal()); + } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | UnrecoverableKeyException e) { + throw new RuntimeException(e); + } + } + + @Override + public int getOrdinal() { + return ordinal; + } + + @Override + public Set getPropertyNames() { + throw new UnsupportedOperationException(); + } + + @Override + public String getValue(final String propertyName) { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return "KeyStoreConfigSource[source=" + url.toString() + "]"; + } + } +} diff --git a/sources/keystore/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory b/sources/keystore/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory new file mode 100644 index 000000000..be558b2b6 --- /dev/null +++ b/sources/keystore/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory @@ -0,0 +1 @@ +io.smallrye.config.source.keystore.KeyStoreConfigSourceFactory diff --git a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java new file mode 100644 index 000000000..53c3581a0 --- /dev/null +++ b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java @@ -0,0 +1,31 @@ +package io.smallrye.config.source.keystore; + +import io.smallrye.config.ConfigValue; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class KeyStoreConfigSourceTest { + @Test + void keystore() throws Exception { + Map properties = new HashMap<>(); + // keytool -importpass -alias my.secret -keystore keystore -storepass secret -storetype PKCS12 -v + properties.put("io.smallrye.config.source.keystore.test.path", "keystore"); + properties.put("io.smallrye.config.source.keystore.test.password", "secret"); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); + + ConfigValue secret = config.getConfigValue("my.secret"); + assertEquals("secret", secret.getValue()); + } +} diff --git a/sources/keystore/src/test/resources/keystore b/sources/keystore/src/test/resources/keystore new file mode 100644 index 0000000000000000000000000000000000000000..10082892f1e0b1a31234436f99c0ec6eeea92364 GIT binary patch literal 378 zcmXqLVk~1~WHxAG)Mw+=YV&CO&dbQoxS)wqfu)I21}H26#3HCttUxJ7gT{XbjlbEr zp?Y|@7+DuIE;eXffGotcpm73AW3NGDmq8lb3|3x)NCOK5m&ZVxMP$m!kG?u8E&qL3 z4@oVqE8Z0;De;bpNnL=6QNe(RjRRr}6DKQ!fh-$mLYoI;Dl-eC7K=dot3#(I>^(Sp z;r_Bq_hNMwSrmlV%>N+Y8JDu^QO1&7KCi!}%Vu}=RV}V4H|rE)&1m>x=w_e@x13YN zP()6MA(x?&L64!BA(bJSp$JHpAS)3y6k!nx$;?evFf=nWF}5%?GBz+UFfy<+P&D9V zW7XzkW|CrMU=gWX`@;HiZoSCsz>az;%#3Xd0D@O^YXATM literal 0 HcmV?d00001 From 119e65ad5b48fdcb5b9f5612615798ee793266e5 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 30 Sep 2022 13:06:07 +0100 Subject: [PATCH 2/8] SecretKeys handler API --- .../SecretKeysConfigSourceInterceptor.java | 47 +++++++++++++++- .../io/smallrye/config/SecretKeysHandler.java | 10 ++++ .../config/SmallRyeConfigBuilder.java | 17 +++++- pom.xml | 1 + .../source/keystore/KeyStoreConfig.java | 10 ++-- .../keystore/KeyStoreConfigSourceFactory.java | 48 +++++++++++------ .../keystore/KeyStoreConfigSourceTest.java | 23 ++++---- utils/crypto/pom.xml | 43 +++++++++++++++ .../AESGCMNoPaddingSecretKeysHandler.java | 18 +++++++ .../io.smallrye.config.SecretKeysHandler | 1 + .../AESGCMNoPaddingSecretKeysHandlerTest.java | 50 ++++++++++++++++++ utils/crypto/src/test/resources/keystore | Bin 0 -> 378 bytes 12 files changed, 235 insertions(+), 33 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java create mode 100644 utils/crypto/pom.xml create mode 100644 utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java create mode 100644 utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler create mode 100644 utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java create mode 100644 utils/crypto/src/test/resources/keystore diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java index 0854a1ec9..05eefcce0 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java @@ -1,19 +1,36 @@ package io.smallrye.config; +import static io.smallrye.common.expression.Expression.Flag.DOUBLE_COLON; +import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX; +import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES; +import static io.smallrye.common.expression.Expression.Flag.NO_TRIM; + +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import jakarta.annotation.Priority; +import io.smallrye.common.expression.Expression; +import io.smallrye.common.expression.ResolveContext; + @Priority(Priorities.LIBRARY + 100) public class SecretKeysConfigSourceInterceptor implements ConfigSourceInterceptor { private static final long serialVersionUID = 7291982039729980590L; private final Set secrets; + private final Map handlers; public SecretKeysConfigSourceInterceptor(final Set secrets) { + this(secrets, Collections.emptyMap()); + } + + public SecretKeysConfigSourceInterceptor(final Set secrets, final Map handlers) { this.secrets = secrets; + this.handlers = handlers; } @Override @@ -21,7 +38,35 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final if (SecretKeys.isLocked() && isSecret(name)) { throw ConfigMessages.msg.notAllowed(name); } - return context.proceed(name); + ConfigValue configValue = context.proceed(name); + + if (configValue == null || handlers.isEmpty()) { + return configValue; + } + + // This executes before the Expression resolution. + // If we find a handler we execute it, if not we proceed + Expression expression = Expression.compile(configValue.getValue(), LENIENT_SYNTAX, NO_TRIM, NO_SMART_BRACES, + DOUBLE_COLON); + String secret = expression.evaluate(new BiConsumer, StringBuilder>() { + @Override + public void accept(final ResolveContext context, final StringBuilder stringBuilder) { + String[] split = context.getKey().split("::"); + if (split.length == 2) { + String key = split[0]; + String secret = split[1]; + + SecretKeysHandler handler = handlers.get(key); + if (handler != null) { + stringBuilder.append(handler.handleSecret(secret)); + } + } + + // TODO - handle errors + } + }); + + return secret != null && !secret.isEmpty() ? configValue.withValue(secret) : configValue; } @Override diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java b/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java new file mode 100644 index 000000000..381e929fa --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java @@ -0,0 +1,10 @@ +package io.smallrye.config; + +import io.smallrye.common.annotation.Experimental; + +@Experimental("") +public interface SecretKeysHandler { + String handleSecret(String secret); + + String getName(); +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 389fb88b8..96aa8299f 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -293,7 +293,22 @@ public OptionalInt getPriority() { return OptionalInt.of(Priorities.LIBRARY + 300); } })); - interceptors.add(new InterceptorWithPriority(new SecretKeysConfigSourceInterceptor(secretKeys))); + interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() { + @Override + public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { + Map discoveredHandlers = new HashMap<>(); + ServiceLoader secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader); + for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) { + discoveredHandlers.put(secretKeysHandler.getName(), secretKeysHandler); + } + return new SecretKeysConfigSourceInterceptor(secretKeys, discoveredHandlers); + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(Priorities.LIBRARY + 100); + } + })); return interceptors; } diff --git a/pom.xml b/pom.xml index 06e334516..5c13b506f 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,7 @@ converters/json utils/events utils/cdi-provider + utils/crypto testsuite documentation examples diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java index a1c3cc630..d236220f6 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java @@ -1,12 +1,12 @@ package io.smallrye.config.source.keystore; +import java.util.Map; +import java.util.Optional; + import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; import io.smallrye.config.WithParentName; -import java.util.Map; -import java.util.Optional; - @ConfigMapping(prefix = "io.smallrye.config.source.keystore") public interface KeyStoreConfig { @WithParentName @@ -20,12 +20,16 @@ interface KeyStore { String password(); + Optional algorithm(); + Map aliases(); interface Alias { Optional name(); Optional password(); + + Optional algorithm(); } } } diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java index feb8abf82..9b0ada0f6 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java @@ -1,15 +1,6 @@ package io.smallrye.config.source.keystore; -import io.smallrye.config.AbstractLocationConfigSourceFactory; -import io.smallrye.config.ConfigSourceContext; -import io.smallrye.config.ConfigSourceFactory; -import io.smallrye.config.ConfigValue; -import io.smallrye.config.ConfigurableConfigSource; -import io.smallrye.config.PropertiesConfigSource; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; -import io.smallrye.config.source.keystore.KeyStoreConfig.KeyStore.Alias; -import org.eclipse.microprofile.config.spi.ConfigSource; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.net.URL; @@ -29,15 +20,25 @@ import java.util.Optional; import java.util.Set; -import static java.nio.charset.StandardCharsets.UTF_8; +import org.eclipse.microprofile.config.spi.ConfigSource; + +import io.smallrye.config.AbstractLocationConfigSourceFactory; +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigSourceFactory; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.ConfigurableConfigSource; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.source.keystore.KeyStoreConfig.KeyStore.Alias; public class KeyStoreConfigSourceFactory implements ConfigSourceFactory { @Override public Iterable getConfigSources(final ConfigSourceContext context) { SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(new ContextConfigSource(context)) - .withMapping(KeyStoreConfig.class) - .build(); + .withSources(new ContextConfigSource(context)) + .withMapping(KeyStoreConfig.class) + .build(); KeyStoreConfig keyStoreConfig = config.getConfigMapping(KeyStoreConfig.class); @@ -123,17 +124,30 @@ public Optional name() { public Optional password() { return Optional.of(keyStoreConfig.password()); } + + @Override + public Optional algorithm() { + return keyStoreConfig.algorithm(); + } }); if (keyStore.isKeyEntry(alias)) { - Key key = keyStore.getKey(alias, aliasConfig.password().orElse(keyStoreConfig.password()).toCharArray()); - properties.put(aliasConfig.name().orElse(alias), new String(key.getEncoded(), UTF_8)); + Key key = keyStore.getKey(alias, + aliasConfig.password().orElse(keyStoreConfig.password()).toCharArray()); + String encoded; + if (aliasConfig.algorithm().isPresent()) { + encoded = "${" + aliasConfig.algorithm().get() + "::" + new String(key.getEncoded(), UTF_8) + "}"; + } else { + encoded = new String(key.getEncoded(), UTF_8); + } + properties.put(aliasConfig.name().orElse(alias), encoded); } else if (keyStore.isCertificateEntry(alias)) { // TODO } } return new PropertiesConfigSource(properties, this.getName(), this.getOrdinal()); - } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | UnrecoverableKeyException e) { + } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException + | UnrecoverableKeyException e) { throw new RuntimeException(e); } } diff --git a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java index 53c3581a0..e1959c634 100644 --- a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java +++ b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java @@ -1,29 +1,30 @@ package io.smallrye.config.source.keystore; -import io.smallrye.config.ConfigValue; -import io.smallrye.config.PropertiesConfigSource; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashMap; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +import io.smallrye.config.ConfigValue; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; class KeyStoreConfigSourceTest { @Test - void keystore() throws Exception { + void keystore() { Map properties = new HashMap<>(); // keytool -importpass -alias my.secret -keystore keystore -storepass secret -storetype PKCS12 -v properties.put("io.smallrye.config.source.keystore.test.path", "keystore"); properties.put("io.smallrye.config.source.keystore.test.password", "secret"); SmallRyeConfig config = new SmallRyeConfigBuilder() - .addDefaultInterceptors() - .addDiscoveredSources() - .withSources(new PropertiesConfigSource(properties, "", 0)) - .build(); + .addDefaultInterceptors() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); ConfigValue secret = config.getConfigValue("my.secret"); assertEquals("secret", secret.getValue()); diff --git a/utils/crypto/pom.xml b/utils/crypto/pom.xml new file mode 100644 index 000000000..f5cbaca70 --- /dev/null +++ b/utils/crypto/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + smallrye-config-parent + io.smallrye.config + 2.12.2-SNAPSHOT + ../../pom.xml + + + smallrye-config-crypto + + SmallRye Config: Crypto + + + + io.smallrye.config + smallrye-config + + + + + org.junit.jupiter + junit-jupiter + + + io.smallrye.testing + smallrye-testing-utilities + + + jakarta.annotation + jakarta.annotation-api + test + + + io.smallrye.config + smallrye-config-source-keystore + ${project.version} + test + + + + diff --git a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java new file mode 100644 index 000000000..cd6bbec9f --- /dev/null +++ b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java @@ -0,0 +1,18 @@ +package io.smallrye.config.crypto; + +import io.smallrye.config.SecretKeysHandler; + +public class AESGCMNoPaddingSecretKeysHandler implements SecretKeysHandler { + // TODO - Should we provide a way to configure the handler? + + @Override + public String handleSecret(final String secret) { + // TODO - handle implementation. + return "decoded"; + } + + @Override + public String getName() { + return "aes-gcm-nopadding"; + } +} diff --git a/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler b/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler new file mode 100644 index 000000000..dacc4ac5c --- /dev/null +++ b/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler @@ -0,0 +1 @@ +io.smallrye.config.crypto.AESGCMNoPaddingSecretKeysHandler diff --git a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java new file mode 100644 index 000000000..31e8a3ed2 --- /dev/null +++ b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java @@ -0,0 +1,50 @@ +package io.smallrye.config.crypto; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.smallrye.config.ConfigValue; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +class AESGCMNoPaddingSecretKeysHandlerTest { + @Test + void handler() { + Map properties = new HashMap<>(); + properties.put("my.secret", "${aes-gcm-nopadding::encoded}"); + properties.put("my.expression", "${not.found:default}"); + properties.put("another.expression", "${my.expression}"); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); + + assertEquals("decoded", config.getRawValue("my.secret")); + assertEquals("default", config.getRawValue("my.expression")); + assertEquals("default", config.getRawValue("another.expression")); + } + + @Test + void keystore() { + Map properties = new HashMap<>(); + properties.put("io.smallrye.config.source.keystore.test.path", "keystore"); + properties.put("io.smallrye.config.source.keystore.test.password", "secret"); + properties.put("io.smallrye.config.source.keystore.test.algorithm", "aes-gcm-nopadding"); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); + + ConfigValue secret = config.getConfigValue("my.secret"); + assertEquals("decoded", secret.getValue()); + } +} diff --git a/utils/crypto/src/test/resources/keystore b/utils/crypto/src/test/resources/keystore new file mode 100644 index 0000000000000000000000000000000000000000..10082892f1e0b1a31234436f99c0ec6eeea92364 GIT binary patch literal 378 zcmXqLVk~1~WHxAG)Mw+=YV&CO&dbQoxS)wqfu)I21}H26#3HCttUxJ7gT{XbjlbEr zp?Y|@7+DuIE;eXffGotcpm73AW3NGDmq8lb3|3x)NCOK5m&ZVxMP$m!kG?u8E&qL3 z4@oVqE8Z0;De;bpNnL=6QNe(RjRRr}6DKQ!fh-$mLYoI;Dl-eC7K=dot3#(I>^(Sp z;r_Bq_hNMwSrmlV%>N+Y8JDu^QO1&7KCi!}%Vu}=RV}V4H|rE)&1m>x=w_e@x13YN zP()6MA(x?&L64!BA(bJSp$JHpAS)3y6k!nx$;?evFf=nWF}5%?GBz+UFfy<+P&D9V zW7XzkW|CrMU=gWX`@;HiZoSCsz>az;%#3Xd0D@O^YXATM literal 0 HcmV?d00001 From a17ede802eb5f41416521e7994804abf81707ef3 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 30 Sep 2022 13:26:34 +0100 Subject: [PATCH 3/8] Add Jasypt SecretKeysHandler --- pom.xml | 1 + utils/jasypt/pom.xml | 42 +++++++++++++++++++ .../jasypt/JasyptSecretKeysHandler.java | 26 ++++++++++++ .../io.smallrye.config.SecretKeysHandler | 1 + .../jasypt/JasyptSecretKeysHandlerTest.java | 28 +++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 utils/jasypt/pom.xml create mode 100644 utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java create mode 100644 utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler create mode 100644 utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java diff --git a/pom.xml b/pom.xml index 5c13b506f..e45ec2497 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,7 @@ utils/events utils/cdi-provider utils/crypto + utils/jasypt testsuite documentation examples diff --git a/utils/jasypt/pom.xml b/utils/jasypt/pom.xml new file mode 100644 index 000000000..92c8b66b6 --- /dev/null +++ b/utils/jasypt/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + smallrye-config-parent + io.smallrye.config + 2.12.2-SNAPSHOT + ../../pom.xml + + + smallrye-config-jasypt + + SmallRye Config: Jasypt + + + + io.smallrye.config + smallrye-config + + + org.jasypt + jasypt + 1.9.3 + + + + + org.junit.jupiter + junit-jupiter + + + io.smallrye.testing + smallrye-testing-utilities + + + jakarta.annotation + jakarta.annotation-api + test + + + + diff --git a/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java new file mode 100644 index 000000000..aa80fd596 --- /dev/null +++ b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java @@ -0,0 +1,26 @@ +package io.smallrye.config.jasypt; + +import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; +import org.jasypt.iv.RandomIvGenerator; +import org.jasypt.properties.PropertyValueEncryptionUtils; + +import io.smallrye.config.SecretKeysHandler; + +public class JasyptSecretKeysHandler implements SecretKeysHandler { + @Override + public String handleSecret(final String secret) { + // TODO - We need to be able to configure this in the Handler. + // Option to configure it in the constructor or retrieve config on the fly? + StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); + encryptor.setPassword("jasypt"); + encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256"); + encryptor.setIvGenerator(new RandomIvGenerator()); + encryptor.initialize(); + return PropertyValueEncryptionUtils.decrypt(secret, encryptor); + } + + @Override + public String getName() { + return "jasypt"; + } +} diff --git a/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler b/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler new file mode 100644 index 000000000..d2338e798 --- /dev/null +++ b/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler @@ -0,0 +1 @@ +io.smallrye.config.jasypt.JasyptSecretKeysHandler diff --git a/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java b/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java new file mode 100644 index 000000000..c44acd6ab --- /dev/null +++ b/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java @@ -0,0 +1,28 @@ +package io.smallrye.config.jasypt; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +class JasyptSecretKeysHandlerTest { + @Test + void jasypt() { + Map properties = new HashMap<>(); + properties.put("my.secret", "${jasypt::ENC(wqp8zDeiCQ5JaFvwDtoAcr2WMLdlD0rjwvo8Rh0thG5qyTQVGxwJjBIiW26y0dtU)}"); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); + + assertEquals("12345678", config.getRawValue("my.secret")); + } +} From ca1e3bd9bad64ef655cd19217213afce8ce7c4c9 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 7 Nov 2022 17:39:52 +0000 Subject: [PATCH 4/8] Handler in ConfigValue --- .../java/io/smallrye/config/ConfigValue.java | 20 ++++++ .../ExpressionConfigSourceInterceptor.java | 35 +++++++---- .../java/io/smallrye/config/SecretKeys.java | 30 +++++++-- .../SecretKeysConfigSourceInterceptor.java | 61 ++----------------- .../io/smallrye/config/SecretKeysHandler.java | 2 +- ...retKeysHandlerConfigSourceInterceptor.java | 23 +++++++ .../config/SmallRyeConfigBuilder.java | 29 ++++++--- .../io/smallrye/config/SecretKeysTest.java | 1 - .../AESGCMNoPaddingSecretKeysHandler.java | 2 +- .../AESGCMNoPaddingSecretKeysHandlerTest.java | 2 +- .../jasypt/JasyptSecretKeysHandler.java | 2 +- 11 files changed, 124 insertions(+), 83 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java diff --git a/implementation/src/main/java/io/smallrye/config/ConfigValue.java b/implementation/src/main/java/io/smallrye/config/ConfigValue.java index f055db2bf..4300d711b 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigValue.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigValue.java @@ -32,6 +32,7 @@ public class ConfigValue implements org.eclipse.microprofile.config.ConfigValue private final int configSourcePosition; private final int lineNumber; + private final String extendedExpressionHandler; private final List problems; private ConfigValue(final ConfigValueBuilder builder) { @@ -43,6 +44,7 @@ private ConfigValue(final ConfigValueBuilder builder) { this.configSourceOrdinal = builder.configSourceOrdinal; this.configSourcePosition = builder.configSourcePosition; this.lineNumber = builder.lineNumber; + this.extendedExpressionHandler = builder.extendedExpressionHandler; this.problems = builder.problems; } @@ -99,6 +101,10 @@ public String getLocation() { return lineNumber != -1 ? configSourceName + ":" + lineNumber : configSourceName; } + public String getExtendedExpressionHandler() { + return extendedExpressionHandler; + } + List getProblems() { return Collections.unmodifiableList(problems); } @@ -131,6 +137,10 @@ public ConfigValue withLineNumber(final int lineNumber) { return from().withLineNumber(lineNumber).build(); } + public ConfigValue withExtendedExpressionHandler(final String extendedExpressionHandler) { + return from().withExtendedExpressionHandler(extendedExpressionHandler).build(); + } + public ConfigValue noProblems() { return from().noProblems().build(); } @@ -190,6 +200,7 @@ public ConfigValueBuilder from() { .withConfigSourceOrdinal(configSourceOrdinal) .withConfigSourcePosition(configSourcePosition) .withLineNumber(lineNumber) + .withExtendedExpressionHandler(extendedExpressionHandler) .withProblems(problems); } @@ -206,6 +217,7 @@ public static class ConfigValueBuilder { private int configSourceOrdinal; private int configSourcePosition; private int lineNumber = -1; + private String extendedExpressionHandler; private final List problems = new ArrayList<>(); public ConfigValueBuilder withName(final String name) { @@ -248,6 +260,11 @@ public ConfigValueBuilder withLineNumber(final int lineNumber) { return this; } + public ConfigValueBuilder withExtendedExpressionHandler(final String extendedExpressionHandler) { + this.extendedExpressionHandler = extendedExpressionHandler; + return this; + } + public ConfigValueBuilder noProblems() { this.problems.clear(); return this; @@ -264,6 +281,9 @@ public ConfigValueBuilder addProblem(final Problem problem) { } public ConfigValue build() { + if (!this.problems.isEmpty()) { + this.value = null; + } return new ConfigValue(this); } } diff --git a/implementation/src/main/java/io/smallrye/config/ExpressionConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/ExpressionConfigSourceInterceptor.java index 09b73ecef..1174736e0 100644 --- a/implementation/src/main/java/io/smallrye/config/ExpressionConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/ExpressionConfigSourceInterceptor.java @@ -1,18 +1,15 @@ package io.smallrye.config; +import static io.smallrye.common.expression.Expression.Flag.DOUBLE_COLON; import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX; import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES; import static io.smallrye.common.expression.Expression.Flag.NO_TRIM; import static io.smallrye.config.ConfigMessages.msg; -import java.util.ArrayList; -import java.util.List; import java.util.function.BiConsumer; import jakarta.annotation.Priority; -import org.eclipse.microprofile.config.Config; - import io.smallrye.common.expression.Expression; import io.smallrye.common.expression.ResolveContext; import io.smallrye.config.ConfigValidationException.Problem; @@ -39,7 +36,7 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final } private ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name, final int depth) { - if (depth == MAX_DEPTH) { + if (depth >= MAX_DEPTH) { throw msg.expressionExpansionTooDepth(name); } @@ -53,25 +50,39 @@ private ConfigValue getValue(final ConfigSourceInterceptorContext context, final return null; } - List problems = new ArrayList<>(); + ConfigValue.ConfigValueBuilder value = configValue.from(); Expression expression = Expression.compile(escapeDollarIfExists(configValue.getValue()), LENIENT_SYNTAX, NO_TRIM, - NO_SMART_BRACES); + NO_SMART_BRACES, DOUBLE_COLON); String expanded = expression.evaluate(new BiConsumer, StringBuilder>() { @Override public void accept(ResolveContext resolveContext, StringBuilder stringBuilder) { - ConfigValue resolve = getValue(context, resolveContext.getKey(), depth + 1); + String key = resolveContext.getKey(); + + // Requires a handler lookup + int index = key.indexOf("::"); + if (index != -1) { + value.withExtendedExpressionHandler(key.substring(0, index)); + stringBuilder.append(key, index + 2, key.length()); + return; + } + + // Expression lookup + ConfigValue resolve = getValue(context, key, depth + 1); if (resolve != null) { - stringBuilder.append(resolve.getValue()); - problems.addAll(resolve.getProblems()); + if (resolve.getProblems().isEmpty()) { + stringBuilder.append(resolve.getValue()); + } else { + value.withProblems(resolve.getProblems()); + } } else if (resolveContext.hasDefault()) { resolveContext.expandDefault(); } else { - problems.add(new Problem(msg.expandingElementNotFound(resolveContext.getKey(), configValue.getName()))); + value.addProblem(new Problem(msg.expandingElementNotFound(key, configValue.getName()))); } } }); - return problems.isEmpty() ? configValue.withValue(expanded) : configValue.withValue(null).withProblems(problems); + return value.withValue(expanded).build(); } /** diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeys.java b/implementation/src/main/java/io/smallrye/config/SecretKeys.java index 7ac20cdbb..a3cf8346b 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeys.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeys.java @@ -1,18 +1,40 @@ package io.smallrye.config; +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; import java.util.function.Supplier; @SuppressWarnings("squid:S5164") -public final class SecretKeys { +public final class SecretKeys implements Serializable { + private static final long serialVersionUID = -3226034787747746735L; + private static final ThreadLocal LOCKED = new ThreadLocal<>(); - private SecretKeys() { - throw new UnsupportedOperationException(); + private final Set secrets; + private final Map handlers; + + SecretKeys(final Set secrets, final Map handlers) { + this.secrets = secrets; + this.handlers = handlers; + } + + public String getSecretValue(final String handlerName, final String secretName) { + SecretKeysHandler handler = handlers.get(handlerName); + if (handler != null) { + return handler.decode(secretName); + } + throw new NoSuchElementException(); + } + + public boolean secretExistsWithName(final String secretName) { + return secrets.contains(secretName); } public static boolean isLocked() { Boolean result = LOCKED.get(); - return result == null ? true : result; + return result == null || result; } public static void doUnlocked(Runnable runnable) { diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java index 05eefcce0..b6bd91a5e 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java @@ -1,72 +1,27 @@ package io.smallrye.config; -import static io.smallrye.common.expression.Expression.Flag.DOUBLE_COLON; -import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX; -import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES; -import static io.smallrye.common.expression.Expression.Flag.NO_TRIM; - -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; import jakarta.annotation.Priority; -import io.smallrye.common.expression.Expression; -import io.smallrye.common.expression.ResolveContext; - @Priority(Priorities.LIBRARY + 100) public class SecretKeysConfigSourceInterceptor implements ConfigSourceInterceptor { private static final long serialVersionUID = 7291982039729980590L; - private final Set secrets; - private final Map handlers; - - public SecretKeysConfigSourceInterceptor(final Set secrets) { - this(secrets, Collections.emptyMap()); - } + private final SecretKeys secrets; - public SecretKeysConfigSourceInterceptor(final Set secrets, final Map handlers) { + public SecretKeysConfigSourceInterceptor(final SecretKeys secrets) { this.secrets = secrets; - this.handlers = handlers; } @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { - if (SecretKeys.isLocked() && isSecret(name)) { + if (secrets.secretExistsWithName(name) && SecretKeys.isLocked()) { throw ConfigMessages.msg.notAllowed(name); } - ConfigValue configValue = context.proceed(name); - - if (configValue == null || handlers.isEmpty()) { - return configValue; - } - - // This executes before the Expression resolution. - // If we find a handler we execute it, if not we proceed - Expression expression = Expression.compile(configValue.getValue(), LENIENT_SYNTAX, NO_TRIM, NO_SMART_BRACES, - DOUBLE_COLON); - String secret = expression.evaluate(new BiConsumer, StringBuilder>() { - @Override - public void accept(final ResolveContext context, final StringBuilder stringBuilder) { - String[] split = context.getKey().split("::"); - if (split.length == 2) { - String key = split[0]; - String secret = split[1]; - - SecretKeysHandler handler = handlers.get(key); - if (handler != null) { - stringBuilder.append(handler.handleSecret(secret)); - } - } - - // TODO - handle errors - } - }); - - return secret != null && !secret.isEmpty() ? configValue.withValue(secret) : configValue; + return context.proceed(name); } @Override @@ -76,7 +31,7 @@ public Iterator iterateNames(final ConfigSourceInterceptorContext contex Iterator namesIterator = context.iterateNames(); while (namesIterator.hasNext()) { String name = namesIterator.next(); - if (!secrets.contains(name)) { + if (!secrets.secretExistsWithName(name)) { names.add(name); } } @@ -92,7 +47,7 @@ public Iterator iterateValues(final ConfigSourceInterceptorContext Iterator valuesIterator = context.iterateValues(); while (valuesIterator.hasNext()) { ConfigValue value = valuesIterator.next(); - if (!secrets.contains(value.getName())) { + if (!secrets.secretExistsWithName(value.getName())) { values.add(value); } } @@ -100,8 +55,4 @@ public Iterator iterateValues(final ConfigSourceInterceptorContext } return context.iterateValues(); } - - private boolean isSecret(final String name) { - return secrets.contains(name); - } } diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java b/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java index 381e929fa..59b7fc57d 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysHandler.java @@ -4,7 +4,7 @@ @Experimental("") public interface SecretKeysHandler { - String handleSecret(String secret); + String decode(String secret); String getName(); } diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java new file mode 100644 index 000000000..33b0a5aed --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java @@ -0,0 +1,23 @@ +package io.smallrye.config; + +public class SecretKeysHandlerConfigSourceInterceptor implements ConfigSourceInterceptor { + private static final long serialVersionUID = -5228028387733656005L; + + private final SecretKeys secretKeys; + + public SecretKeysHandlerConfigSourceInterceptor(final SecretKeys secretKeys) { + this.secretKeys = secretKeys; + } + + @Override + public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { + ConfigValue configValue = context.proceed(name); + if (configValue != null && configValue.getValue() != null) { + String handler = configValue.getExtendedExpressionHandler(); + if (handler != null) { + return configValue.withValue(secretKeys.getSecretValue(handler, configValue.getValue())); + } + } + return configValue; + } +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 96aa8299f..ec8769f57 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -283,7 +283,7 @@ public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorConte boolean expressions = true; ConfigValue expressionsValue = context.proceed(Config.PROPERTY_EXPRESSIONS_ENABLED); if (expressionsValue != null) { - expressions = Boolean.valueOf(expressionsValue.getValue()); + expressions = Boolean.parseBoolean(expressionsValue.getValue()); } return new ExpressionConfigSourceInterceptor(expressions); } @@ -293,15 +293,19 @@ public OptionalInt getPriority() { return OptionalInt.of(Priorities.LIBRARY + 300); } })); + + Map discoveredHandlers = new HashMap<>(); + ServiceLoader secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader); + for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) { + discoveredHandlers.put(secretKeysHandler.getName(), secretKeysHandler); + } + + SecretKeys secretKeys = new SecretKeys(this.secretKeys, discoveredHandlers); + interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() { @Override public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { - Map discoveredHandlers = new HashMap<>(); - ServiceLoader secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader); - for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) { - discoveredHandlers.put(secretKeysHandler.getName(), secretKeysHandler); - } - return new SecretKeysConfigSourceInterceptor(secretKeys, discoveredHandlers); + return new SecretKeysConfigSourceInterceptor(secretKeys); } @Override @@ -309,6 +313,17 @@ public OptionalInt getPriority() { return OptionalInt.of(Priorities.LIBRARY + 100); } })); + interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() { + @Override + public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { + return new SecretKeysHandlerConfigSourceInterceptor(secretKeys); + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(Priorities.LIBRARY + 310); + } + })); return interceptors; } diff --git a/implementation/src/test/java/io/smallrye/config/SecretKeysTest.java b/implementation/src/test/java/io/smallrye/config/SecretKeysTest.java index a279c469a..f192fbeb0 100644 --- a/implementation/src/test/java/io/smallrye/config/SecretKeysTest.java +++ b/implementation/src/test/java/io/smallrye/config/SecretKeysTest.java @@ -92,7 +92,6 @@ void mapping() { private static Config buildConfig(String... keyValues) { return new SmallRyeConfigBuilder() - .addDefaultSources() .addDefaultInterceptors() .withSources(KeyValuesConfigSource.config(keyValues)) .withSecretKeys("secret") diff --git a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java index cd6bbec9f..25d635dc3 100644 --- a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java +++ b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java @@ -6,7 +6,7 @@ public class AESGCMNoPaddingSecretKeysHandler implements SecretKeysHandler { // TODO - Should we provide a way to configure the handler? @Override - public String handleSecret(final String secret) { + public String decode(final String secret) { // TODO - handle implementation. return "decoded"; } diff --git a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java index 31e8a3ed2..4de43fd59 100644 --- a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java +++ b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java @@ -16,7 +16,7 @@ class AESGCMNoPaddingSecretKeysHandlerTest { @Test void handler() { Map properties = new HashMap<>(); - properties.put("my.secret", "${aes-gcm-nopadding::encoded}"); + properties.put("my.secret", "${aes-gcm-nopadding::encoded::x}"); properties.put("my.expression", "${not.found:default}"); properties.put("another.expression", "${my.expression}"); diff --git a/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java index aa80fd596..e970dc3e0 100644 --- a/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java +++ b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java @@ -8,7 +8,7 @@ public class JasyptSecretKeysHandler implements SecretKeysHandler { @Override - public String handleSecret(final String secret) { + public String decode(final String secret) { // TODO - We need to be able to configure this in the Handler. // Option to configure it in the constructor or retrieve config on the fly? StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); From c1f21d02d068a94370f945c6088a777a53ab91f9 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 19 Jan 2023 17:24:11 +0000 Subject: [PATCH 5/8] Updated POMs versions --- pom.xml | 2 +- sources/keystore/pom.xml | 2 +- .../keystore/KeyStoreConfigSourceFactory.java | 55 ++----------------- .../keystore/KeyStoreConfigSourceTest.java | 32 +++++++++-- utils/crypto/pom.xml | 2 +- utils/jasypt/pom.xml | 2 +- 6 files changed, 38 insertions(+), 57 deletions(-) diff --git a/pom.xml b/pom.xml index e45ec2497..4ba00e5c4 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 3.0.2 - 2.0.0 + 2.1.0 9.4 2.3.0 diff --git a/sources/keystore/pom.xml b/sources/keystore/pom.xml index c3435360c..66a69504a 100644 --- a/sources/keystore/pom.xml +++ b/sources/keystore/pom.xml @@ -4,7 +4,7 @@ smallrye-config-parent io.smallrye.config - 2.12.2-SNAPSHOT + 3.1.4-SNAPSHOT ../../pom.xml diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java index 9b0ada0f6..0d44a5130 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java @@ -13,8 +13,6 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,28 +22,17 @@ import io.smallrye.config.AbstractLocationConfigSourceFactory; import io.smallrye.config.ConfigSourceContext; -import io.smallrye.config.ConfigSourceFactory; -import io.smallrye.config.ConfigValue; +import io.smallrye.config.ConfigSourceFactory.ConfigurableConfigSourceFactory; import io.smallrye.config.ConfigurableConfigSource; import io.smallrye.config.PropertiesConfigSource; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; import io.smallrye.config.source.keystore.KeyStoreConfig.KeyStore.Alias; -public class KeyStoreConfigSourceFactory implements ConfigSourceFactory { +public class KeyStoreConfigSourceFactory implements ConfigurableConfigSourceFactory { @Override - public Iterable getConfigSources(final ConfigSourceContext context) { - SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(new ContextConfigSource(context)) - .withMapping(KeyStoreConfig.class) - .build(); - - KeyStoreConfig keyStoreConfig = config.getConfigMapping(KeyStoreConfig.class); - + public Iterable getConfigSources(final ConfigSourceContext context, KeyStoreConfig keyStoreConfig) { List keyStoreSources = new ArrayList<>(); for (Map.Entry keyStoreEntry : keyStoreConfig.keystores().entrySet()) { KeyStoreConfig.KeyStore keyStore = keyStoreEntry.getValue(); - keyStoreSources.add(new ConfigurableConfigSource(new AbstractLocationConfigSourceFactory() { @Override protected String[] getFileExtensions() { @@ -53,7 +40,7 @@ protected String[] getFileExtensions() { } @Override - protected ConfigSource loadConfigSource(final URL url, final int ordinal) { + protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { return new UrlKeyStoreConfigSource(url, ordinal).loadKeyStore(keyStore); } @@ -67,35 +54,6 @@ public Iterable getConfigSources(final ConfigSourceContext context return keyStoreSources; } - private static class ContextConfigSource implements ConfigSource { - private final ConfigSourceContext context; - - public ContextConfigSource(final ConfigSourceContext context) { - this.context = context; - } - - @Override - public Set getPropertyNames() { - Set names = new HashSet<>(); - Iterator namesIterator = context.iterateNames(); - while (namesIterator.hasNext()) { - names.add(namesIterator.next()); - } - return names; - } - - @Override - public String getValue(final String propertyName) { - ConfigValue value = context.getValue(propertyName); - return value != null && value.getValue() != null ? value.getValue() : null; - } - - @Override - public String getName() { - return ContextConfigSource.class.getName(); - } - } - private static class UrlKeyStoreConfigSource implements ConfigSource { private final URL url; private final int ordinal; @@ -105,7 +63,7 @@ private static class UrlKeyStoreConfigSource implements ConfigSource { this.ordinal = ordinal; } - ConfigSource loadKeyStore(KeyStoreConfig.KeyStore keyStoreConfig) { + ConfigSource loadKeyStore(KeyStoreConfig.KeyStore keyStoreConfig) throws IOException { try { KeyStore keyStore = KeyStore.getInstance(keyStoreConfig.type()); keyStore.load(url.openStream(), keyStoreConfig.password().toCharArray()); @@ -146,8 +104,7 @@ public Optional algorithm() { } } return new PropertiesConfigSource(properties, this.getName(), this.getOrdinal()); - } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException - | UnrecoverableKeyException e) { + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException e) { throw new RuntimeException(e); } } diff --git a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java index e1959c634..1cb782bfb 100644 --- a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java +++ b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java @@ -1,8 +1,9 @@ package io.smallrye.config.source.keystore; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; -import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -15,12 +16,13 @@ class KeyStoreConfigSourceTest { @Test void keystore() { - Map properties = new HashMap<>(); // keytool -importpass -alias my.secret -keystore keystore -storepass secret -storetype PKCS12 -v - properties.put("io.smallrye.config.source.keystore.test.path", "keystore"); - properties.put("io.smallrye.config.source.keystore.test.password", "secret"); + Map properties = Map.of( + "io.smallrye.config.source.keystore.test.path", "keystore", + "io.smallrye.config.source.keystore.test.password", "secret"); SmallRyeConfig config = new SmallRyeConfigBuilder() + .withProfile("prod") .addDefaultInterceptors() .addDiscoveredSources() .withSources(new PropertiesConfigSource(properties, "", 0)) @@ -29,4 +31,26 @@ void keystore() { ConfigValue secret = config.getConfigValue("my.secret"); assertEquals("secret", secret.getValue()); } + + @Test + void keyStoreNotFound() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withProfile("prod") + .addDefaultInterceptors() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(Map.of( + "io.smallrye.config.source.keystore.test.path", "not.found", + "io.smallrye.config.source.keystore.test.password", "secret"), "", 0)) + .build(); + + ConfigValue secret = config.getConfigValue("my.secret"); + assertNull(secret.getValue()); + + assertThrows(IllegalStateException.class, () -> new SmallRyeConfigBuilder() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(Map.of( + "io.smallrye.config.source.keystore.test.path", "file:/not.found", + "io.smallrye.config.source.keystore.test.password", "secret"), "", 0)) + .build()); + } } diff --git a/utils/crypto/pom.xml b/utils/crypto/pom.xml index f5cbaca70..756b7481b 100644 --- a/utils/crypto/pom.xml +++ b/utils/crypto/pom.xml @@ -4,7 +4,7 @@ smallrye-config-parent io.smallrye.config - 2.12.2-SNAPSHOT + 3.1.4-SNAPSHOT ../../pom.xml diff --git a/utils/jasypt/pom.xml b/utils/jasypt/pom.xml index 92c8b66b6..4823bff13 100644 --- a/utils/jasypt/pom.xml +++ b/utils/jasypt/pom.xml @@ -4,7 +4,7 @@ smallrye-config-parent io.smallrye.config - 2.12.2-SNAPSHOT + 3.1.4-SNAPSHOT ../../pom.xml From a9e736603227c6f6bdd1e3cef375c9a8396f9c72 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 8 Mar 2023 19:09:05 +0000 Subject: [PATCH 6/8] Support configurable SecretKeyHandlers --- .../io/smallrye/config/ConfigMessages.java | 3 + .../java/io/smallrye/config/SecretKeys.java | 23 ----- .../SecretKeysConfigSourceInterceptor.java | 10 +- ...retKeysHandlerConfigSourceInterceptor.java | 22 ++++- .../config/SecretKeysHandlerFactory.java | 10 ++ .../config/SmallRyeConfigBuilder.java | 89 +++++++++++++++--- .../config/SecretKeysHandlerTest.java | 41 ++++++++ .../AESGCMNoPaddingSecretKeysHandler.java | 40 +++++++- ...SGCMNoPaddingSecretKeysHandlerFactory.java | 30 ++++++ .../io.smallrye.config.SecretKeysHandler | 1 - ...o.smallrye.config.SecretKeysHandlerFactory | 1 + .../AESGCMNoPaddingSecretKeysHandlerTest.java | 41 ++++++-- utils/crypto/src/test/resources/keystore | Bin 378 -> 443 bytes .../jasypt/JasyptSecretKeysHandler.java | 17 ++-- .../JasyptSecretKeysHandlerFactory.java | 31 ++++++ .../io.smallrye.config.SecretKeysHandler | 1 - ...o.smallrye.config.SecretKeysHandlerFactory | 1 + .../jasypt/JasyptSecretKeysHandlerTest.java | 9 +- 18 files changed, 301 insertions(+), 69 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/SecretKeysHandlerFactory.java create mode 100644 implementation/src/test/java/io/smallrye/config/SecretKeysHandlerTest.java create mode 100644 utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java delete mode 100644 utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler create mode 100644 utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory create mode 100644 utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerFactory.java delete mode 100644 utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler create mode 100644 utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMessages.java b/implementation/src/main/java/io/smallrye/config/ConfigMessages.java index 83f646174..f0cc0ef10 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMessages.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMessages.java @@ -154,4 +154,7 @@ IllegalArgumentException converterException(@Cause Throwable converterException, @Message(id = 45, value = "The %s class is not a ConfigMapping") IllegalArgumentException classIsNotAMapping(Class type); + + @Message(id = 46, value = "Could not find a secret key handler for %s") + NoSuchElementException secretKeyHandlerNotFound(String handler); } diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeys.java b/implementation/src/main/java/io/smallrye/config/SecretKeys.java index a3cf8346b..be0371312 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeys.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeys.java @@ -1,9 +1,6 @@ package io.smallrye.config; import java.io.Serializable; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; import java.util.function.Supplier; @SuppressWarnings("squid:S5164") @@ -12,26 +9,6 @@ public final class SecretKeys implements Serializable { private static final ThreadLocal LOCKED = new ThreadLocal<>(); - private final Set secrets; - private final Map handlers; - - SecretKeys(final Set secrets, final Map handlers) { - this.secrets = secrets; - this.handlers = handlers; - } - - public String getSecretValue(final String handlerName, final String secretName) { - SecretKeysHandler handler = handlers.get(handlerName); - if (handler != null) { - return handler.decode(secretName); - } - throw new NoSuchElementException(); - } - - public boolean secretExistsWithName(final String secretName) { - return secrets.contains(secretName); - } - public static boolean isLocked() { Boolean result = LOCKED.get(); return result == null || result; diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java index b6bd91a5e..8140e8fc1 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysConfigSourceInterceptor.java @@ -10,15 +10,15 @@ public class SecretKeysConfigSourceInterceptor implements ConfigSourceInterceptor { private static final long serialVersionUID = 7291982039729980590L; - private final SecretKeys secrets; + private final Set secrets; - public SecretKeysConfigSourceInterceptor(final SecretKeys secrets) { + public SecretKeysConfigSourceInterceptor(final Set secrets) { this.secrets = secrets; } @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { - if (secrets.secretExistsWithName(name) && SecretKeys.isLocked()) { + if (SecretKeys.isLocked() && secrets.contains(name)) { throw ConfigMessages.msg.notAllowed(name); } return context.proceed(name); @@ -31,7 +31,7 @@ public Iterator iterateNames(final ConfigSourceInterceptorContext contex Iterator namesIterator = context.iterateNames(); while (namesIterator.hasNext()) { String name = namesIterator.next(); - if (!secrets.secretExistsWithName(name)) { + if (!secrets.contains(name)) { names.add(name); } } @@ -47,7 +47,7 @@ public Iterator iterateValues(final ConfigSourceInterceptorContext Iterator valuesIterator = context.iterateValues(); while (valuesIterator.hasNext()) { ConfigValue value = valuesIterator.next(); - if (!secrets.secretExistsWithName(value.getName())) { + if (!secrets.contains(value.getName())) { values.add(value); } } diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java index 33b0a5aed..0eacabfd5 100644 --- a/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerConfigSourceInterceptor.java @@ -1,12 +1,18 @@ package io.smallrye.config; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class SecretKeysHandlerConfigSourceInterceptor implements ConfigSourceInterceptor { private static final long serialVersionUID = -5228028387733656005L; - private final SecretKeys secretKeys; + private final Map handlers = new HashMap<>(); - public SecretKeysHandlerConfigSourceInterceptor(final SecretKeys secretKeys) { - this.secretKeys = secretKeys; + public SecretKeysHandlerConfigSourceInterceptor(final List handlers) { + for (SecretKeysHandler handler : handlers) { + this.handlers.put(handler.getName(), handler); + } } @Override @@ -15,9 +21,17 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final if (configValue != null && configValue.getValue() != null) { String handler = configValue.getExtendedExpressionHandler(); if (handler != null) { - return configValue.withValue(secretKeys.getSecretValue(handler, configValue.getValue())); + return configValue.withValue(getSecretValue(handler, configValue.getValue())); } } return configValue; } + + private String getSecretValue(final String handlerName, final String secretName) { + SecretKeysHandler handler = handlers.get(handlerName); + if (handler != null) { + return handler.decode(secretName); + } + throw ConfigMessages.msg.secretKeyHandlerNotFound(handlerName); + } } diff --git a/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerFactory.java b/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerFactory.java new file mode 100644 index 000000000..80f61fc22 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/SecretKeysHandlerFactory.java @@ -0,0 +1,10 @@ +package io.smallrye.config; + +import io.smallrye.common.annotation.Experimental; + +@Experimental("") +public interface SecretKeysHandlerFactory { + SecretKeysHandler getSecretKeysHandler(ConfigSourceContext context); + + String getName(); +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index ec8769f57..8b8cc28f0 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -17,6 +17,9 @@ package io.smallrye.config; import static io.smallrye.config.ConfigSourceInterceptorFactory.DEFAULT_PRIORITY; +import static io.smallrye.config.Converters.STRING_CONVERTER; +import static io.smallrye.config.Converters.newCollectionConverter; +import static io.smallrye.config.Converters.newTrimmingConverter; import static io.smallrye.config.ProfileConfigSourceInterceptor.convertProfile; import static io.smallrye.config.PropertiesConfigSourceProvider.classPathSources; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE; @@ -24,6 +27,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -58,15 +62,17 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private final List profiles = new ArrayList<>(); private final Set secretKeys = new HashSet<>(); private final List interceptors = new ArrayList<>(); + private final List secretKeysHandlers = new ArrayList<>(); + private ConfigValidator validator = ConfigValidator.EMPTY; private final KeyMap defaultValues = new KeyMap<>(); private final ConfigMappingProvider.Builder mappingsBuilder = ConfigMappingProvider.builder(); - private ConfigValidator validator = ConfigValidator.EMPTY; private ClassLoader classLoader = SecuritySupport.getContextClassLoader(); private boolean addDefaultSources = false; private boolean addDefaultInterceptors = false; private boolean addDiscoveredSources = false; private boolean addDiscoveredConverters = false; private boolean addDiscoveredInterceptors = false; + private boolean addDiscoveredSecretKeysHandlers = false; private boolean addDiscoveredValidator = false; public SmallRyeConfigBuilder() { @@ -89,6 +95,11 @@ public SmallRyeConfigBuilder addDiscoveredInterceptors() { return this; } + public SmallRyeConfigBuilder addDiscoveredSecretKeysHandlers() { + addDiscoveredSecretKeysHandlers = true; + return this; + } + public SmallRyeConfigBuilder addDiscoveredValidator() { addDiscoveredValidator = true; return this; @@ -294,18 +305,10 @@ public OptionalInt getPriority() { } })); - Map discoveredHandlers = new HashMap<>(); - ServiceLoader secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader); - for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) { - discoveredHandlers.put(secretKeysHandler.getName(), secretKeysHandler); - } - - SecretKeys secretKeys = new SecretKeys(this.secretKeys, discoveredHandlers); - interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() { @Override public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { - return new SecretKeysConfigSourceInterceptor(secretKeys); + return new SecretKeysConfigSourceInterceptor(SmallRyeConfigBuilder.this.secretKeys); } @Override @@ -313,16 +316,71 @@ public OptionalInt getPriority() { return OptionalInt.of(Priorities.LIBRARY + 100); } })); + + withDefaultValue("smallrye.config.secret-handlers", "all"); interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() { @Override public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { - return new SecretKeysHandlerConfigSourceInterceptor(secretKeys); + if (isAddDiscoveredSecretKeysHandlers()) { + secretKeysHandlers.addAll(discoverSecretKeysHandlers(context)); + } + return new SecretKeysHandlerConfigSourceInterceptor(secretKeysHandlers); } @Override public OptionalInt getPriority() { return OptionalInt.of(Priorities.LIBRARY + 310); } + + private List getEnabledHandlers(final ConfigSourceInterceptorContext context) { + ConfigValue enabledHandlers = context.proceed("smallrye.config.secret-handlers"); + if (enabledHandlers == null || enabledHandlers.getValue().equals("all")) { + return List.of(); + } + + List handlers = newCollectionConverter(newTrimmingConverter(STRING_CONVERTER), ArrayList::new) + .convert(enabledHandlers.getValue()); + return handlers != null ? handlers : List.of(); + } + + private List discoverSecretKeysHandlers(final ConfigSourceInterceptorContext context) { + List enabledHandlers = getEnabledHandlers(context); + + List discoveredHandlers = new ArrayList<>(); + ServiceLoader secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader); + for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) { + if (enabledHandlers.isEmpty() || enabledHandlers.contains(secretKeysHandler.getName())) { + discoveredHandlers.add(secretKeysHandler); + } + } + + ServiceLoader secretKeysHandlerFactories = ServiceLoader + .load(SecretKeysHandlerFactory.class, classLoader); + for (SecretKeysHandlerFactory secretKeysHandlerFactory : secretKeysHandlerFactories) { + if (enabledHandlers.isEmpty() || enabledHandlers.contains(secretKeysHandlerFactory.getName())) { + discoveredHandlers.add( + secretKeysHandlerFactory + .getSecretKeysHandler(new ConfigSourceContext() { + @Override + public ConfigValue getValue(final String name) { + return context.proceed(name); + } + + @Override + public List getProfiles() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterateNames() { + return context.iterateNames(); + } + })); + } + } + + return discoveredHandlers; + } })); return interceptors; @@ -371,6 +429,11 @@ public SmallRyeConfigBuilder withInterceptorFactories(ConfigSourceInterceptorFac return this; } + public SmallRyeConfigBuilder withSecretKeysHandlers(SecretKeysHandler... secretKeysHandler) { + this.secretKeysHandlers.addAll(Arrays.asList(secretKeysHandler)); + return this; + } + public SmallRyeConfigBuilder withProfile(String profile) { addDefaultInterceptors(); this.profiles.addAll(convertProfile(profile)); @@ -516,6 +579,10 @@ public boolean isAddDiscoveredInterceptors() { return addDiscoveredInterceptors; } + public boolean isAddDiscoveredSecretKeysHandlers() { + return addDiscoveredSecretKeysHandlers; + } + public boolean isAddDiscoveredValidator() { return addDiscoveredValidator; } diff --git a/implementation/src/test/java/io/smallrye/config/SecretKeysHandlerTest.java b/implementation/src/test/java/io/smallrye/config/SecretKeysHandlerTest.java new file mode 100644 index 000000000..95cb18046 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/SecretKeysHandlerTest.java @@ -0,0 +1,41 @@ +package io.smallrye.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +class SecretKeysHandlerTest { + @Test + void notFound() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .withDefaultValue("my.secret", "${missing::secret}") + .build(); + + assertThrows(NoSuchElementException.class, () -> config.getConfigValue("my.secret")); + } + + @Test + void handler() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .withSecretKeysHandlers(new SecretKeysHandler() { + @Override + public String decode(final String secret) { + return "decoded"; + } + + @Override + public String getName() { + return "handler"; + } + }) + .withDefaultValue("my.secret", "${handler::secret}") + .build(); + + assertEquals("decoded", config.getRawValue("my.secret")); + } +} diff --git a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java index 25d635dc3..724b5ec0e 100644 --- a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java +++ b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandler.java @@ -1,14 +1,48 @@ package io.smallrye.config.crypto; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + import io.smallrye.config.SecretKeysHandler; public class AESGCMNoPaddingSecretKeysHandler implements SecretKeysHandler { - // TODO - Should we provide a way to configure the handler? + private static final String ENC_ALGORITHM = "AES/GCM/NoPadding"; + private static final int ENC_LENGTH = 128; + + private final SecretKeySpec encryptionKey; + + public AESGCMNoPaddingSecretKeysHandler(final String encryptionKey) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.update(encryptionKey.getBytes(UTF_8)); + this.encryptionKey = new SecretKeySpec(sha256.digest(), "AES"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } @Override public String decode(final String secret) { - // TODO - handle implementation. - return "decoded"; + try { + Cipher cipher = Cipher.getInstance(ENC_ALGORITHM); + ByteBuffer byteBuffer = ByteBuffer.wrap(Base64.getUrlDecoder().decode(secret.getBytes(UTF_8))); + int ivLength = byteBuffer.get(); + byte[] iv = new byte[ivLength]; + byteBuffer.get(iv); + byte[] encrypted = new byte[byteBuffer.remaining()]; + byteBuffer.get(encrypted); + cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new GCMParameterSpec(ENC_LENGTH, iv)); + return new String(cipher.doFinal(encrypted), UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override diff --git a/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java new file mode 100644 index 000000000..9163c6e40 --- /dev/null +++ b/utils/crypto/src/main/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerFactory.java @@ -0,0 +1,30 @@ +package io.smallrye.config.crypto; + +import java.util.NoSuchElementException; + +import io.smallrye.config.ConfigMessages; +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SecretKeysHandler; +import io.smallrye.config.SecretKeysHandlerFactory; + +public class AESGCMNoPaddingSecretKeysHandlerFactory implements SecretKeysHandlerFactory { + @Override + public SecretKeysHandler getSecretKeysHandler(final ConfigSourceContext context) { + String encryptionKey = requireValue(context, "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key"); + return new AESGCMNoPaddingSecretKeysHandler(encryptionKey); + } + + @Override + public String getName() { + return "aes-gcm-nopadding"; + } + + private static String requireValue(final ConfigSourceContext context, final String name) { + ConfigValue value = context.getValue(name); + if (value != null) { + return value.getValue(); + } + throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name)); + } +} diff --git a/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler b/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler deleted file mode 100644 index dacc4ac5c..000000000 --- a/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.config.crypto.AESGCMNoPaddingSecretKeysHandler diff --git a/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory b/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory new file mode 100644 index 000000000..3cf6c8d38 --- /dev/null +++ b/utils/crypto/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory @@ -0,0 +1 @@ +io.smallrye.config.crypto.AESGCMNoPaddingSecretKeysHandlerFactory diff --git a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java index 4de43fd59..365ab4b0e 100644 --- a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java +++ b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java @@ -1,9 +1,11 @@ package io.smallrye.config.crypto; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.HashMap; import java.util.Map; +import java.util.NoSuchElementException; import org.junit.jupiter.api.Test; @@ -15,14 +17,15 @@ class AESGCMNoPaddingSecretKeysHandlerTest { @Test void handler() { - Map properties = new HashMap<>(); - properties.put("my.secret", "${aes-gcm-nopadding::encoded::x}"); - properties.put("my.expression", "${not.found:default}"); - properties.put("another.expression", "${my.expression}"); + Map properties = Map.of( + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "somearbitrarycrazystringthatdoesnotmatter", + "my.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}", + "my.expression", "${not.found:default}", + "another.expression", "${my.expression}"); SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() - .addDiscoveredSources() + .addDiscoveredSecretKeysHandlers() .withSources(new PropertiesConfigSource(properties, "", 0)) .build(); @@ -33,18 +36,36 @@ void handler() { @Test void keystore() { - Map properties = new HashMap<>(); - properties.put("io.smallrye.config.source.keystore.test.path", "keystore"); - properties.put("io.smallrye.config.source.keystore.test.password", "secret"); - properties.put("io.smallrye.config.source.keystore.test.algorithm", "aes-gcm-nopadding"); + Map properties = Map.of( + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "somearbitrarycrazystringthatdoesnotmatter", + "io.smallrye.config.source.keystore.test.path", "keystore", + "io.smallrye.config.source.keystore.test.password", "secret", + "io.smallrye.config.source.keystore.test.algorithm", "aes-gcm-nopadding"); SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() .addDiscoveredSources() + .addDiscoveredSecretKeysHandlers() .withSources(new PropertiesConfigSource(properties, "", 0)) .build(); ConfigValue secret = config.getConfigValue("my.secret"); assertEquals("decoded", secret.getValue()); } + + @Test + void noEncriptionKey() { + assertThrows(NoSuchElementException.class, () -> new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSecretKeysHandlers() + .build()); + + Map properties = Map.of("smallrye.config.secret-handlers", "none"); + new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSecretKeysHandlers() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); + assertTrue(true); + } } diff --git a/utils/crypto/src/test/resources/keystore b/utils/crypto/src/test/resources/keystore index 10082892f1e0b1a31234436f99c0ec6eeea92364..74db4bb9a9dc92c8494f7afa43e7a81ecf03f9fd 100644 GIT binary patch delta 382 zcmeyxw41r!powui6C*Q_N@U~IYV&CO&dbQoxS)wKh^2|q4=C&f#BQik=0GW9ps+p= zYqN1fb@6a9vMy-6Y|wZCS%_&t;{le&y#|fD4AS7Hu<{y28d!k16KlCe{#%($YO@wb*G;D4gD&#n(iyH+b6cv{1> zcKZc`lggSl*)5ws9q17j6I#`uZS*bq_V?#!e8Vf7VwP`q`JeulDdm7O*K-qTMM2S9 zQ#b$R2%ot4z`4tn7d0>P?l*KZP=q^*Q^Zh2PKY6wp^`z5p_n0cGC!k2l(~hmfswI^ zfdvry8W0;m_o-=PsnV8fC03JMtv;Y7A delta 267 zcmdnZ{EI2Wpoy`JiIEvd>9cWawRyCC=VfGPT+qa*z|zDh0~D43Vi8m+R-hE4LE}Gz z#@}q*P(3_cjI0Y97aKG#Ko(+J&^UpmvDcunYhspy$dr>GeRWh?{`;^Vl3H9>yem>t z;@!k~Dn{wA4xO5?_u%Y>`^zrfi`7+RQ4n4;|ATyIT*|6P8B22cy#AIho88q{wYZ|( ztW$_Jqv6M76-HZcGZSMALnC7Y0|O%iI|D@nPBvC;K4vB5S&_m)gKv2WI-=XXu_I=s(1#l+0mwg3SB#$eb0 diff --git a/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java index e970dc3e0..7cb499e1e 100644 --- a/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java +++ b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandler.java @@ -7,15 +7,18 @@ import io.smallrye.config.SecretKeysHandler; public class JasyptSecretKeysHandler implements SecretKeysHandler { - @Override - public String decode(final String secret) { - // TODO - We need to be able to configure this in the Handler. - // Option to configure it in the constructor or retrieve config on the fly? - StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); - encryptor.setPassword("jasypt"); - encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256"); + private final StandardPBEStringEncryptor encryptor; + + public JasyptSecretKeysHandler(final String password, final String algorithm) { + encryptor = new StandardPBEStringEncryptor(); + encryptor.setPassword(password); + encryptor.setAlgorithm(algorithm); encryptor.setIvGenerator(new RandomIvGenerator()); encryptor.initialize(); + } + + @Override + public String decode(final String secret) { return PropertyValueEncryptionUtils.decrypt(secret, encryptor); } diff --git a/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerFactory.java b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerFactory.java new file mode 100644 index 000000000..da4296535 --- /dev/null +++ b/utils/jasypt/src/main/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerFactory.java @@ -0,0 +1,31 @@ +package io.smallrye.config.jasypt; + +import java.util.NoSuchElementException; + +import io.smallrye.config.ConfigMessages; +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SecretKeysHandler; +import io.smallrye.config.SecretKeysHandlerFactory; + +public class JasyptSecretKeysHandlerFactory implements SecretKeysHandlerFactory { + @Override + public SecretKeysHandler getSecretKeysHandler(final ConfigSourceContext context) { + String password = requireValue(context, "smallrye.config.secret-handler.jasypt.password"); + String algorithm = requireValue(context, "smallrye.config.secret-handler.jasypt.algorithm"); + return new JasyptSecretKeysHandler(password, algorithm); + } + + @Override + public String getName() { + return "jasypt"; + } + + private static String requireValue(final ConfigSourceContext context, final String name) { + ConfigValue value = context.getValue(name); + if (value != null) { + return value.getValue(); + } + throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name)); + } +} diff --git a/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler b/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler deleted file mode 100644 index d2338e798..000000000 --- a/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandler +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.config.jasypt.JasyptSecretKeysHandler diff --git a/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory b/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory new file mode 100644 index 000000000..bb867b0b5 --- /dev/null +++ b/utils/jasypt/src/main/resources/META-INF/services/io.smallrye.config.SecretKeysHandlerFactory @@ -0,0 +1 @@ +io.smallrye.config.jasypt.JasyptSecretKeysHandlerFactory diff --git a/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java b/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java index c44acd6ab..b2854d8fd 100644 --- a/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java +++ b/utils/jasypt/src/test/java/io/smallrye/config/jasypt/JasyptSecretKeysHandlerTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -14,12 +13,14 @@ class JasyptSecretKeysHandlerTest { @Test void jasypt() { - Map properties = new HashMap<>(); - properties.put("my.secret", "${jasypt::ENC(wqp8zDeiCQ5JaFvwDtoAcr2WMLdlD0rjwvo8Rh0thG5qyTQVGxwJjBIiW26y0dtU)}"); + Map properties = Map.of( + "smallrye.config.secret-handler.jasypt.password", "jasypt", + "smallrye.config.secret-handler.jasypt.algorithm", "PBEWithHMACSHA512AndAES_256", + "my.secret", "${jasypt::ENC(wqp8zDeiCQ5JaFvwDtoAcr2WMLdlD0rjwvo8Rh0thG5qyTQVGxwJjBIiW26y0dtU)}"); SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() - .addDiscoveredSources() + .addDiscoveredSecretKeysHandlers() .withSources(new PropertiesConfigSource(properties, "", 0)) .build(); From 063b1ae574e4b2cdc507bf1c4b8b9140743026cd Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 10 Mar 2023 16:33:56 +0000 Subject: [PATCH 7/8] Document Secret Keys Handlers --- README.adoc | 2 +- documentation/mkdocs.yaml | 2 + .../src/main/docs/config-sources/keystore.md | 25 +++++++ .../src/main/docs/config/secret-keys.md | 69 ++++++++++++++++++- .../source/keystore/KeyStoreConfig.java | 4 +- .../keystore/KeyStoreConfigSourceFactory.java | 8 +-- .../keystore/KeyStoreConfigSourceTest.java | 2 +- .../AESGCMNoPaddingSecretKeysHandlerTest.java | 2 +- 8 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 documentation/src/main/docs/config-sources/keystore.md diff --git a/README.adoc b/README.adoc index 58e160ddd..ae6b27dd6 100644 --- a/README.adoc +++ b/README.adoc @@ -20,7 +20,7 @@ Compile and test the project: mvn verify ---- -Generate the documentation (from the documentation folder): +Generate the documentation (from the `documentation` folder): [source,bash] ---- diff --git a/documentation/mkdocs.yaml b/documentation/mkdocs.yaml index 49eb791c1..b1f985169 100644 --- a/documentation/mkdocs.yaml +++ b/documentation/mkdocs.yaml @@ -27,6 +27,7 @@ nav: - 'FileSystem': config-sources/filesystem.md - 'ZooKeeper': config-sources/zookeeper.md - 'HOCON': config-sources/hocon.md + - 'KeyStore': config-sources/keystore.md - Converters: - 'Custom': converters/custom.md - 'JSON': converters/json.md @@ -87,6 +88,7 @@ theme: markdown_extensions: - toc: + toc_depth: 3 permalink: '#' - admonition - smarty diff --git a/documentation/src/main/docs/config-sources/keystore.md b/documentation/src/main/docs/config-sources/keystore.md new file mode 100644 index 000000000..14463137e --- /dev/null +++ b/documentation/src/main/docs/config-sources/keystore.md @@ -0,0 +1,25 @@ +# KeyStore Config Source + +This Config Source allows to use a Java `KeyStore` to load configuration values. It uses an ordinal of `100`. + +The following dependency is required in the classpath to use the KeyStore Config Source: + +```xml + + io.smallrye.config + smallrye-config-source-keystore + {{attributes['version']}} + +``` + +## Configuration + +| Configuration Property | Type | Default | +|---------------------------------------------------------------------------------------------------------------------|--- |----| +| `io.smallrye.config.source.keystore."name".path`
The KeyStore path. | String | | +| `io.smallrye.config.source.keystore."name".password`
The KeyStore password. | String | | +| `io.smallrye.config.source.keystore."name".type`
The KeyStore type. | String | `PKCS12` | +| `io.smallrye.config.source.keystore."name".handler`
An Optional secret keys handler. | String | | +| `io.smallrye.config.source.keystore."name".aliases."key".name`
An Optional aliases key name. | String | | +| `io.smallrye.config.source.keystore."name".aliases."key".password`
An Optional aliases key password. | String | | +| `io.smallrye.config.source.keystore."name".aliases."key".handler`
An Optional aliases key secret keys handler. | String | | diff --git a/documentation/src/main/docs/config/secret-keys.md b/documentation/src/main/docs/config/secret-keys.md index ea9d191a5..df50af431 100644 --- a/documentation/src/main/docs/config/secret-keys.md +++ b/documentation/src/main/docs/config/secret-keys.md @@ -1,11 +1,76 @@ # Secret Keys +## Secret Keys Expressions + +In SmallRye Config, a secret configuration may be expressed as `${handler::value}`, where the `handler` is the name of +a `io.smallrye.config.SecretKeysHandler` to decode or decrypt the `value`. Consider: + +```properties +my.secret=${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg} +``` + +A lookup to `my.secret` will use the `SecretKeysHandler` name `aes-gcm-nopadding` to decode the value +`DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg`. + +It is possible to create a custom `SecretKeysHandler` and provide different ways to decode or decrypt configuration +values. + +A custom `SecretKeysHandler` requires an implementation of `io.smallrye.config.SecretKeysHandler` or +`io.smallrye.config.SecretKeysHandlerFactory`. Each implementation requires registration via the `ServiceLoader` +mechanism, either in `META-INF/services/io.smallrye.config.SecretKeysHandler` or +`META-INF/services/io.smallrye.config.SecretKeysHandlerFactory` files. + +### Crypto + +The `smallrye-config-crypto` artifact contains a few out-of-the-box `SecretKeysHandler`s ready for use. It requires +the following dependency: + +```xml + + io.smallrye.config + smallrye-config-crypto + {{attributes['version']}} + +``` + +#### AES/GCM/NoPadding `${aes-gcm-nopadding::...}` + +##### Configuration + +| Configuration Property | Type | Default | +|--- |--- |--- | +| `smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key`
The encription key to use to decode secrets encoded by the `AES/GCM/NoPadding` algorithm. | String | | + +### Jasypt + +[Jasypt](http://www.jasypt.org) is a java library which allows the developer to add basic encryption capabilities. It +requires the following dependency: + +```xml + + io.smallrye.config + smallrye-config-jasypt + {{attributes['version']}} + +``` + +#### Jasypt ``${jasypt::...}`` + +##### Configuration + +| Configuration Property | Type | Default | +|--- |--- |--- | +| `smallrye.config.secret-handler.jasypt.password`
The Jasypt password to use | String | | +| `smallrye.config.secret-handler.jasypt.algorithm`
The Jasypt algorithm to use | String | | + +## Secret Keys Names + When configuration properties contain passwords or other kinds of secrets, Smallrye Config can hide them to prevent accidental exposure of such values. **This is no way a replacement for securing secrets.** Proper security mechanisms must still be used to secure -secrets. However, there is still the basic problem that passwords and secrets are generally encoded simply as strings. -Secret Keys provides a way to "lock" the configuration so that secrets do not appear unless explicitly enabled. +secrets. However, there is still the fundamental problem that passwords and secrets are generally encoded simply as +strings. Secret Keys provides a way to "lock" the configuration so that secrets do not appear unless explicitly enabled. To mark specific keys as secrets, register an instance of `io.smallrye.config.SecretKeysConfigSourceInterceptor` by using the interceptor factory as follows: diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java index d236220f6..6ac03d66d 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java @@ -20,7 +20,7 @@ interface KeyStore { String password(); - Optional algorithm(); + Optional handler(); Map aliases(); @@ -29,7 +29,7 @@ interface Alias { Optional password(); - Optional algorithm(); + Optional handler(); } } } diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java index 0d44a5130..bf0f48094 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceFactory.java @@ -84,8 +84,8 @@ public Optional password() { } @Override - public Optional algorithm() { - return keyStoreConfig.algorithm(); + public Optional handler() { + return keyStoreConfig.handler(); } }); @@ -93,8 +93,8 @@ public Optional algorithm() { Key key = keyStore.getKey(alias, aliasConfig.password().orElse(keyStoreConfig.password()).toCharArray()); String encoded; - if (aliasConfig.algorithm().isPresent()) { - encoded = "${" + aliasConfig.algorithm().get() + "::" + new String(key.getEncoded(), UTF_8) + "}"; + if (aliasConfig.handler().isPresent()) { + encoded = "${" + aliasConfig.handler().get() + "::" + new String(key.getEncoded(), UTF_8) + "}"; } else { encoded = new String(key.getEncoded(), UTF_8); } diff --git a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java index 1cb782bfb..2dd74c6ac 100644 --- a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java +++ b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java @@ -46,7 +46,7 @@ void keyStoreNotFound() { ConfigValue secret = config.getConfigValue("my.secret"); assertNull(secret.getValue()); - assertThrows(IllegalStateException.class, () -> new SmallRyeConfigBuilder() + assertThrows(IllegalArgumentException.class, () -> new SmallRyeConfigBuilder() .addDiscoveredSources() .withSources(new PropertiesConfigSource(Map.of( "io.smallrye.config.source.keystore.test.path", "file:/not.found", diff --git a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java index 365ab4b0e..87ead4f5e 100644 --- a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java +++ b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java @@ -40,7 +40,7 @@ void keystore() { "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "somearbitrarycrazystringthatdoesnotmatter", "io.smallrye.config.source.keystore.test.path", "keystore", "io.smallrye.config.source.keystore.test.password", "secret", - "io.smallrye.config.source.keystore.test.algorithm", "aes-gcm-nopadding"); + "io.smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding"); SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() From e4564043885e8ff404210dbc693963e5a46523d0 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 20 Mar 2023 23:48:21 +0000 Subject: [PATCH 8/8] Consistent configuration names --- .../src/main/docs/config-sources/keystore.md | 14 +++---- sources/keystore/pom.xml | 18 +++++++++ .../source/keystore/KeyStoreConfig.java | 2 +- .../keystore/KeyStoreConfigSourceTest.java | 12 +++--- testsuite/extra/pom.xml | 10 +++++ .../secrets/MultipleSecretHandlersTest.java | 32 +++++++++++++++ .../AESGCMNoPaddingSecretKeysHandlerTest.java | 39 +++++++++++++++++-- utils/jasypt/pom.xml | 6 ++- 8 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java diff --git a/documentation/src/main/docs/config-sources/keystore.md b/documentation/src/main/docs/config-sources/keystore.md index 14463137e..5fb6a7140 100644 --- a/documentation/src/main/docs/config-sources/keystore.md +++ b/documentation/src/main/docs/config-sources/keystore.md @@ -16,10 +16,10 @@ The following dependency is required in the classpath to use the KeyStore Config | Configuration Property | Type | Default | |---------------------------------------------------------------------------------------------------------------------|--- |----| -| `io.smallrye.config.source.keystore."name".path`
The KeyStore path. | String | | -| `io.smallrye.config.source.keystore."name".password`
The KeyStore password. | String | | -| `io.smallrye.config.source.keystore."name".type`
The KeyStore type. | String | `PKCS12` | -| `io.smallrye.config.source.keystore."name".handler`
An Optional secret keys handler. | String | | -| `io.smallrye.config.source.keystore."name".aliases."key".name`
An Optional aliases key name. | String | | -| `io.smallrye.config.source.keystore."name".aliases."key".password`
An Optional aliases key password. | String | | -| `io.smallrye.config.source.keystore."name".aliases."key".handler`
An Optional aliases key secret keys handler. | String | | +| `smallrye.config.source.keystore."name".path`
The KeyStore path. | String | | +| `smallrye.config.source.keystore."name".password`
The KeyStore password. | String | | +| `smallrye.config.source.keystore."name".type`
The KeyStore type. | String | `PKCS12` | +| `smallrye.config.source.keystore."name".handler`
An Optional secret keys handler. | String | | +| `smallrye.config.source.keystore."name".aliases."key".name`
An Optional aliases key name. | String | | +| `smallrye.config.source.keystore."name".aliases."key".password`
An Optional aliases key password. | String | | +| `smallrye.config.source.keystore."name".aliases."key".handler`
An Optional aliases key secret keys handler. | String | | diff --git a/sources/keystore/pom.xml b/sources/keystore/pom.xml index 66a69504a..058851292 100644 --- a/sources/keystore/pom.xml +++ b/sources/keystore/pom.xml @@ -34,4 +34,22 @@ + + + + io.smallrye + jandex-maven-plugin + ${version.org.jboss.jandex} + + + make-index + + jandex + + + + + + + diff --git a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java index 6ac03d66d..248d26009 100644 --- a/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java +++ b/sources/keystore/src/main/java/io/smallrye/config/source/keystore/KeyStoreConfig.java @@ -7,7 +7,7 @@ import io.smallrye.config.WithDefault; import io.smallrye.config.WithParentName; -@ConfigMapping(prefix = "io.smallrye.config.source.keystore") +@ConfigMapping(prefix = "smallrye.config.source.keystore") public interface KeyStoreConfig { @WithParentName Map keystores(); diff --git a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java index 2dd74c6ac..8af2fd79f 100644 --- a/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java +++ b/sources/keystore/src/test/java/io/smallrye/config/source/keystore/KeyStoreConfigSourceTest.java @@ -18,8 +18,8 @@ class KeyStoreConfigSourceTest { void keystore() { // keytool -importpass -alias my.secret -keystore keystore -storepass secret -storetype PKCS12 -v Map properties = Map.of( - "io.smallrye.config.source.keystore.test.path", "keystore", - "io.smallrye.config.source.keystore.test.password", "secret"); + "smallrye.config.source.keystore.test.path", "keystore", + "smallrye.config.source.keystore.test.password", "secret"); SmallRyeConfig config = new SmallRyeConfigBuilder() .withProfile("prod") @@ -39,8 +39,8 @@ void keyStoreNotFound() { .addDefaultInterceptors() .addDiscoveredSources() .withSources(new PropertiesConfigSource(Map.of( - "io.smallrye.config.source.keystore.test.path", "not.found", - "io.smallrye.config.source.keystore.test.password", "secret"), "", 0)) + "smallrye.config.source.keystore.test.path", "not.found", + "smallrye.config.source.keystore.test.password", "secret"), "", 0)) .build(); ConfigValue secret = config.getConfigValue("my.secret"); @@ -49,8 +49,8 @@ void keyStoreNotFound() { assertThrows(IllegalArgumentException.class, () -> new SmallRyeConfigBuilder() .addDiscoveredSources() .withSources(new PropertiesConfigSource(Map.of( - "io.smallrye.config.source.keystore.test.path", "file:/not.found", - "io.smallrye.config.source.keystore.test.password", "secret"), "", 0)) + "smallrye.config.source.keystore.test.path", "file:/not.found", + "smallrye.config.source.keystore.test.password", "secret"), "", 0)) .build()); } } diff --git a/testsuite/extra/pom.xml b/testsuite/extra/pom.xml index f0aeca197..e17c67dee 100644 --- a/testsuite/extra/pom.xml +++ b/testsuite/extra/pom.xml @@ -53,6 +53,16 @@ smallrye-config-source-yaml ${project.version} + + io.smallrye.config + smallrye-config-crypto + ${project.version} + + + io.smallrye.config + smallrye-config-jasypt + ${project.version} + diff --git a/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java b/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java new file mode 100644 index 000000000..60ada1a03 --- /dev/null +++ b/testsuite/extra/src/test/java/io/smallrye/config/test/secrets/MultipleSecretHandlersTest.java @@ -0,0 +1,32 @@ +package io.smallrye.config.test.secrets; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +class MultipleSecretHandlersTest { + @Test + void multipleHandlers() { + Map properties = Map.of( + "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "somearbitrarycrazystringthatdoesnotmatter", + "aes-gcm-nopadding.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}", + "smallrye.config.secret-handler.jasypt.password", "jasypt", + "smallrye.config.secret-handler.jasypt.algorithm", "PBEWithHMACSHA512AndAES_256", + "jasypt.secret", "${jasypt::ENC(wqp8zDeiCQ5JaFvwDtoAcr2WMLdlD0rjwvo8Rh0thG5qyTQVGxwJjBIiW26y0dtU)}"); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSecretKeysHandlers() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .build(); + + assertEquals("decoded", config.getRawValue("aes-gcm-nopadding.secret")); + assertEquals("12345678", config.getRawValue("jasypt.secret")); + } +} diff --git a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java index 87ead4f5e..b91b87c11 100644 --- a/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java +++ b/utils/crypto/src/test/java/io/smallrye/config/crypto/AESGCMNoPaddingSecretKeysHandlerTest.java @@ -4,11 +4,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigSourceFactory; import io.smallrye.config.ConfigValue; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.SmallRyeConfig; @@ -38,9 +43,9 @@ void handler() { void keystore() { Map properties = Map.of( "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "somearbitrarycrazystringthatdoesnotmatter", - "io.smallrye.config.source.keystore.test.path", "keystore", - "io.smallrye.config.source.keystore.test.password", "secret", - "io.smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding"); + "smallrye.config.source.keystore.test.path", "keystore", + "smallrye.config.source.keystore.test.password", "secret", + "smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding"); SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() @@ -68,4 +73,32 @@ void noEncriptionKey() { .build(); assertTrue(true); } + + @Test + @Disabled + void configurableSource() { + Map properties = Map.of( + "smallrye.config.source.keystore.test.path", "keystore", + "smallrye.config.source.keystore.test.password", "secret", + "smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding"); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .addDiscoveredSources() + .addDiscoveredSecretKeysHandlers() + .withSources(new PropertiesConfigSource(properties, "", 0)) + .withSources(new ConfigSourceFactory() { + @Override + public Iterable getConfigSources(final ConfigSourceContext context) { + return List.of(new PropertiesConfigSource( + Map.of("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", + "somearbitrarycrazystringthatdoesnotmatter"), + "", 0)); + } + }) + .build(); + + ConfigValue secret = config.getConfigValue("my.secret"); + assertEquals("decoded", secret.getValue()); + } } diff --git a/utils/jasypt/pom.xml b/utils/jasypt/pom.xml index 4823bff13..5da623277 100644 --- a/utils/jasypt/pom.xml +++ b/utils/jasypt/pom.xml @@ -12,6 +12,10 @@ SmallRye Config: Jasypt + + 1.9.3 + + io.smallrye.config @@ -20,7 +24,7 @@ org.jasypt jasypt - 1.9.3 + ${version.jasypt}