Support for Vault Agent
We now support Vault Agent usage by creating VaultTemplate and ReactiveVaultTemplate without a client authentication mechanism/session manager to inherit Vault Agent's authentication. Closes gh-515
This commit is contained in:
@@ -40,6 +40,7 @@ import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.client.WebClientBuilder;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultResponseSupport;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
@@ -68,6 +69,22 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
|
||||
|
||||
private final VaultTokenSupplier vaultTokenSupplier;
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpoint},
|
||||
* {@link ClientHttpConnector}. This constructor does not use a
|
||||
* {@link VaultTokenSupplier}. It is intended for usage with Vault Agent to inherit
|
||||
* Vault Agent's authentication without using the {@link VaultHttpHeaders#VAULT_TOKEN
|
||||
* authentication token header}.
|
||||
*
|
||||
* @param vaultEndpoint must not be {@literal null}.
|
||||
* @param connector must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public ReactiveVaultTemplate(VaultEndpoint vaultEndpoint,
|
||||
ClientHttpConnector connector) {
|
||||
this(SimpleVaultEndpointProvider.of(vaultEndpoint), connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpoint},
|
||||
* {@link ClientHttpConnector} and {@link VaultTokenSupplier}.
|
||||
@@ -82,6 +99,30 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
|
||||
vaultTokenSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpointProvider} and
|
||||
* {@link ClientHttpConnector}. This constructor does not use a
|
||||
* {@link VaultTokenSupplier}. It is intended for usage with Vault Agent to inherit
|
||||
* Vault Agent's authentication without using the {@link VaultHttpHeaders#VAULT_TOKEN
|
||||
* authentication token header}.
|
||||
*
|
||||
* @param endpointProvider must not be {@literal null}.
|
||||
* @param connector must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public ReactiveVaultTemplate(VaultEndpointProvider endpointProvider,
|
||||
ClientHttpConnector connector) {
|
||||
|
||||
Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
|
||||
Assert.notNull(connector, "ClientHttpConnector must not be null");
|
||||
|
||||
WebClient webClient = doCreateWebClient(endpointProvider, connector);
|
||||
|
||||
this.vaultTokenSupplier = NoTokenSupplier.INSTANCE;
|
||||
this.statelessClient = webClient;
|
||||
this.sessionClient = webClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveVaultTemplate} with a {@link VaultEndpointProvider},
|
||||
* {@link ClientHttpConnector} and {@link VaultTokenSupplier}.
|
||||
@@ -102,6 +143,26 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
|
||||
this.sessionClient = doCreateSessionWebClient(endpointProvider, connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveVaultTemplate} through a {@link WebClientBuilder}. This
|
||||
* constructor does not use a {@link VaultTokenSupplier}. It is intended for usage
|
||||
* with Vault Agent to inherit Vault Agent's authentication without using the
|
||||
* {@link VaultHttpHeaders#VAULT_TOKEN authentication token header}.
|
||||
*
|
||||
* @param webClientBuilder must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public ReactiveVaultTemplate(WebClientBuilder webClientBuilder) {
|
||||
|
||||
Assert.notNull(webClientBuilder, "WebClientBuilder must not be null");
|
||||
|
||||
WebClient webClient = webClientBuilder.build();
|
||||
|
||||
this.vaultTokenSupplier = NoTokenSupplier.INSTANCE;
|
||||
this.statelessClient = webClient;
|
||||
this.sessionClient = webClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveVaultTemplate} through a {@link WebClientBuilder}, and
|
||||
* {@link VaultTokenSupplier}.
|
||||
@@ -321,4 +382,14 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
|
||||
private static class VaultListResponse
|
||||
extends VaultResponseSupport<Map<String, Object>> {
|
||||
}
|
||||
|
||||
private enum NoTokenSupplier implements VaultTokenSupplier {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Mono<VaultToken> getVaultToken() {
|
||||
return Mono
|
||||
.error(new UnsupportedOperationException("Token retrieval disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultResponseSupport;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@@ -62,6 +63,20 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
|
||||
|
||||
private final boolean dedicatedSessionManager;
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} with a {@link VaultEndpoint}. This constructor
|
||||
* does not use a {@link ClientAuthentication} mechanism. It is intended for usage
|
||||
* with Vault Agent to inherit Vault Agent's authentication without using the
|
||||
* {@link VaultHttpHeaders#VAULT_TOKEN authentication token header}.
|
||||
*
|
||||
* @param vaultEndpoint must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public VaultTemplate(VaultEndpoint vaultEndpoint) {
|
||||
this(SimpleVaultEndpointProvider.of(vaultEndpoint),
|
||||
new SimpleClientHttpRequestFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} with a {@link VaultEndpoint} and
|
||||
* {@link ClientAuthentication}.
|
||||
@@ -87,6 +102,22 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
|
||||
this.sessionTemplate = doCreateSessionTemplate(endpointProvider, requestFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} with a {@link VaultEndpoint}, and
|
||||
* {@link ClientHttpRequestFactory}. This constructor does not use a
|
||||
* {@link ClientAuthentication} mechanism. It is intended for usage with Vault Agent
|
||||
* to inherit Vault Agent's authentication without using the
|
||||
* {@link VaultHttpHeaders#VAULT_TOKEN authentication token header}.
|
||||
*
|
||||
* @param vaultEndpoint must not be {@literal null}.
|
||||
* @param clientHttpRequestFactory must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public VaultTemplate(VaultEndpoint vaultEndpoint,
|
||||
ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||
this(SimpleVaultEndpointProvider.of(vaultEndpoint), clientHttpRequestFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} with a {@link VaultEndpoint},
|
||||
* {@link ClientHttpRequestFactory} and {@link SessionManager}.
|
||||
@@ -102,6 +133,32 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
|
||||
sessionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} with a {@link VaultEndpointProvider},
|
||||
* {@link ClientHttpRequestFactory} and {@link SessionManager}. This constructor does
|
||||
* not use a {@link ClientAuthentication} mechanism. It is intended for usage with
|
||||
* Vault Agent to inherit Vault Agent's authentication without using the
|
||||
* {@link VaultHttpHeaders#VAULT_TOKEN authentication token header}.
|
||||
*
|
||||
* @param endpointProvider must not be {@literal null}.
|
||||
* @param requestFactory must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public VaultTemplate(VaultEndpointProvider endpointProvider,
|
||||
ClientHttpRequestFactory requestFactory) {
|
||||
|
||||
Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
|
||||
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
|
||||
|
||||
RestTemplate restTemplate = doCreateRestTemplate(endpointProvider,
|
||||
requestFactory);
|
||||
|
||||
this.sessionManager = NoSessionManager.INSTANCE;
|
||||
this.dedicatedSessionManager = false;
|
||||
this.statelessTemplate = restTemplate;
|
||||
this.sessionTemplate = restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} with a {@link VaultEndpointProvider},
|
||||
* {@link ClientHttpRequestFactory} and {@link SessionManager}.
|
||||
@@ -120,11 +177,32 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
|
||||
|
||||
this.sessionManager = sessionManager;
|
||||
this.dedicatedSessionManager = false;
|
||||
|
||||
this.statelessTemplate = doCreateRestTemplate(endpointProvider, requestFactory);
|
||||
this.sessionTemplate = doCreateSessionTemplate(endpointProvider, requestFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} through a {@link RestTemplateBuilder} and
|
||||
* {@link SessionManager}. This constructor does not use a
|
||||
* {@link ClientAuthentication} mechanism. It is intended for usage with Vault Agent
|
||||
* to inherit Vault Agent's authentication without using the
|
||||
* {@link VaultHttpHeaders#VAULT_TOKEN authentication token header}.
|
||||
*
|
||||
* @param restTemplateBuilder must not be {@literal null}.
|
||||
* @since 2.2.1
|
||||
*/
|
||||
public VaultTemplate(RestTemplateBuilder restTemplateBuilder) {
|
||||
|
||||
Assert.notNull(restTemplateBuilder, "RestTemplateBuilder must not be null");
|
||||
|
||||
RestTemplate restTemplate = restTemplateBuilder.build();
|
||||
|
||||
this.sessionManager = NoSessionManager.INSTANCE;
|
||||
this.dedicatedSessionManager = false;
|
||||
this.statelessTemplate = restTemplate;
|
||||
this.sessionTemplate = restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTemplate} through a {@link RestTemplateBuilder} and
|
||||
* {@link SessionManager}.
|
||||
@@ -410,4 +488,13 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private enum NoSessionManager implements SessionManager {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public VaultToken getSessionToken() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package org.springframework.vault.authentication;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.util.Files;
|
||||
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.vault.core.RestOperationsCallback;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
@@ -40,6 +41,11 @@ import static org.springframework.vault.util.Settings.findWorkDir;
|
||||
public abstract class ClientCertificateAuthenticationIntegrationTestBase
|
||||
extends IntegrationTestSupport {
|
||||
|
||||
static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*")
|
||||
.capabilities(Policy.BuiltinCapabilities.READ,
|
||||
Policy.BuiltinCapabilities.CREATE, Policy.BuiltinCapabilities.UPDATE)
|
||||
.build());
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
||||
@@ -47,6 +53,9 @@ public abstract class ClientCertificateAuthenticationIntegrationTestBase
|
||||
prepare().mountAuth("cert");
|
||||
}
|
||||
|
||||
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy("cert-auth",
|
||||
POLICY);
|
||||
|
||||
prepare().getVaultOperations()
|
||||
.doWithSession((RestOperationsCallback<Object>) restOperations -> {
|
||||
File workDir = findWorkDir();
|
||||
@@ -55,8 +64,12 @@ public abstract class ClientCertificateAuthenticationIntegrationTestBase
|
||||
new File(workDir, "ca/certs/client.cert.pem"),
|
||||
StandardCharsets.US_ASCII);
|
||||
|
||||
Map<String, Object> role = new LinkedHashMap<>();
|
||||
role.put("token_policies", "cert-auth");
|
||||
role.put("certificate", certificate);
|
||||
|
||||
return restOperations.postForEntity("auth/cert/certs/my-role",
|
||||
Collections.singletonMap("certificate", certificate),
|
||||
role,
|
||||
Map.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opentest4j.TestAbortedException;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.vault.client.ClientHttpConnectorFactory;
|
||||
import org.springframework.vault.client.VaultEndpoint;
|
||||
import org.springframework.vault.client.WebClientBuilder;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.Settings;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ReactiveVaultTemplate} through Vault Agent.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class ReactiveVaultTemplateAgentIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
ClientHttpConnector connector = ClientHttpConnectorFactory.create(new ClientOptions(),
|
||||
Settings.createSslConfiguration());
|
||||
|
||||
VaultEndpoint endpoint = VaultEndpoint.create("localhost", 8202);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
try (Socket socket = new Socket()) {
|
||||
|
||||
socket.connect(new InetSocketAddress(endpoint.getHost(), endpoint.getPort()),
|
||||
(int) new ClientOptions().getConnectionTimeout().toMillis());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new TestAbortedException(
|
||||
"Vault Agent not available: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseAgentAuthentication() {
|
||||
|
||||
ReactiveVaultTemplate vaultTemplate = new ReactiveVaultTemplate(endpoint,
|
||||
connector);
|
||||
|
||||
vaultTemplate.write("secret/foo", Collections.singletonMap("key", "value"))
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseAgentAuthenticationWithBuilder() {
|
||||
|
||||
WebClientBuilder builder = WebClientBuilder.builder().endpoint(endpoint)
|
||||
.httpConnector(connector);
|
||||
|
||||
ReactiveVaultTemplate vaultTemplate = new ReactiveVaultTemplate(builder);
|
||||
|
||||
vaultTemplate.write("secret/foo", Collections.singletonMap("key", "value"))
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opentest4j.TestAbortedException;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
|
||||
import org.springframework.vault.client.RestTemplateBuilder;
|
||||
import org.springframework.vault.client.VaultEndpoint;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.Settings;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultTemplate} through Vault Agent.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class VaultTemplateAgentIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryFactory
|
||||
.create(new ClientOptions(), Settings.createSslConfiguration());
|
||||
|
||||
VaultEndpoint endpoint = VaultEndpoint.create("localhost", 8202);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
try (Socket socket = new Socket()) {
|
||||
|
||||
socket.connect(new InetSocketAddress(endpoint.getHost(), endpoint.getPort()),
|
||||
(int) new ClientOptions().getConnectionTimeout().toMillis());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new TestAbortedException(
|
||||
"Vault Agent not available: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseAgentAuthentication() {
|
||||
|
||||
VaultTemplate vaultTemplate = new VaultTemplate(endpoint, requestFactory);
|
||||
|
||||
vaultTemplate.write("secret/foo", Collections.singletonMap("key", "value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseAgentAuthenticationWithBuilder() {
|
||||
|
||||
RestTemplateBuilder builder = RestTemplateBuilder.builder().endpoint(endpoint)
|
||||
.requestFactory(requestFactory);
|
||||
|
||||
VaultTemplate vaultTemplate = new VaultTemplate(builder);
|
||||
|
||||
vaultTemplate.write("secret/foo", Collections.singletonMap("key", "value"));
|
||||
}
|
||||
}
|
||||
25
src/test/bash/vault-agent.conf
Normal file
25
src/test/bash/vault-agent.conf
Normal file
@@ -0,0 +1,25 @@
|
||||
exit_after_auth = false
|
||||
pid_file = "./pidfile"
|
||||
|
||||
vault {
|
||||
address = "https://127.0.0.1:8200"
|
||||
ca_cert = "work/ca/certs/ca.cert.pem"
|
||||
client_cert = "work/ca/certs/client.cert.pem"
|
||||
client_key = "work/ca/private/client.decrypted.key.pem"
|
||||
}
|
||||
|
||||
auto_auth {
|
||||
method "cert" {
|
||||
mount_path = "auth/cert"
|
||||
}
|
||||
}
|
||||
|
||||
cache {
|
||||
use_auto_auth_token = true
|
||||
}
|
||||
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8202"
|
||||
tls_cert_file = "work/ca/certs/localhost.cert.pem"
|
||||
tls_key_file = "work/ca/private/localhost.decrypted.key.pem"
|
||||
}
|
||||
Reference in New Issue
Block a user