Add property to disable adding invalid prefix when decryption fails (#2708)

Fixes #2632

Signed-off-by: Ryan Baxter <ryan.baxter@broadcom.com>
This commit is contained in:
Ryan Baxter
2025-01-15 16:09:44 -05:00
committed by GitHub
parent 84fafcddee
commit 2d88fa05db
7 changed files with 184 additions and 6 deletions

View File

@@ -77,3 +77,76 @@ AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...
NOTE: The `--key` argument is mandatory (despite having a `--` prefix).
== Decryption Errors
When the config server fails to decrypt a value it will create an `invalid` property in the HTTP response.
For example
[source,json]
----
{
"label": null,
"name": "application",
"profiles": [
"prd"
],
"propertySources": [
{
"name": "file:/demo/configserver/application-prd.yaml",
"source": {
"invalid.SharedPassword": "<n/a>"
}
},
{
"name": "file:/demo/configserver/application.yaml",
"source": {
"SharedPassword": "Fill_me_in"
}
}
],
"state": null,
"version": null
}
----
In the example above the config server could not decrypt the value of `SharedPassword` in `application-prd.yaml`
so the config server prefixed the property name with `invalid`.
If this response was received by the Config Client and then added to the app's `Environment` and the client
requested the value of `SharedPassword` it would get `Fill_me_in`.
If you do not want the config server to prefix properties it can't decrypt wit `invalid` then you can set
`spring.cloud.config.server.encrypt.prefix-invalid-properties` to `false`. If you do this then the same response from
the config server would look like this:
[source,json]
----
"label": null,
"name": "application",
"profiles": [
"prd"
],
"propertySources": [
{
"name": "file:/demo/configserver/application-prd.yaml",
"source": {
"SharedPassword": "AYBKlpcZpaR36OcRDQjNIQl6fmnddAQhetMw/uyTpnn5fDj+unJ9QOEbqiPc9fX0N+CC8i+EJiN6nlH9Xqu6sH1tX/P6zg1CIy+ct/1RWGNbmQ256jc6vQaXhiN8sA8Mr6QiqYnMoBd+Jni/Miir5G3a7G9MmjbEUASKJOhUlIFKqL1IqB81RBT/cv0bg9kAiy5VBF1WppxP/PwtjECzbeUi2Y1jbpYb98rnc/qmRO3ZJam9fDNcPpW09qGFhGgJIujca257F7G4guS2w/7haVzNoyRiwHzZ14oL8AIxHLMBSJJF19ULlsMAkROj9o9TnwhL9r4rX9sAWk28c5eq77+iVpmlT3yoRdZqvMqffzKiibDlzz95Gmms7V7mctxrhNVOOWTwMSJvk94Y9ZPenljKgPJIV3Z1cqqx+W8JxFFeelOuYvMEe4bOVBh1TepGzzdWVdYbylgXJy35uRTZ2drybUe5+jc0hiAuujHz0zdY1FwOHfwzSsSidlYn4syPeuytnxTzn7fbWXeXetTTtDlmLRf8MBSzXzDFWNH0cNGOCQ=="
}
},
{
"name": "file:/demo/configserver/application.yaml",
"source": {
"SharedPassword": "Fill_me_in"
}
}
],
"state": null,
"version": null
}
----
In this case if the config client were to receive the above response and requested that value
of `SharedPassword` from the `Environment` it would get the encrypted value back instead of
`Fill_me_in`.

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.config.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -44,6 +45,9 @@ import org.springframework.security.crypto.encrypt.TextEncryptor;
@AutoConfigureAfter(DefaultTextEncryptionAutoConfiguration.class)
public class EncryptionAutoConfiguration {
@Value("${spring.cloud.config.server.encrypt.prefixInvalidProperties:${spring.cloud.config.server.encrypt.prefix-invalid-properties:true}}")
private boolean prefixInvalidProperties;
@Bean
@ConditionalOnBean(TextEncryptor.class)
@ConditionalOnMissingBean(TextEncryptorLocator.class)
@@ -60,7 +64,9 @@ public class EncryptionAutoConfiguration {
if (locator == null) {
locator = new SingleTextEncryptorLocator(encryptor);
}
return new CipherEnvironmentEncryptor(locator);
CipherEnvironmentEncryptor environmentEncryptor = new CipherEnvironmentEncryptor(locator);
environmentEncryptor.setPrefixInvalidProperties(prefixInvalidProperties);
return environmentEncryptor;
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.config.server.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.config.server.encryption.vault.VaultEnvironmentEncryptor;
import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository;
@@ -34,10 +35,16 @@ import org.springframework.vault.core.VaultTemplate;
@Profile("vault")
public class VaultEncryptionAutoConfiguration {
@Value("${spring.cloud.config.server.encrypt.prefixInvalidProperties:${spring.cloud.config.server.encrypt.prefix-invalid-properties:true}}")
private boolean prefixInvalidProperties;
@Bean
public VaultEnvironmentEncryptor vaultEnvironmentEncryptor(
SpringVaultEnvironmentRepository vaultEnvironmentRepository) {
return new VaultEnvironmentEncryptor(vaultEnvironmentRepository.getKeyValueTemplate());
VaultEnvironmentEncryptor vaultEnvironmentEncryptor = new VaultEnvironmentEncryptor(
vaultEnvironmentRepository.getKeyValueTemplate());
vaultEnvironmentEncryptor.setPrefixInvalidProperties(this.prefixInvalidProperties);
return vaultEnvironmentEncryptor;
}
}

View File

@@ -44,6 +44,8 @@ public class CipherEnvironmentEncryptor implements EnvironmentEncryptor {
private final TextEncryptorLocator encryptor;
private boolean prefixInvalidProperties = true;
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
@Autowired
@@ -74,8 +76,10 @@ public class CipherEnvironmentEncryptor implements EnvironmentEncryptor {
.decrypt(this.helper.stripPrefix(value));
}
catch (Exception e) {
value = "<n/a>";
name = "invalid." + name;
if (this.prefixInvalidProperties) {
value = "<n/a>";
name = "invalid." + name;
}
String message = "Cannot decrypt key: " + key + " (" + e.getClass() + ": " + e.getMessage()
+ ")";
if (logger.isDebugEnabled()) {
@@ -93,4 +97,8 @@ public class CipherEnvironmentEncryptor implements EnvironmentEncryptor {
return result;
}
public void setPrefixInvalidProperties(boolean prefixInvalidProperties) {
this.prefixInvalidProperties = prefixInvalidProperties;
}
}

View File

@@ -44,6 +44,8 @@ public class VaultEnvironmentEncryptor implements EnvironmentEncryptor {
private final VaultKeyValueOperations keyValueTemplate;
private boolean prefixInvalidProperties = true;
public VaultEnvironmentEncryptor(VaultKeyValueOperations keyValueTemplate) {
this.keyValueTemplate = keyValueTemplate;
}
@@ -102,8 +104,10 @@ public class VaultEnvironmentEncryptor implements EnvironmentEncryptor {
}
}
catch (Exception e) {
value = "<n/a>";
name = "invalid." + name;
if (this.prefixInvalidProperties) {
value = "<n/a>";
name = "invalid." + name;
}
String message = "Cannot resolve key: " + key + " (" + e.getClass() + ": " + e.getMessage()
+ ")";
if (logger.isDebugEnabled()) {
@@ -121,4 +125,8 @@ public class VaultEnvironmentEncryptor implements EnvironmentEncryptor {
return result;
}
public void setPrefixInvalidProperties(boolean prefixInvalidProperties) {
this.prefixInvalidProperties = prefixInvalidProperties;
}
}

View File

@@ -119,4 +119,55 @@ public class CipherEnvironmentEncryptorTests {
.isEqualTo(secret);
}
@ParameterizedTest
@MethodSource("params")
public void shouldDecryptFailed(String salt, String key) {
TextEncryptor textEncryptor = new EncryptorFactory(salt).create(key);
CipherEnvironmentEncryptor encryptor = new CipherEnvironmentEncryptor(keys -> textEncryptor);
encryptor.setPrefixInvalidProperties(true);
// given
String secret = randomUUID().toString();
// when
Environment environment = new Environment("name", "profile", "label");
String encrypted = "{cipher}" + new EncryptorFactory(salt).create("dummykey").encrypt(secret);
environment.add(new PropertySource("a", Collections.<Object, Object>singletonMap(environment.getName(),
new PropertyValueDescriptor(encrypted, "encrypted value"))));
// then
assertThat(encryptor.decrypt(environment)
.getPropertySources()
.get(0)
.getSource()
.get("invalid." + environment.getName())).isEqualTo("<n/a>");
assertThat(encryptor.decrypt(environment).getPropertySources().get(0).getSource().get(environment.getName()))
.isNull();
}
@ParameterizedTest
@MethodSource("params")
public void decryptFailedWithoutInvalidPrefix(String salt, String key) {
TextEncryptor textEncryptor = new EncryptorFactory(salt).create(key);
CipherEnvironmentEncryptor encryptor = new CipherEnvironmentEncryptor(keys -> textEncryptor);
encryptor.setPrefixInvalidProperties(false);
// given
String secret = randomUUID().toString();
// when
Environment environment = new Environment("name", "profile", "label");
String encryptedSecret = new EncryptorFactory(salt).create("dummykey").encrypt(secret);
String encrypted = "{cipher}" + encryptedSecret;
environment.add(new PropertySource("a", Collections.<Object, Object>singletonMap(environment.getName(),
new PropertyValueDescriptor(encrypted, "encrypted value"))));
// then
assertThat(encryptor.decrypt(environment)
.getPropertySources()
.get(0)
.getSource()
.get("invalid." + environment.getName())).isNull();
assertThat(encryptor.decrypt(environment).getPropertySources().get(0).getSource().get(environment.getName()))
.isEqualTo(encryptedSecret);
}
}

View File

@@ -117,6 +117,31 @@ public class VaultEnvironmentEncryptorTests {
.isEqualTo("<n/a>");
}
@Test
public void shouldNotPrefixInvalidPropertyWithNoKeyValue() {
// given
String accounts = "accounts/mypay";
String value = "{vault}:" + accounts;
VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
VaultEnvironmentEncryptor encryptor = new VaultEnvironmentEncryptor(keyValueTemplate);
encryptor.setPrefixInvalidProperties(false);
// when
Environment environment = new Environment("name", "profile", "label");
environment
.add(new PropertySource("a", Collections.<Object, Object>singletonMap(environment.getName(), value)));
// then
Environment processedEnvironment = encryptor.decrypt(environment);
assertThat(processedEnvironment.getPropertySources().get(0).getSource().get("invalid." + environment.getName()))
.isNull();
assertThat(processedEnvironment.getPropertySources().get(0).getSource().get(environment.getName()))
.isEqualTo(accounts);
}
@Test
public void shouldMarkAsInvalidPropertyWithNoEmptyValue() {
// given