Set the FailesafeTextEncryptor delegate if we can create a valid KeyProperties (#2399)
Fixes #2330 Co-authored-by: Ryan Baxter <524254+ryanjbaxter@users.noreply.github.com>
This commit is contained in:
@@ -37,10 +37,16 @@ import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.logging.DeferredLogFactory;
|
||||
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
|
||||
import org.springframework.cloud.bootstrap.encrypt.RsaProperties;
|
||||
import org.springframework.cloud.bootstrap.encrypt.TextEncryptorUtils;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.context.encrypt.EncryptorFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -54,6 +60,8 @@ public class ConfigServerConfigDataLocationResolver
|
||||
* Prefix for Config Server imports.
|
||||
*/
|
||||
public static final String PREFIX = "configserver:";
|
||||
static final boolean RSA_IS_PRESENT = ClassUtils
|
||||
.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null);
|
||||
|
||||
private final Log log;
|
||||
|
||||
@@ -66,6 +74,41 @@ public class ConfigServerConfigDataLocationResolver
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Depending on whether encrypt.key is set as an environment or system property we may
|
||||
* have created a TextEncryptor implementation or just created a
|
||||
* FailsafeTextEncryptor. This is because when TextEncryptorConfigBootstrapper runs we
|
||||
* have not yet loaded any configuration files (application.yaml | properties etc).
|
||||
* However, at this point when the ConfigServerConfigDataLocationResolver is resolving
|
||||
* configuration we would have resolved the configuration files on the classpath at
|
||||
* least so we can potentially create a properly configured TextEncryptor. So if the
|
||||
* FailsafeTextEncryptor is in the context and we can create a TextEncryptor then we
|
||||
* set the delegate in the FailsafeTextEncryptor so that we can decrypt any encrypted
|
||||
* properties at this point.
|
||||
*/
|
||||
protected void setTextEncryptorDelegate(ConfigDataLocationResolverContext context) {
|
||||
if (context.getBootstrapContext().isRegistered(TextEncryptor.class)) {
|
||||
Binder binder = context.getBinder();
|
||||
KeyProperties keyProperties = binder.bindOrCreate(KeyProperties.PREFIX, Bindable.of(KeyProperties.class));
|
||||
boolean textEncryptorRegistered = context.getBootstrapContext().isRegistered(TextEncryptor.class);
|
||||
if (TextEncryptorUtils.keysConfigured(keyProperties) && textEncryptorRegistered) {
|
||||
TextEncryptor textEncryptor = context.getBootstrapContext().get(TextEncryptor.class);
|
||||
if (textEncryptor instanceof TextEncryptorUtils.FailsafeTextEncryptor failsafeTextEncryptor) {
|
||||
TextEncryptor delegate;
|
||||
if (RSA_IS_PRESENT) {
|
||||
RsaProperties rsaProperties = binder.bindOrCreate(RsaProperties.PREFIX,
|
||||
Bindable.of(RsaProperties.class));
|
||||
delegate = TextEncryptorUtils.createTextEncryptor(keyProperties, rsaProperties);
|
||||
}
|
||||
else {
|
||||
delegate = new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
|
||||
}
|
||||
failsafeTextEncryptor.setDelegate(delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected PropertyHolder loadProperties(ConfigDataLocationResolverContext context, String uris) {
|
||||
Binder binder = context.getBinder();
|
||||
BindHandler bindHandler = getBindHandler(context);
|
||||
@@ -182,6 +225,7 @@ public class ConfigServerConfigDataLocationResolver
|
||||
public List<ConfigServerConfigDataResource> resolveProfileSpecific(
|
||||
ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location, Profiles profiles)
|
||||
throws ConfigDataLocationNotFoundException {
|
||||
setTextEncryptorDelegate(resolverContext);
|
||||
String uris = location.getNonPrefixedValue(getPrefix());
|
||||
PropertyHolder propertyHolder = loadProperties(resolverContext, uris);
|
||||
ConfigClientProperties properties = propertyHolder.properties;
|
||||
@@ -269,14 +313,6 @@ public class ConfigServerConfigDataLocationResolver
|
||||
return locations;
|
||||
}
|
||||
|
||||
private class PropertyHolder {
|
||||
|
||||
ConfigClientProperties properties;
|
||||
|
||||
RetryProperties retryProperties;
|
||||
|
||||
}
|
||||
|
||||
public static class PropertyResolver {
|
||||
|
||||
private final Binder binder;
|
||||
@@ -302,4 +338,12 @@ public class ConfigServerConfigDataLocationResolver
|
||||
|
||||
}
|
||||
|
||||
private class PropertyHolder {
|
||||
|
||||
ConfigClientProperties properties;
|
||||
|
||||
RetryProperties retryProperties;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,14 +21,23 @@ import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||
import org.springframework.boot.BootstrapRegistry;
|
||||
import org.springframework.boot.ConfigurableBootstrapContext;
|
||||
import org.springframework.boot.context.config.ConfigDataLocation;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
|
||||
import org.springframework.boot.context.config.Profiles;
|
||||
import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.logging.DeferredLog;
|
||||
import org.springframework.cloud.bootstrap.TextEncryptorBindHandler;
|
||||
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
|
||||
import org.springframework.cloud.bootstrap.encrypt.TextEncryptorUtils;
|
||||
import org.springframework.cloud.context.encrypt.EncryptorFactory;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.eq;
|
||||
@@ -205,6 +214,65 @@ public class ConfigServerConfigDataLocationResolverTests {
|
||||
assertThat(resource2.getProperties().getUri()).isEqualTo(new String[] { "http://urlNo2" });
|
||||
}
|
||||
|
||||
@Test
|
||||
void setFailsafeDelegateKeysNotConfigured() {
|
||||
ConfigurableBootstrapContext bootstrapContext = mock(ConfigurableBootstrapContext.class);
|
||||
when(bootstrapContext.isRegistered(eq(ConfigClientProperties.class))).thenReturn(true);
|
||||
KeyProperties keyProperties = new KeyProperties();
|
||||
ConfigClientProperties configClientProperties = new ConfigClientProperties();
|
||||
configClientProperties.setUri(new String[] { "http://myuri" });
|
||||
when(bootstrapContext.isRegistered(TextEncryptor.class)).thenReturn(true);
|
||||
when(bootstrapContext.get(TextEncryptor.class)).thenReturn(new TextEncryptorUtils.FailsafeTextEncryptor());
|
||||
when(bootstrapContext.get(eq(ConfigClientProperties.class))).thenReturn(configClientProperties);
|
||||
when(context.getBootstrapContext()).thenReturn(bootstrapContext);
|
||||
this.resolver.resolve(context, ConfigDataLocation.of("configserver:http://urlNo1"));
|
||||
TextEncryptor textEncryptor = bootstrapContext.get(TextEncryptor.class);
|
||||
assertThat(textEncryptor).isInstanceOf(TextEncryptorUtils.FailsafeTextEncryptor.class);
|
||||
assertThat(((TextEncryptorUtils.FailsafeTextEncryptor) textEncryptor).getDelegate()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setFailsafeDelegateKeysConfigured() {
|
||||
ConfigurableBootstrapContext bootstrapContext = mock(ConfigurableBootstrapContext.class);
|
||||
when(bootstrapContext.isRegistered(eq(ConfigClientProperties.class))).thenReturn(true);
|
||||
environment.setProperty("encrypt.key", "mykey");
|
||||
|
||||
// The value is "password" encrypted with the key "mykey"
|
||||
environment.setProperty("spring.cloud.config.password",
|
||||
"{cipher}6defc102cd76752fcf4c78231ed82ead85133a09741d9a1442595b4800e2b3d1");
|
||||
ConfigClientProperties configClientProperties = new ConfigClientProperties();
|
||||
configClientProperties.setUri(new String[] { "http://myuri" });
|
||||
when(bootstrapContext.isRegistered(TextEncryptor.class)).thenReturn(true);
|
||||
when(bootstrapContext.get(TextEncryptor.class)).thenReturn(new TextEncryptorUtils.FailsafeTextEncryptor());
|
||||
when(bootstrapContext.get(eq(ConfigClientProperties.class))).thenReturn(configClientProperties);
|
||||
when(context.getBootstrapContext()).thenReturn(bootstrapContext);
|
||||
KeyProperties keyProperties = new KeyProperties();
|
||||
keyProperties.setKey("mykey");
|
||||
|
||||
// Use this TextEncryptor in the BindHandler we return so it will decrypt the
|
||||
// password when we bind ConfigClientProperties
|
||||
TextEncryptor bindHandlerTextEncryptor = new EncryptorFactory(keyProperties.getSalt())
|
||||
.create(keyProperties.getKey());
|
||||
when(context.getBootstrapContext().getOrElse(eq(BindHandler.class), eq(null)))
|
||||
.thenReturn(new TextEncryptorBindHandler(bindHandlerTextEncryptor, keyProperties));
|
||||
|
||||
// Call resolve so we can test that the delegate is added to the
|
||||
// FailsafeTextEncryptor
|
||||
this.resolver.resolve(context, ConfigDataLocation.of("configserver:http://urlNo1"));
|
||||
TextEncryptor textEncryptor = bootstrapContext.get(TextEncryptor.class);
|
||||
|
||||
// Capture the ConfigClientProperties we create and register in
|
||||
// ConfigServerConfigDataLocationResolver.resolveProfileSpecific
|
||||
// it should have the decrypted passord in it
|
||||
ArgumentCaptor<BootstrapRegistry.InstanceSupplier<ConfigClientProperties>> captor = ArgumentCaptor
|
||||
.forClass(BootstrapRegistry.InstanceSupplier.class);
|
||||
verify(bootstrapContext).register(eq(ConfigClientProperties.class), captor.capture());
|
||||
assertThat(captor.getValue().get(bootstrapContext).getPassword()).isEqualTo("password");
|
||||
assertThat(textEncryptor).isInstanceOf(TextEncryptorUtils.FailsafeTextEncryptor.class);
|
||||
assertThat(((TextEncryptorUtils.FailsafeTextEncryptor) textEncryptor).getDelegate())
|
||||
.isInstanceOf(TextEncryptor.class);
|
||||
}
|
||||
|
||||
private ConfigServerConfigDataResource testUri(String propertyUri, String locationUri) {
|
||||
this.environment.setProperty(ConfigClientProperties.PREFIX + ".uri", propertyUri);
|
||||
when(context.getBootstrapContext()).thenReturn(mock(ConfigurableBootstrapContext.class));
|
||||
|
||||
Reference in New Issue
Block a user