Skip to content

Commit

Permalink
Support configurable SecretKeyHandlers
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Mar 21, 2023
1 parent db9158f commit 78766ea
Show file tree
Hide file tree
Showing 18 changed files with 301 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
23 changes: 0 additions & 23 deletions implementation/src/main/java/io/smallrye/config/SecretKeys.java
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -12,26 +9,6 @@ public final class SecretKeys implements Serializable {

private static final ThreadLocal<Boolean> LOCKED = new ThreadLocal<>();

private final Set<String> secrets;
private final Map<String, SecretKeysHandler> handlers;

SecretKeys(final Set<String> secrets, final Map<String, SecretKeysHandler> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
public class SecretKeysConfigSourceInterceptor implements ConfigSourceInterceptor {
private static final long serialVersionUID = 7291982039729980590L;

private final SecretKeys secrets;
private final Set<String> secrets;

public SecretKeysConfigSourceInterceptor(final SecretKeys secrets) {
public SecretKeysConfigSourceInterceptor(final Set<String> 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);
Expand All @@ -31,7 +31,7 @@ public Iterator<String> iterateNames(final ConfigSourceInterceptorContext contex
Iterator<String> namesIterator = context.iterateNames();
while (namesIterator.hasNext()) {
String name = namesIterator.next();
if (!secrets.secretExistsWithName(name)) {
if (!secrets.contains(name)) {
names.add(name);
}
}
Expand All @@ -47,7 +47,7 @@ public Iterator<ConfigValue> iterateValues(final ConfigSourceInterceptorContext
Iterator<ConfigValue> valuesIterator = context.iterateValues();
while (valuesIterator.hasNext()) {
ConfigValue value = valuesIterator.next();
if (!secrets.secretExistsWithName(value.getName())) {
if (!secrets.contains(value.getName())) {
values.add(value);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, SecretKeysHandler> handlers = new HashMap<>();

public SecretKeysHandlerConfigSourceInterceptor(final SecretKeys secretKeys) {
this.secretKeys = secretKeys;
public SecretKeysHandlerConfigSourceInterceptor(final List<SecretKeysHandler> handlers) {
for (SecretKeysHandler handler : handlers) {
this.handlers.put(handler.getName(), handler);
}
}

@Override
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.smallrye.config;

import io.smallrye.common.annotation.Experimental;

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

String getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
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;
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE_PARENT;

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;
Expand Down Expand Up @@ -58,15 +62,17 @@ public class SmallRyeConfigBuilder implements ConfigBuilder {
private final List<String> profiles = new ArrayList<>();
private final Set<String> secretKeys = new HashSet<>();
private final List<InterceptorWithPriority> interceptors = new ArrayList<>();
private final List<SecretKeysHandler> secretKeysHandlers = new ArrayList<>();
private ConfigValidator validator = ConfigValidator.EMPTY;
private final KeyMap<String> 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() {
Expand All @@ -89,6 +95,11 @@ public SmallRyeConfigBuilder addDiscoveredInterceptors() {
return this;
}

public SmallRyeConfigBuilder addDiscoveredSecretKeysHandlers() {
addDiscoveredSecretKeysHandlers = true;
return this;
}

public SmallRyeConfigBuilder addDiscoveredValidator() {
addDiscoveredValidator = true;
return this;
Expand Down Expand Up @@ -294,35 +305,82 @@ public OptionalInt getPriority() {
}
}));

Map<String, SecretKeysHandler> discoveredHandlers = new HashMap<>();
ServiceLoader<SecretKeysHandler> 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
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<String> getEnabledHandlers(final ConfigSourceInterceptorContext context) {
ConfigValue enabledHandlers = context.proceed("smallrye.config.secret-handlers");
if (enabledHandlers == null || enabledHandlers.getValue().equals("all")) {
return List.of();
}

List<String> handlers = newCollectionConverter(newTrimmingConverter(STRING_CONVERTER), ArrayList::new)
.convert(enabledHandlers.getValue());
return handlers != null ? handlers : List.of();
}

private List<SecretKeysHandler> discoverSecretKeysHandlers(final ConfigSourceInterceptorContext context) {
List<String> enabledHandlers = getEnabledHandlers(context);

List<SecretKeysHandler> discoveredHandlers = new ArrayList<>();
ServiceLoader<SecretKeysHandler> secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader);
for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) {
if (enabledHandlers.isEmpty() || enabledHandlers.contains(secretKeysHandler.getName())) {
discoveredHandlers.add(secretKeysHandler);
}
}

ServiceLoader<SecretKeysHandlerFactory> 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<String> getProfiles() {
throw new UnsupportedOperationException();
}

@Override
public Iterator<String> iterateNames() {
return context.iterateNames();
}
}));
}
}

return discoveredHandlers;
}
}));

return interceptors;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -516,6 +579,10 @@ public boolean isAddDiscoveredInterceptors() {
return addDiscoveredInterceptors;
}

public boolean isAddDiscoveredSecretKeysHandlers() {
return addDiscoveredSecretKeysHandlers;
}

public boolean isAddDiscoveredValidator() {
return addDiscoveredValidator;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading

0 comments on commit 78766ea

Please sign in to comment.