Skip to content

Commit

Permalink
Support lazy SecretKeysHandlers (#961)
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez authored Jul 12, 2023
1 parent 7f4441a commit 2ff6c9a
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.smallrye.config;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import io.smallrye.config.SecretKeysHandlerFactory.LazySecretKeysHandler;

public class SecretKeysHandlerConfigSourceInterceptor implements ConfigSourceInterceptor {
private static final long serialVersionUID = -5228028387733656005L;

Expand All @@ -23,17 +26,36 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final
if (enabled && configValue != null && configValue.getValue() != null) {
String handler = configValue.getExtendedExpressionHandler();
if (handler != null) {
return configValue.withValue(getSecretValue(handler, configValue.getValue()));
return configValue.withValue(getHandler(handler, context).decode(configValue.getValue()));
}
}
return configValue;
}

private String getSecretValue(final String handlerName, final String secretName) {
private SecretKeysHandler getHandler(final String handlerName, final ConfigSourceInterceptorContext context) {
SecretKeysHandler handler = handlers.get(handlerName);
if (handler != null) {
return handler.decode(secretName);
if (handler == null) {
throw ConfigMessages.msg.secretKeyHandlerNotFound(handlerName);
}

if (handler instanceof LazySecretKeysHandler) {
handler = ((LazySecretKeysHandler) handler).get(new ConfigSourceContext() {
@Override
public ConfigValue getValue(final String name) {
return context.proceed(name);
}

@Override
public List<String> getProfiles() {
throw new UnsupportedOperationException();
}

@Override
public Iterator<String> iterateNames() {
return context.iterateNames();
}
});
}
throw ConfigMessages.msg.secretKeyHandlerNotFound(handlerName);
return handler;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
package io.smallrye.config;

import java.util.concurrent.atomic.AtomicReference;

import io.smallrye.common.annotation.Experimental;

@Experimental("")
public interface SecretKeysHandlerFactory {
SecretKeysHandler getSecretKeysHandler(ConfigSourceContext context);

String getName();

class LazySecretKeysHandler implements SecretKeysHandler {
private final SecretKeysHandlerFactory factory;
private final AtomicReference<SecretKeysHandler> handler = new AtomicReference<>();

public LazySecretKeysHandler(final SecretKeysHandlerFactory factory) {
this.factory = factory;
}

public SecretKeysHandler get(ConfigSourceContext configSourceContext) {
if (handler.get() == null) {
handler.compareAndSet(null, factory.getSecretKeysHandler(configSourceContext));
}
return handler.get();
}

@Override
public String decode(final String secret) {
if (handler.get() == null) {
throw new IllegalStateException();
}
return handler.get().decode(secret);
}

@Override
public String getName() {
return factory.getName();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,31 @@ public class AESGCMNoPaddingSecretKeysHandlerFactory implements SecretKeysHandle

@Override
public SecretKeysHandler getSecretKeysHandler(final ConfigSourceContext context) {
ConfigValue encryptionKey = context.getValue(ENCRYPTION_KEY);
if (encryptionKey == null || encryptionKey.getValue() == null) {
throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(ENCRYPTION_KEY));
}

boolean decode = true;
ConfigValue plain = context.getValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode");
if (plain != null && plain.getValue() != null) {
decode = Converters.getImplicitConverter(Boolean.class).convert(plain.getValue());
}

byte[] encryptionKeyBytes = decode ? Base64.getUrlDecoder().decode(encryptionKey.getValue())
: encryptionKey.getValue().getBytes(StandardCharsets.UTF_8);

return new AESGCMNoPaddingSecretKeysHandler(encryptionKeyBytes);
return new LazySecretKeysHandler(new SecretKeysHandlerFactory() {
@Override
public SecretKeysHandler getSecretKeysHandler(final ConfigSourceContext context) {
ConfigValue encryptionKey = context.getValue(ENCRYPTION_KEY);
if (encryptionKey == null || encryptionKey.getValue() == null) {
throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(ENCRYPTION_KEY));
}

boolean decode = true;
ConfigValue plain = context.getValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode");
if (plain != null && plain.getValue() != null) {
decode = Converters.getImplicitConverter(Boolean.class).convert(plain.getValue());
}

byte[] encryptionKeyBytes = decode ? Base64.getUrlDecoder().decode(encryptionKey.getValue())
: encryptionKey.getValue().getBytes(StandardCharsets.UTF_8);

return new AESGCMNoPaddingSecretKeysHandler(encryptionKeyBytes);
}

@Override
public String getName() {
return AESGCMNoPaddingSecretKeysHandlerFactory.this.getName();
}
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
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;
Expand All @@ -22,17 +19,16 @@
class AESGCMNoPaddingSecretKeysHandlerTest {
@Test
void handler() {
Map<String, String> properties = Map.of(
"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key",
"c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI",
"my.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}",
"my.expression", "${not.found:default}",
"another.expression", "${my.expression}");

SmallRyeConfig config = new SmallRyeConfigBuilder()
.addDefaultInterceptors()
.addDiscoveredSecretKeysHandlers()
.withSources(new PropertiesConfigSource(properties, "", 0))
.withDefaultValues(Map.of(
"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key",
"c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI",
"my.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}",
"my.expression", "${not.found:default}",
"another.expression", "${my.expression}"))
.build();

assertEquals("decoded", config.getRawValue("my.secret"));
Expand All @@ -42,18 +38,17 @@ void handler() {

@Test
void keystore() {
Map<String, String> properties = Map.of(
"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "somearbitrarycrazystringthatdoesnotmatter",
"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode", "false",
"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))
.withDefaultValues(Map.of(
"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key",
"somearbitrarycrazystringthatdoesnotmatter",
"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode", "false",
"smallrye.config.source.keystore.test.path", "keystore",
"smallrye.config.source.keystore.test.password", "secret",
"smallrye.config.source.keystore.test.handler", "aes-gcm-nopadding"))
.build();

ConfigValue secret = config.getConfigValue("my.secret");
Expand All @@ -62,10 +57,14 @@ void keystore() {

@Test
void noEncriptionKey() {
assertThrows(NoSuchElementException.class, () -> new SmallRyeConfigBuilder()
SmallRyeConfig config = new SmallRyeConfigBuilder()
.addDefaultInterceptors()
.addDiscoveredSecretKeysHandlers()
.build());
.withDefaultValues(Map.of(
"my.secret", "${aes-gcm-nopadding::DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg}"))
.build();

assertThrows(NoSuchElementException.class, () -> config.getConfigValue("my.secret"));

Map<String, String> properties = Map.of("smallrye.config.secret-handlers", "none");
new SmallRyeConfigBuilder()
Expand All @@ -77,27 +76,18 @@ void noEncriptionKey() {
}

@Test
@Disabled
void configurableSource() {
Map<String, String> 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<ConfigSource> getConfigSources(final ConfigSourceContext context) {
return List.of(new PropertiesConfigSource(
Map.of("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key",
"somearbitrarycrazystringthatdoesnotmatter"),
"", 0));
}
})
.withDefaultValues(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"))
.withSources((ConfigSourceFactory) context -> List.of(
new PropertiesConfigSource(Map.of("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key",
"c29tZWFyYml0cmFyeWNyYXp5c3RyaW5ndGhhdGRvZXNub3RtYXR0ZXI"), "", 0)))
.build();

ConfigValue secret = config.getConfigValue("my.secret");
Expand Down

0 comments on commit 2ff6c9a

Please sign in to comment.