diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java index 9f96640e..faa8aa01 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java @@ -59,6 +59,16 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp */ private String defaultKey = "application"; + /** + * KV2 API required "data" after "mount-path". There could be folder/path structure, + * where the keys/applications are grouped. This property is the path after + * mount-path, under which application(s) are located (appended after "data") Default + * value is blank, which means all grouped applications are located right under the + * mount-path + * + */ + private String pathToKey = ""; + /** Vault profile separator. Defaults to comma. */ private String profileSeparator = ","; @@ -263,6 +273,14 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp return authentication; } + public String getPathToKey() { + return pathToKey; + } + + public void setPathToKey(String pathToKey) { + this.pathToKey = pathToKey; + } + public enum AuthenticationMethod { /** diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java index f1745ce5..17b86bfa 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java @@ -92,7 +92,8 @@ public class VaultEnvironmentRepository extends AbstractVaultEnvironmentReposito String baseUrl = String.format("%s://%s:%s", this.scheme, this.host, this.port); - this.accessStrategy = VaultKvAccessStrategyFactory.forVersion(rest, baseUrl, properties.getKvVersion()); + this.accessStrategy = VaultKvAccessStrategyFactory.forVersion(rest, baseUrl, properties.getKvVersion(), + properties.getPathToKey()); } /* for testing */ void setAccessStrategy(VaultKvAccessStrategy accessStrategy) { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactory.java index 77e32e3d..75b50154 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactory.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactory.java @@ -18,6 +18,7 @@ package org.springframework.cloud.config.server.environment; import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; /** @@ -39,15 +40,16 @@ public final class VaultKvAccessStrategyFactory { * @param rest must not be {@literal null}. * @param baseUrl the Vault base URL. * @param version version of the Vault key-value backend. + * @param pathToKey path after the mount-path, under which the key(s) can be found. * @return the access strategy. */ - public static VaultKvAccessStrategy forVersion(RestOperations rest, String baseUrl, int version) { + public static VaultKvAccessStrategy forVersion(RestOperations rest, String baseUrl, int version, String pathToKey) { switch (version) { case 1: return new V1VaultKvAccessStrategy(baseUrl, rest); case 2: - return new V2VaultKvAccessStrategy(baseUrl, rest); + return new V2VaultKvAccessStrategy(baseUrl, pathToKey, rest); default: throw new IllegalArgumentException("No support for given Vault k/v backend version " + version); } @@ -79,12 +81,20 @@ public final class VaultKvAccessStrategyFactory { */ static class V2VaultKvAccessStrategy extends VaultKvAccessStrategySupport { - V2VaultKvAccessStrategy(String baseUrl, RestOperations rest) { + private String pathToKey; + + V2VaultKvAccessStrategy(String baseUrl, String pathToKey, RestOperations rest) { super(baseUrl, rest); + this.pathToKey = pathToKey; } @Override public String getPath() { + + if (StringUtils.hasText(pathToKey)) { + return "data/" + pathToKey + "/{key}"; + } + return "data/{key}"; } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java index 58b1a707..b2144616 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java @@ -333,6 +333,43 @@ public class VaultEnvironmentRepositoryTests { .isEqualTo(firstResult); } + @Test + @SuppressWarnings("unchecked") + public void testVaultKV2WithPath2Key() { + RestTemplate rest = mock(RestTemplate.class); + + ResponseEntity myAppResp = mock(ResponseEntity.class); + when(myAppResp.getStatusCode()).thenReturn(HttpStatus.OK); + VaultResponse myAppVaultResp = getVaultResponse("{\"data\": {\"data\": {\"foo\": \"bar\"}}}"); + when(myAppResp.getBody()).thenReturn(myAppVaultResp); + when(rest.exchange(eq("http://127.0.0.1:8200/v1/secret/data/myorg/{key}"), eq(HttpMethod.GET), + any(HttpEntity.class), eq(VaultResponse.class), eq("myapp"))).thenReturn(myAppResp); + + ResponseEntity appResp = mock(ResponseEntity.class); + when(appResp.getStatusCode()).thenReturn(HttpStatus.OK); + VaultResponse appVaultResp = getVaultResponse("{\"data\": {\"data\": {\"def-foo\":\"def-bar\"}}}"); + when(appResp.getBody()).thenReturn(appVaultResp); + when(rest.exchange(eq("http://127.0.0.1:8200/v1/secret/data/myorg/{key}"), eq(HttpMethod.GET), + any(HttpEntity.class), eq(VaultResponse.class), eq("application"))).thenReturn(appResp); + + final VaultEnvironmentProperties vaultEnvironmentProperties = new VaultEnvironmentProperties(); + vaultEnvironmentProperties.setKvVersion(2); + vaultEnvironmentProperties.setPathToKey("myorg"); + VaultEnvironmentRepository repo = new VaultEnvironmentRepository(mockHttpRequest(), + new EnvironmentWatch.Default(), rest, vaultEnvironmentProperties, mockTokenProvider()); + + Environment e = repo.findOne("myapp", null, null); + assertThat(e.getName()).as("Name should be the same as the application argument").isEqualTo("myapp"); + assertThat(e.getPropertySources().size()).as( + "Properties for specified application and default application with key 'application' should be returned") + .isEqualTo(2); + Map firstResult = new HashMap<>(); + firstResult.put("foo", "bar"); + assertThat(e.getPropertySources().get(0).getSource()) + .as("Properties for specified application should be returned in priority position") + .isEqualTo(firstResult); + } + @Test @SuppressWarnings({ "Duplicates", "unchecked" }) public void testNamespaceHeaderSent() { @@ -401,7 +438,7 @@ public class VaultEnvironmentRepositoryTests { String baseUrl = String.format("%s://%s:%s", properties.getScheme(), properties.getHost(), properties.getPort()); this.accessStrategy = VaultKvAccessStrategyFactory.forVersion(restTemplate, baseUrl, - properties.getKvVersion()); + properties.getKvVersion(), ""); } @Override diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactoryTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactoryTest.java index 681fbb69..4c5e37fe 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactoryTest.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyFactoryTest.java @@ -30,19 +30,19 @@ public class VaultKvAccessStrategyFactoryTest { @Test public void testGetV1Strategy() { - VaultKvAccessStrategy vaultKvAccessStrategy = VaultKvAccessStrategyFactory.forVersion(null, "foo", 1); + VaultKvAccessStrategy vaultKvAccessStrategy = VaultKvAccessStrategyFactory.forVersion(null, "foo", 1, ""); assertThat(vaultKvAccessStrategy instanceof V1VaultKvAccessStrategy).isTrue(); } @Test public void testGetV2Strategy() { - VaultKvAccessStrategy vaultKvAccessStrategy = VaultKvAccessStrategyFactory.forVersion(null, "foo", 2); + VaultKvAccessStrategy vaultKvAccessStrategy = VaultKvAccessStrategyFactory.forVersion(null, "foo", 2, ""); assertThat(vaultKvAccessStrategy instanceof V2VaultKvAccessStrategy).isTrue(); } @Test(expected = IllegalArgumentException.class) public void testGetUnsupportedStrategy() { - VaultKvAccessStrategyFactory.forVersion(null, "foo", 0); + VaultKvAccessStrategyFactory.forVersion(null, "foo", 0, ""); } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyTest.java index 677c95c8..bb4ca09e 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyTest.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultKvAccessStrategyTest.java @@ -36,7 +36,7 @@ public class VaultKvAccessStrategyTest { private ObjectMapper objectMapper = new ObjectMapper(); private static VaultKvAccessStrategySupport getStrategy(int version) { - return (VaultKvAccessStrategySupport) VaultKvAccessStrategyFactory.forVersion(null, "foo", version); + return (VaultKvAccessStrategySupport) VaultKvAccessStrategyFactory.forVersion(null, "foo", version, ""); } @Test