diff --git a/docs/modules/ROOT/pages/server/encryption-and-decryption.adoc b/docs/modules/ROOT/pages/server/encryption-and-decryption.adoc index 1719de6981..c793e97538 100644 --- a/docs/modules/ROOT/pages/server/encryption-and-decryption.adoc +++ b/docs/modules/ROOT/pages/server/encryption-and-decryption.adoc @@ -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": "" + } + }, + { + "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`. diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EncryptionAutoConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EncryptionAutoConfiguration.java index 310fb97511..0dfa415ed6 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EncryptionAutoConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EncryptionAutoConfiguration.java @@ -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 @@ @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 EnvironmentEncryptor environmentEncryptor(@Autowired(required = false) Te if (locator == null) { locator = new SingleTextEncryptorLocator(encryptor); } - return new CipherEnvironmentEncryptor(locator); + CipherEnvironmentEncryptor environmentEncryptor = new CipherEnvironmentEncryptor(locator); + environmentEncryptor.setPrefixInvalidProperties(prefixInvalidProperties); + return environmentEncryptor; } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/VaultEncryptionAutoConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/VaultEncryptionAutoConfiguration.java index bc7f3bada2..55e44e2182 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/VaultEncryptionAutoConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/VaultEncryptionAutoConfiguration.java @@ -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 @@ @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; } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptor.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptor.java index fedc75c74f..f2e7b46908 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptor.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptor.java @@ -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 @@ private Environment decrypt(Environment environment, TextEncryptorLocator encryp .decrypt(this.helper.stripPrefix(value)); } catch (Exception e) { - value = ""; - name = "invalid." + name; + if (this.prefixInvalidProperties) { + value = ""; + name = "invalid." + name; + } String message = "Cannot decrypt key: " + key + " (" + e.getClass() + ": " + e.getMessage() + ")"; if (logger.isDebugEnabled()) { @@ -93,4 +97,8 @@ else if (logger.isWarnEnabled()) { return result; } + public void setPrefixInvalidProperties(boolean prefixInvalidProperties) { + this.prefixInvalidProperties = prefixInvalidProperties; + } + } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptor.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptor.java index 073f159841..ab2412b6f2 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptor.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptor.java @@ -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 Environment decrypt(Environment environment) { } } catch (Exception e) { - value = ""; - name = "invalid." + name; + if (this.prefixInvalidProperties) { + value = ""; + name = "invalid." + name; + } String message = "Cannot resolve key: " + key + " (" + e.getClass() + ": " + e.getMessage() + ")"; if (logger.isDebugEnabled()) { @@ -121,4 +125,8 @@ else if (logger.isWarnEnabled()) { return result; } + public void setPrefixInvalidProperties(boolean prefixInvalidProperties) { + this.prefixInvalidProperties = prefixInvalidProperties; + } + } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptorTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptorTests.java index 71fb02fc51..0c13488356 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptorTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/CipherEnvironmentEncryptorTests.java @@ -119,4 +119,55 @@ public void shouldDecryptEnvironmentIncludeOrigin(String salt, String key) { .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.singletonMap(environment.getName(), + new PropertyValueDescriptor(encrypted, "encrypted value")))); + + // then + assertThat(encryptor.decrypt(environment) + .getPropertySources() + .get(0) + .getSource() + .get("invalid." + environment.getName())).isEqualTo(""); + 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.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); + } + } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptorTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptorTests.java index 617a75b217..9ee18c8b68 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptorTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/vault/VaultEnvironmentEncryptorTests.java @@ -117,6 +117,31 @@ public void shouldMarkAsInvalidPropertyWithNoKeyValue() { .isEqualTo(""); } + @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.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