Adds support for the optional X-Vault-Namespace header.
fixes gh-1259
This commit is contained in:
10
README.adoc
10
README.adoc
@@ -1,4 +1,8 @@
|
||||
// Do not edit this file (e.g. go instead to src/main/asciidoc)
|
||||
////
|
||||
DO NOT EDIT THIS FILE. IT WAS GENERATED.
|
||||
Manual changes to this file will be lost when it is generated again.
|
||||
Edit the files in the src/main/asciidoc/ directory instead.
|
||||
////
|
||||
|
||||
image::https://circleci.com/gh/spring-cloud/spring-cloud-config/tree/master.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-config/tree/master"]
|
||||
image::https://codecov.io/gh/spring-cloud/spring-cloud-config/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-config/branch/master"]
|
||||
@@ -73,7 +77,7 @@ The HTTP service has resources in the following form:
|
||||
|
||||
where `application` is injected as the `spring.config.name` in the `SpringApplication` (what is normally `application` in a regular Spring Boot app), `profile` is an active profile (or comma-separated list of properties), and `label` is an optional git label (defaults to `master`.)
|
||||
|
||||
Spring Cloud Config Server pulls configuration for remote clients from a git repository (which must be provided), as shown in the following example:
|
||||
Spring Cloud Config Server pulls configuration for remote clients from various sources. The following example gets configuration from a git repository (which must be provided), as shown in the following example:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -85,6 +89,8 @@ spring:
|
||||
uri: https://github.com/spring-cloud-samples/config-repo
|
||||
----
|
||||
|
||||
Other sources are any JDBC compatible database, Subversion, Hashicorp Vault, Credhub and local filesystems.
|
||||
|
||||
=== Client Side Usage
|
||||
|
||||
To use these features in an application, you can build it as a Spring Boot application that depends on spring-cloud-config-client (for an example, see the test cases for the config-client or the sample application).
|
||||
|
||||
@@ -34,7 +34,7 @@ The HTTP service has resources in the following form:
|
||||
|
||||
where `application` is injected as the `spring.config.name` in the `SpringApplication` (what is normally `application` in a regular Spring Boot app), `profile` is an active profile (or comma-separated list of properties), and `label` is an optional git label (defaults to `master`.)
|
||||
|
||||
Spring Cloud Config Server pulls configuration for remote clients from a git repository (which must be provided), as shown in the following example:
|
||||
Spring Cloud Config Server pulls configuration for remote clients from various sources. The following example gets configuration from a git repository (which must be provided), as shown in the following example:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -46,6 +46,8 @@ spring:
|
||||
uri: https://github.com/spring-cloud-samples/config-repo
|
||||
----
|
||||
|
||||
Other sources are any JDBC compatible database, Subversion, Hashicorp Vault, Credhub and local filesystems.
|
||||
|
||||
=== Client Side Usage
|
||||
|
||||
To use these features in an application, you can build it as a Spring Boot application that depends on spring-cloud-config-client (for an example, see the test cases for the config-client or the sample application).
|
||||
|
||||
@@ -593,14 +593,19 @@ The following table describes configurable Vault properties:
|
||||
|timeout
|
||||
|5
|
||||
|
||||
|namespace
|
||||
|null
|
||||
|
||||
|===
|
||||
|
||||
IMPORTANT: All of the properties in the preceding table must be prefixed with `spring.cloud.config.server.vault`.
|
||||
IMPORTANT: All of the properties in the preceding table must be prefixed with `spring.cloud.config.server.vault` or placed in the correct Vault section of a composite configuration.
|
||||
|
||||
All configurable properties can be found in `org.springframework.cloud.config.server.environment.VaultEnvironmentRepository`.
|
||||
All configurable properties can be found in `org.springframework.cloud.config.server.environment.VaultEnvironmentProperties`.
|
||||
|
||||
Vault 0.10.0 introduced a versioned key-value backend (k/v backend version 2) that exposes a different API than earlier versions, it now requires a `data/` between the mount path and the actual context path and wraps secrets in a `data` object. Setting `kvVersion=2` will take this into account.
|
||||
|
||||
Optionally, there is support for the Vault Enterprise `X-Vault-Namespace` header. To have it sent to Vault set the `namespace` property.
|
||||
|
||||
With your config server running, you can make HTTP requests to the server to retrieve
|
||||
values from the Vault backend.
|
||||
To do so, you need a token for your Vault server.
|
||||
|
||||
@@ -97,7 +97,8 @@ public class EncryptionAutoConfiguration {
|
||||
KeyStore keyStore = this.key.getKeyStore();
|
||||
KeyStoreTextEncryptorLocator locator = new KeyStoreTextEncryptorLocator(
|
||||
new KeyStoreKeyFactory(keyStore.getLocation(),
|
||||
keyStore.getPassword().toCharArray(), key.getKeyStore().getType()),
|
||||
keyStore.getPassword().toCharArray(),
|
||||
key.getKeyStore().getType()),
|
||||
keyStore.getSecret(), keyStore.getAlias());
|
||||
RsaAlgorithm algorithm = this.rsaProperties.getAlgorithm();
|
||||
locator.setRsaAlgorithm(algorithm);
|
||||
|
||||
@@ -73,6 +73,11 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp
|
||||
*/
|
||||
private int kvVersion = 1;
|
||||
|
||||
/**
|
||||
* The value of the Vault X-Vault-Namespace header. Defaults to null. This a Vault Enterprise feature only.
|
||||
*/
|
||||
private String namespace;
|
||||
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
@@ -166,4 +171,12 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp
|
||||
this.kvVersion = kvVersion;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,6 +54,11 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
|
||||
*/
|
||||
public static final String VAULT_TOKEN = "X-Vault-Token";
|
||||
|
||||
/**
|
||||
* Vault namespace header name.
|
||||
*/
|
||||
public static final String VAULT_NAMESPACE = "X-Vault-Namespace";
|
||||
|
||||
/** Vault host. Defaults to 127.0.0.1. */
|
||||
@NotEmpty
|
||||
private String host;
|
||||
@@ -76,6 +81,9 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
|
||||
*/
|
||||
private String defaultKey;
|
||||
|
||||
/** Vault Namespace header value. */
|
||||
private String namespace;
|
||||
|
||||
/** Vault profile separator. Defaults to comma. */
|
||||
@NotEmpty
|
||||
private String profileSeparator;
|
||||
@@ -101,6 +109,7 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
|
||||
this.port = properties.getPort();
|
||||
this.profileSeparator = properties.getProfileSeparator();
|
||||
this.scheme = properties.getScheme();
|
||||
this.namespace = properties.getNamespace();
|
||||
|
||||
String baseUrl = String.format("%s://%s:%s", this.scheme, this.host, this.port);
|
||||
|
||||
@@ -108,6 +117,10 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
|
||||
properties.getKvVersion());
|
||||
}
|
||||
|
||||
/* for testing */ void setAccessStrategy(VaultKvAccessStrategy accessStrategy) {
|
||||
this.accessStrategy = accessStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Environment findOne(String application, String profile, String label) {
|
||||
|
||||
@@ -186,6 +199,10 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
|
||||
"Missing required header: " + TOKEN_HEADER);
|
||||
}
|
||||
headers.add(VAULT_TOKEN, token);
|
||||
if (StringUtils.hasText(this.namespace)) {
|
||||
headers.add(VAULT_NAMESPACE, this.namespace);
|
||||
}
|
||||
|
||||
return this.accessStrategy.getData(headers, this.backend, key);
|
||||
}
|
||||
|
||||
@@ -213,6 +230,10 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
|
||||
this.profileSeparator = profileSeparator;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.config.server.environment;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -29,10 +30,12 @@ import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.config.environment.Environment;
|
||||
import org.springframework.cloud.config.server.environment.VaultKvAccessStrategy.VaultResponse;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -335,6 +338,48 @@ public class VaultEnvironmentRepositoryTests {
|
||||
.isEqualTo(firstResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "Duplicates", "unchecked" })
|
||||
public void testNamespaceHeaderSent() {
|
||||
MockHttpServletRequest configRequest = new MockHttpServletRequest();
|
||||
configRequest.addHeader("X-CONFIG-TOKEN", "mytoken");
|
||||
|
||||
RestTemplate rest = mock(RestTemplate.class);
|
||||
|
||||
ResponseEntity<VaultResponse> myAppResp = mock(ResponseEntity.class);
|
||||
when(myAppResp.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||
VaultResponse myAppVaultResp = mock(VaultResponse.class);
|
||||
when(myAppVaultResp.getData()).thenReturn("{\"foo\":\"bar\"}");
|
||||
when(myAppResp.getBody()).thenReturn(myAppVaultResp);
|
||||
when(rest.exchange(eq("http://127.0.0.1:8200/v1/secret/{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 = mock(VaultResponse.class);
|
||||
when(appVaultResp.getData()).thenReturn("{\"def-foo\":\"def-bar\"}");
|
||||
when(appResp.getBody()).thenReturn(appVaultResp);
|
||||
when(rest.exchange(eq("http://127.0.0.1:8200/v1/secret/{key}"),
|
||||
eq(HttpMethod.GET), any(HttpEntity.class), eq(VaultResponse.class),
|
||||
eq("application"))).thenReturn(appResp);
|
||||
|
||||
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
|
||||
properties.setNamespace("mynamespace");
|
||||
VaultEnvironmentRepository repo = new VaultEnvironmentRepository(
|
||||
mockProvide(configRequest), new EnvironmentWatch.Default(), rest,
|
||||
properties);
|
||||
|
||||
TestAccessStrategy accessStrategy = new TestAccessStrategy(rest, properties);
|
||||
repo.setAccessStrategy(accessStrategy);
|
||||
|
||||
repo.findOne("myapp", null, null);
|
||||
|
||||
assertThat(accessStrategy.headers).containsEntry(
|
||||
VaultEnvironmentRepository.VAULT_NAMESPACE,
|
||||
Collections.singletonList("mynamespace"));
|
||||
}
|
||||
|
||||
private VaultResponse getVaultResponse(String json) {
|
||||
try {
|
||||
return this.objectMapper.readValue(json, VaultResponse.class);
|
||||
@@ -345,4 +390,27 @@ public class VaultEnvironmentRepositoryTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class TestAccessStrategy implements VaultKvAccessStrategy {
|
||||
|
||||
private final VaultKvAccessStrategy accessStrategy;
|
||||
|
||||
private HttpHeaders headers;
|
||||
|
||||
TestAccessStrategy(RestTemplate restTemplate,
|
||||
VaultEnvironmentProperties properties) {
|
||||
String baseUrl = String.format("%s://%s:%s", properties.getScheme(),
|
||||
properties.getHost(), properties.getPort());
|
||||
this.accessStrategy = VaultKvAccessStrategyFactory.forVersion(restTemplate,
|
||||
baseUrl, properties.getKvVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getData(HttpHeaders headers, String backend, String key)
|
||||
throws RestClientException {
|
||||
this.headers = headers;
|
||||
return this.accessStrategy.getData(headers, backend, key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user