Feature(Vault KV2): to support a path after mount-path (backend) under which the key(s)/application(s) be found (#1833)

* adding changes to support a path after mount-path (for KV2) under which applications/keys can be located

Co-authored-by: cah-venkatasuryasasikala-peri <venkatasuryasasikala.peri@cardinalhealth.com>
Co-authored-by: Sasi Peri <pvssasikala@gmil.com>
This commit is contained in:
sasiperi
2021-07-27 17:57:07 -04:00
committed by GitHub
parent 5635b74c4a
commit 851116d3e6
6 changed files with 75 additions and 9 deletions

View File

@@ -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 {
/**

View File

@@ -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) {

View File

@@ -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}";
}

View File

@@ -333,6 +333,43 @@ public class VaultEnvironmentRepositoryTests {
.isEqualTo(firstResult);
}
@Test
@SuppressWarnings("unchecked")
public void testVaultKV2WithPath2Key() {
RestTemplate rest = mock(RestTemplate.class);
ResponseEntity<VaultResponse> 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<VaultResponse> 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<String, String> 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

View File

@@ -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, "");
}
}

View File

@@ -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