+ * This class is responsible for decrypting properties in the environment that are
+ * prefixed with "{vault}". The vault key-value pairs are retrieved from a Vault server
+ * using the provided {@link VaultKeyValueOperations} template. Properties that start with
+ * "{vault}" are expected to follow a specific format: "{vault}:key#path" where:
+ *
+ *
"key" is the Vault secret path
+ *
"path" is the key within the Vault secret
+ *
+ *
+ *
+ * Before retrieving values from Vault, any environment variable placeholders in the
+ * format ${VAR_NAME} found in the vault reference are replaced with actual environment
+ * variable values. If an environment variable is not found, the placeholder remains
+ * unchanged.
+ *
+ *
* @author Alexey Zhokhov
+ * @author Pavel Andrusov
*/
public class VaultEnvironmentEncryptor implements EnvironmentEncryptor {
@@ -50,6 +70,20 @@ public VaultEnvironmentEncryptor(VaultKeyValueOperations keyValueTemplate) {
this.keyValueTemplate = keyValueTemplate;
}
+ /**
+ * Decrypts property values in the provided environment that are prefixed with
+ * "{vault}". For each such property, this method:
+ *
+ *
Extracts the Vault key and path from the property value
+ *
Replaces any environment variable placeholders (${VAR_NAME}) in the vault
+ * reference with actual environment values
+ *
Retrieves the secret value from Vault using the provided key-value
+ * template
+ *
Replaces the property value with the decrypted value from Vault
+ *
+ * @param environment the environment containing properties to decrypt
+ * @return a new Environment object with decrypted values where applicable
+ */
@Override
public Environment decrypt(Environment environment) {
Map loadedVaultKeys = new HashMap<>();
@@ -86,8 +120,8 @@ public Environment decrypt(Environment environment) {
throw new RuntimeException("Wrong format");
}
- String vaultKey = parts[0];
- String vaultParamName = parts[1];
+ String vaultKey = replaceEnvironmentPlaceholders(parts[0]);
+ String vaultParamName = replaceEnvironmentPlaceholders(parts[1]);
if (!loadedVaultKeys.containsKey(vaultKey)) {
loadedVaultKeys.put(vaultKey, keyValueTemplate.get(vaultKey));
@@ -125,7 +159,80 @@ else if (logger.isWarnEnabled()) {
return result;
}
- public void setPrefixInvalidProperties(boolean prefixInvalidProperties) {
+ /**
+ * Replace environment variable placeholders like ${VAR_NAME} with actual values from
+ * system environment variables.
+ *
+ *
+ * If an environment variable is not found, the placeholder remains unchanged.
+ *
+ * @param value the string value that may contain environment variable placeholders
+ * @return the string with environment variable placeholders replaced by their values,
+ * or the original string if no placeholders are found
+ */
+ private String replaceEnvironmentPlaceholders(final String value) {
+ if (value == null) {
+ logger.debug("Input value is null, returning null");
+ return null;
+ }
+
+ logger.debug("Processing placeholder replacement for input: %s".formatted(value));
+
+ // Pattern to match ${VAR_NAME} format
+ Pattern pattern = Pattern.compile("\\$\\{([^}]+)}");
+ Matcher matcher = pattern.matcher(value);
+
+ if (!matcher.find()) {
+ logger.debug("No placeholders found in input string");
+ return value;
+ }
+
+ // Reset matcher for replacement process
+ matcher.reset();
+
+ StringBuilder result = new StringBuilder();
+ int lastEnd = 0;
+ int processedCount = 0;
+
+ while (matcher.find()) {
+ processedCount++;
+ String variableName = matcher.group(1);
+ logger.debug("Found placeholder with variable name: %s".formatted(variableName));
+
+ // Append the text before the placeholder
+ result.append(value, lastEnd, matcher.start());
+
+ String replacement = System.getenv(variableName);
+ if (replacement != null) {
+ // Replace with environment variable value
+ result.append(replacement);
+ logger.info(
+ "Successfully resolved '%s' placeholder from system environment variables. Placeholder replaced."
+ .formatted(variableName));
+ }
+ else {
+ // If environment variable not found, keep original placeholder
+ result.append(matcher.group(0));
+ logger
+ .warn("Environment variable '%s' not found. Keeping original placeholder.".formatted(variableName));
+ }
+
+ lastEnd = matcher.end();
+ }
+
+ // Append the remaining text after the last placeholder
+ result.append(value, lastEnd, value.length());
+
+ logger.debug("Placeholder replacement completed. Processed %d placeholders.".formatted(processedCount));
+
+ return result.toString();
+ }
+
+ /**
+ * Set whether to prefix invalid properties with "invalid.".
+ * @param prefixInvalidProperties whether to prefix invalid properties
+ */
+ public void setPrefixInvalidProperties(final boolean prefixInvalidProperties) {
this.prefixInvalidProperties = prefixInvalidProperties;
}
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 9ee18c8b68..91ff1b6fcf 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
@@ -33,6 +33,7 @@
/**
* @author Alexey Zhokhov
+ * @author Pavel Andrusov
*/
public class VaultEnvironmentEncryptorTests {
@@ -208,6 +209,199 @@ public void shouldMarkAsInvalidPropertyWithWrongFormat2() {
.isEqualTo("");
}
+ @Test
+ public void shouldResolvePropertyWithEnvironmentVariableInVaultKey() {
+ // given
+ String secret = "mysecret";
+ String vaultKeyWithEnvVar = "accounts/${PATH}/mypay";
+
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+
+ // Use PATH environment variable which should exist on most systems
+ String pathValue = System.getenv("PATH");
+ when(keyValueTemplate.get("accounts/" + pathValue + "/mypay"))
+ .thenReturn(withVaultResponse("access_key", secret));
+
+ VaultEnvironmentEncryptor encryptor = new VaultEnvironmentEncryptor(keyValueTemplate);
+
+ // when
+ Environment environment = new Environment("name", "profile", "label");
+ environment.add(new PropertySource("a", Collections.