Add support for TLS certificate authentication
We now support TLS client certificate authentication. The client needs to enable SSL and configure a keystore containing the client certificate/private key pair.
spring.cloud.vault:
authentication: CERT
ssl:
key-store: classpath:keystore.jks
key-store-password: changeit
Fixes gh-25
This commit is contained in:
@@ -218,6 +218,37 @@ authentication method
|
||||
|
||||
See also: https://www.vaultproject.io/docs/auth/aws-ec2.html[Vault Documentation: Using the aws-ec2 auth backend]
|
||||
|
||||
=== TLS certificate authentication
|
||||
|
||||
The `cert` auth backend allows authentication using SSL/TLS client
|
||||
certificates that are either signed by a CA or self-signed.
|
||||
|
||||
To enable `cert` authentication you need to:
|
||||
|
||||
1. Use SSL, see <<vault-client-ssl>>
|
||||
2. Configure a Java `Keystore` that contains the client
|
||||
certificate and the private key
|
||||
3. Set the `spring.cloud.vault.authentication` to `CERT`
|
||||
|
||||
[source,yaml]
|
||||
.bootstrap.yml
|
||||
----
|
||||
spring.cloud.vault:
|
||||
authentication: CERT
|
||||
ssl:
|
||||
key-store: classpath:keystore.jks
|
||||
key-store-password: changeit
|
||||
cert-auth-path: cert
|
||||
----
|
||||
|
||||
* `key-store` sets the resource for the key-store. SSL-secured Vault
|
||||
communication will validate the Vault SSL certificate with the specified
|
||||
trust-store.
|
||||
* `key-store-password` sets the key-store password
|
||||
* `cert-auth-path` sets the path of the cert authentication mount to use
|
||||
|
||||
See also: https://www.vaultproject.io/docs/auth/cert.html[Vault Documentation: Using the cert auth backend]
|
||||
|
||||
== Backends
|
||||
|
||||
[[vault-client-generic]]
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.cloud.vault.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.cloud.vault.util.Settings.*;
|
||||
|
||||
import org.assertj.core.util.Files;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Integration test using config infrastructure with TLS certificate authentication. In
|
||||
* case this test should fail because of SSL make sure you run the test within the
|
||||
* spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is
|
||||
* referenced with {@code ../work/keystore.jks}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = VaultConfigTlsCertAuthenticationTests.TestApplication.class)
|
||||
@IntegrationTest({ "spring.cloud.vault.authentication=cert",
|
||||
"spring.cloud.vault.ssl.key-store=file:../work/client-cert.jks",
|
||||
"spring.cloud.vault.ssl.key-store-password=changeit",
|
||||
"spring.application.name=VaultConfigTlsCertAuthenticationTests" })
|
||||
public class VaultConfigTlsCertAuthenticationTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
|
||||
VaultRule vaultRule = new VaultRule();
|
||||
vaultRule.before();
|
||||
|
||||
vaultRule.prepare().writeSecret(
|
||||
VaultConfigTlsCertAuthenticationTests.class.getSimpleName(),
|
||||
Collections.singletonMap("vault.value", "foo"));
|
||||
|
||||
VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
|
||||
if (!vaultRule.prepare().hasAuth(vaultProperties.getSsl().getCertAuthPath())) {
|
||||
vaultRule.prepare().mountAuth(vaultProperties.getSsl().getCertAuthPath());
|
||||
}
|
||||
|
||||
File workDir = findWorkDir();
|
||||
|
||||
String certificate = Files.contentOf(
|
||||
new File(workDir, "ca/certs/client.cert.pem"), StandardCharsets.US_ASCII);
|
||||
|
||||
Map<String, String> role = new HashMap<>();
|
||||
role.put("certificate", certificate);
|
||||
role.put("policies", "root");
|
||||
|
||||
vaultRule.prepare().write("auth/cert/certs/my-role", role);
|
||||
}
|
||||
|
||||
@Value("${vault.value}")
|
||||
String configValue;
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
assertThat(configValue).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
public static class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,13 @@ import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.springframework.cloud.vault.VaultProperties.Ssl;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
@@ -38,11 +38,15 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
|
||||
/**
|
||||
* Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components,
|
||||
* OkHttp, Netty and the JDK HTTP client (in that order). This factory configures a
|
||||
@@ -104,25 +108,48 @@ class ClientHttpRequestFactoryFactory {
|
||||
private static SSLContext getSSLContext(VaultProperties.Ssl ssl)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyManager[] keyManagers = ssl.getKeyStore() != null ? createKeyManagerFactory(
|
||||
ssl.getKeyStore(), ssl.getKeyStorePassword()).getKeyManagers() : null;
|
||||
|
||||
TrustManager[] trustManagers = ssl.getTrustStore() != null ? createTrustManagerFactory(
|
||||
ssl.getTrustStore(), ssl.getTrustStorePassword()).getTrustManagers()
|
||||
: null;
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, createTrustManagerFactory(ssl.getTrustStore(),
|
||||
ssl.getTrustStorePassword()).getTrustManagers(), null);
|
||||
sslContext.init(keyManagers, trustManagers, null);
|
||||
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
private static KeyManagerFactory createKeyManagerFactory(Resource keystoreFile,
|
||||
String storePassword) throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
|
||||
try (InputStream inputStream = keystoreFile.getInputStream()) {
|
||||
keyStore.load(inputStream,
|
||||
StringUtils.hasText(storePassword) ? storePassword.toCharArray()
|
||||
: null);
|
||||
}
|
||||
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory
|
||||
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
keyManagerFactory.init(keyStore,
|
||||
StringUtils.hasText(storePassword) ? storePassword.toCharArray()
|
||||
: new char[0]);
|
||||
|
||||
return keyManagerFactory;
|
||||
}
|
||||
|
||||
private static TrustManagerFactory createTrustManagerFactory(Resource trustFile,
|
||||
String storePassword) throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
InputStream inputStream = trustFile.getInputStream();
|
||||
|
||||
try {
|
||||
trustStore.load(inputStream, StringUtils.hasText(storePassword)
|
||||
? storePassword.toCharArray() : null);
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
try (InputStream inputStream = trustFile.getInputStream()) {
|
||||
trustStore.load(inputStream,
|
||||
StringUtils.hasText(storePassword) ? storePassword.toCharArray()
|
||||
: null);
|
||||
}
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory
|
||||
@@ -133,8 +160,14 @@ class ClientHttpRequestFactoryFactory {
|
||||
}
|
||||
|
||||
private static boolean hasSslConfiguration(VaultProperties vaultProperties) {
|
||||
return vaultProperties.getSsl() != null
|
||||
&& vaultProperties.getSsl().getTrustStore() != null;
|
||||
|
||||
Ssl ssl = vaultProperties.getSsl();
|
||||
|
||||
if (ssl == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ssl.getTrustStore() != null || ssl.getKeyStore() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,30 +176,31 @@ class ClientHttpRequestFactoryFactory {
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class HttpComponents {
|
||||
protected static ClientHttpRequestFactory usingHttpComponents(
|
||||
VaultProperties vaultProperties)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
static ClientHttpRequestFactory usingHttpComponents(
|
||||
VaultProperties vaultProperties) throws GeneralSecurityException,
|
||||
IOException {
|
||||
|
||||
HttpClientBuilder httpClientBuilder = HttpClients.custom();
|
||||
|
||||
if (hasSslConfiguration(vaultProperties)) {
|
||||
|
||||
SSLContext sslContext = getSSLContext(vaultProperties.getSsl());
|
||||
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
|
||||
getSSLContext(vaultProperties.getSsl()));
|
||||
sslContext);
|
||||
httpClientBuilder.setSSLSocketFactory(sslSocketFactory);
|
||||
httpClientBuilder.setSSLContext(sslContext);
|
||||
}
|
||||
|
||||
RequestConfig requestConfig = RequestConfig.custom() //
|
||||
.setConnectTimeout(vaultProperties.getConnectionTimeout()) //
|
||||
.setSocketTimeout(vaultProperties.getReadTimeout()) //
|
||||
.setAuthenticationEnabled(true) //
|
||||
.build();
|
||||
|
||||
httpClientBuilder.setDefaultRequestConfig(requestConfig);
|
||||
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClientBuilder.build());
|
||||
|
||||
return factory;
|
||||
return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +211,7 @@ class ClientHttpRequestFactoryFactory {
|
||||
*/
|
||||
static class OkHttp {
|
||||
|
||||
protected static ClientHttpRequestFactory usingOkHttp(
|
||||
VaultProperties vaultProperties)
|
||||
static ClientHttpRequestFactory usingOkHttp(VaultProperties vaultProperties)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
final OkHttpClient okHttpClient = new OkHttpClient();
|
||||
@@ -198,8 +231,8 @@ class ClientHttpRequestFactoryFactory {
|
||||
};
|
||||
|
||||
if (hasSslConfiguration(vaultProperties)) {
|
||||
okHttpClient.setSslSocketFactory(
|
||||
getSSLContext(vaultProperties.getSsl()).getSocketFactory());
|
||||
okHttpClient.setSslSocketFactory(getSSLContext(vaultProperties.getSsl())
|
||||
.getSocketFactory());
|
||||
}
|
||||
|
||||
requestFactory.setConnectTimeout(vaultProperties.getConnectionTimeout());
|
||||
@@ -215,8 +248,8 @@ class ClientHttpRequestFactoryFactory {
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class Netty {
|
||||
protected static ClientHttpRequestFactory usingNetty(
|
||||
VaultProperties vaultProperties)
|
||||
|
||||
static ClientHttpRequestFactory usingNetty(VaultProperties vaultProperties)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
VaultProperties.Ssl ssl = vaultProperties.getSsl();
|
||||
@@ -225,14 +258,21 @@ class ClientHttpRequestFactoryFactory {
|
||||
|
||||
if (hasSslConfiguration(vaultProperties)) {
|
||||
|
||||
SslContext sslContext = SslContextBuilder //
|
||||
.forClient() //
|
||||
.trustManager(createTrustManagerFactory(ssl.getTrustStore(),
|
||||
ssl.getTrustStorePassword())) //
|
||||
.sslProvider(SslProvider.JDK) //
|
||||
.build();
|
||||
SslContextBuilder sslContextBuilder = SslContextBuilder //
|
||||
.forClient();
|
||||
|
||||
requestFactory.setSslContext(sslContext);
|
||||
if (ssl.getTrustStore() != null) {
|
||||
sslContextBuilder.trustManager(createTrustManagerFactory(
|
||||
ssl.getTrustStore(), ssl.getTrustStorePassword()));
|
||||
}
|
||||
|
||||
if (ssl.getKeyStore() != null) {
|
||||
sslContextBuilder.keyManager(createKeyManagerFactory(
|
||||
ssl.getKeyStore(), ssl.getKeyStorePassword()));
|
||||
}
|
||||
|
||||
requestFactory.setSslContext(sslContextBuilder.sslProvider(
|
||||
SslProvider.JDK).build());
|
||||
}
|
||||
|
||||
requestFactory.setConnectTimeout(vaultProperties.getConnectionTimeout());
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
package org.springframework.cloud.vault;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.VaultProperties.Ssl;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -90,6 +92,13 @@ class DefaultClientAuthentication extends ClientAuthentication {
|
||||
appIdUserIdMechanism.createUserId()), appId);
|
||||
}
|
||||
|
||||
if (properties.getAuthentication() == AuthenticationMethod.CERT
|
||||
&& properties.getSsl() != null) {
|
||||
log.info("Using TLS Certificate authentication to log into Vault");
|
||||
|
||||
return createTokenUsingTlsCertAuthentication(properties.getSsl());
|
||||
}
|
||||
|
||||
if (properties.getAuthentication() == VaultProperties.AuthenticationMethod.AWS_EC2) {
|
||||
log.info("Using AWS-EC2 authentication to log into Vault");
|
||||
|
||||
@@ -104,14 +113,13 @@ class DefaultClientAuthentication extends ClientAuthentication {
|
||||
private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple,
|
||||
VaultProperties.AppIdProperties appId) {
|
||||
|
||||
URI uri = vaultClient.buildUri(properties,
|
||||
URI uri = VaultClient.buildUri(properties,
|
||||
String.format("auth/%s/login", appId.getAppIdPath()));
|
||||
|
||||
Map<String, String> login = getAppIdLogin(appIdTuple);
|
||||
|
||||
VaultClientResponse response = vaultClient.write(uri, login);
|
||||
|
||||
HttpStatus status = response.getStatusCode();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Cannot login using app-id: %s", response.getMessage()));
|
||||
@@ -125,6 +133,26 @@ class DefaultClientAuthentication extends ClientAuthentication {
|
||||
return VaultToken.of(token, body.getLeaseDuration());
|
||||
}
|
||||
|
||||
private VaultToken createTokenUsingTlsCertAuthentication(Ssl ssl) {
|
||||
|
||||
URI uri = VaultClient.buildUri(properties,
|
||||
String.format("auth/%s/login", ssl.getCertAuthPath()));
|
||||
|
||||
VaultClientResponse response = vaultClient.write(uri, Collections.emptyMap());
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Cannot login using TLS certificates: %s", response.getMessage()));
|
||||
}
|
||||
|
||||
VaultResponse body = response.getBody();
|
||||
String token = (String) body.getAuth().get("client_token");
|
||||
|
||||
log.debug("Login successful using TLS certificates");
|
||||
|
||||
return VaultToken.of(token, body.getLeaseDuration());
|
||||
}
|
||||
|
||||
private Map<String, String> getAppIdLogin(AppIdTuple appIdTuple) {
|
||||
|
||||
Map<String, String> login = new HashMap<>();
|
||||
@@ -138,7 +166,7 @@ class DefaultClientAuthentication extends ClientAuthentication {
|
||||
|
||||
VaultProperties.AwsEc2Properties awsEc2 = this.properties.getAwsEc2();
|
||||
|
||||
URI uri = vaultClient.buildUri(this.properties,
|
||||
URI uri = VaultClient.buildUri(this.properties,
|
||||
String.format("auth/%s/login", awsEc2.getAwsEc2Path()));
|
||||
|
||||
Map<String, String> login = getEc2Login(awsEc2);
|
||||
|
||||
@@ -150,6 +150,16 @@ public class VaultProperties {
|
||||
@Data
|
||||
public static class Ssl {
|
||||
|
||||
/**
|
||||
* Trust store that holds certificates and private keys.
|
||||
*/
|
||||
private Resource keyStore;
|
||||
|
||||
/**
|
||||
* Password used to access the key store.
|
||||
*/
|
||||
private String keyStorePassword;
|
||||
|
||||
/**
|
||||
* Trust store that holds SSL certificates.
|
||||
*/
|
||||
@@ -159,9 +169,15 @@ public class VaultProperties {
|
||||
* Password used to access the trust store.
|
||||
*/
|
||||
private String trustStorePassword;
|
||||
|
||||
/**
|
||||
* Mount path of the TLS cert authentication backend.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String certAuthPath = "cert";
|
||||
}
|
||||
|
||||
public enum AuthenticationMethod {
|
||||
TOKEN, APPID, AWS_EC2,
|
||||
TOKEN, APPID, AWS_EC2, CERT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.cloud.vault;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.cloud.vault.ClientHttpRequestFactoryFactory.Netty;
|
||||
import org.springframework.cloud.vault.ClientHttpRequestFactoryFactory.OkHttp;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.cloud.vault.util.Settings.*;
|
||||
|
||||
import org.assertj.core.util.Files;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using TLS certificate authentication using
|
||||
* various HTTP clients.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CertAuthenticationMethodsIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
VaultProperties vaultProperties = prepareCertAuthenticationMethod();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
if (!prepare().hasAuth("cert")) {
|
||||
prepare().mountAuth("cert");
|
||||
}
|
||||
|
||||
File workDir = findWorkDir();
|
||||
|
||||
String certificate = Files.contentOf(
|
||||
new File(workDir, "ca/certs/client.cert.pem"), StandardCharsets.US_ASCII);
|
||||
|
||||
prepare().write("auth/cert/certs/my-role",
|
||||
Collections.singletonMap("certificate", certificate));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAuthenticateUsingCertificateAuthenticationUsingHttpComponents()
|
||||
throws Exception {
|
||||
|
||||
VaultClient client = new VaultClient(
|
||||
TestRestTemplateFactory
|
||||
.create(ClientHttpRequestFactoryFactory.HttpComponents
|
||||
.usingHttpComponents(vaultProperties)));
|
||||
|
||||
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
|
||||
vaultProperties, client);
|
||||
|
||||
assertThat(clientAuthentication.login()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAuthenticateUsingCertificateAuthenticationUsingOkHttp()
|
||||
throws Exception {
|
||||
|
||||
ClientHttpRequestFactory factory = OkHttp.usingOkHttp(vaultProperties);
|
||||
|
||||
VaultClient client = new VaultClient(TestRestTemplateFactory.create(factory));
|
||||
|
||||
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
|
||||
vaultProperties, client);
|
||||
|
||||
assertThat(clientAuthentication.login()).isNotNull();
|
||||
|
||||
((DisposableBean) factory).destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAuthenticateUsingCertificateAuthenticationUsingNetty()
|
||||
throws Exception {
|
||||
|
||||
ClientHttpRequestFactory factory = Netty.usingNetty(vaultProperties);
|
||||
|
||||
VaultClient client = new VaultClient(TestRestTemplateFactory.create(factory));
|
||||
|
||||
ClientAuthentication clientAuthentication = new DefaultClientAuthentication(
|
||||
vaultProperties, client);
|
||||
|
||||
assertThat(clientAuthentication.login()).isNotNull();
|
||||
|
||||
((DisposableBean) factory).destroy();
|
||||
}
|
||||
|
||||
private VaultProperties prepareCertAuthenticationMethod() {
|
||||
|
||||
VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
|
||||
vaultProperties.setAuthentication(AuthenticationMethod.CERT);
|
||||
|
||||
vaultProperties.getSsl().setKeyStorePassword("changeit");
|
||||
vaultProperties.getSsl().setKeyStore(
|
||||
new FileSystemResource(new File(findWorkDir(), "client-cert.jks")));
|
||||
|
||||
return vaultProperties;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
@@ -37,23 +38,47 @@ public class TestRestTemplateFactory {
|
||||
private final static AtomicReference<ClientHttpRequestFactory> factoryCache = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* @param vaultProperties
|
||||
* Create a new {@link TestRestTemplate} using the {@link VaultProperties}. The
|
||||
* underlying {@link ClientHttpRequestFactory} is cached. See
|
||||
* {@link #create(ClientHttpRequestFactory)} to create {@link TestRestTemplate} for a
|
||||
* given {@link ClientHttpRequestFactory}.
|
||||
*
|
||||
* @param vaultProperties must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static TestRestTemplate create(VaultProperties vaultProperties) {
|
||||
|
||||
Assert.notNull(vaultProperties, "VaultProperties must not be null!");
|
||||
|
||||
initializeClientHttpRequestFactory(vaultProperties);
|
||||
return create(factoryCache.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link TestRestTemplate} using the {@link ClientHttpRequestFactory}.
|
||||
* The {@link TestRestTemplate} will throw
|
||||
* {@link org.springframework.web.client.HttpStatusCodeException exceptions} in error
|
||||
* cases and behave in that aspect like the regular
|
||||
* {@link org.springframework.web.client.RestTemplate}.
|
||||
*
|
||||
* @param requestFactory must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static TestRestTemplate create(ClientHttpRequestFactory requestFactory) {
|
||||
|
||||
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null!");
|
||||
|
||||
TestRestTemplate testRestTemplate = new TestRestTemplate();
|
||||
testRestTemplate.setErrorHandler(new DefaultResponseErrorHandler());
|
||||
testRestTemplate.setRequestFactory(factoryCache.get());
|
||||
testRestTemplate.setRequestFactory(requestFactory);
|
||||
|
||||
return testRestTemplate;
|
||||
}
|
||||
|
||||
private static void initializeClientHttpRequestFactory(
|
||||
VaultProperties vaultProperties) throws Exception {
|
||||
private static void initializeClientHttpRequestFactory(VaultProperties vaultProperties)
|
||||
throws Exception {
|
||||
|
||||
if (factoryCache.get() != null) {
|
||||
return;
|
||||
|
||||
@@ -33,20 +33,38 @@ public class Settings {
|
||||
*/
|
||||
public static VaultProperties createVaultProperties() {
|
||||
|
||||
File workDir = findWorkDir(new File(System.getProperty("user.dir")));
|
||||
File workDir = findWorkDir();
|
||||
|
||||
VaultProperties vaultProperties = new VaultProperties();
|
||||
vaultProperties.getSsl().setTrustStorePassword("changeit");
|
||||
vaultProperties.getSsl().setTrustStore(new FileSystemResource(new File(workDir, "keystore.jks")));
|
||||
vaultProperties.getSsl().setTrustStore(
|
||||
new FileSystemResource(new File(workDir, "keystore.jks")));
|
||||
vaultProperties.setToken(token().getToken());
|
||||
|
||||
return vaultProperties;
|
||||
}
|
||||
|
||||
private static File findWorkDir(File file) {
|
||||
/**
|
||||
* Find the {@code work} directory, starting at the {@code user.dir} directory. Search
|
||||
* is performed by walking the parent directories.
|
||||
* @return the {@link File} pointing to the {@code work} directory
|
||||
* @throws IllegalStateException If the {@code work} directory cannot be found.
|
||||
*/
|
||||
public static File findWorkDir() {
|
||||
return findWorkDir(new File(System.getProperty("user.dir")));
|
||||
}
|
||||
|
||||
File searchLevel = file;
|
||||
while (searchLevel.getParentFile() != null && searchLevel.getParentFile() != searchLevel) {
|
||||
/**
|
||||
* Find the {@code work} directory, starting at the given {@code directory}. Search
|
||||
* is performed by walking the parent directories.
|
||||
* @return the {@link File} pointing to the {@code work} directory
|
||||
* @throws IllegalStateException If the {@code work} directory cannot be found.
|
||||
*/
|
||||
public static File findWorkDir(File directory) {
|
||||
|
||||
File searchLevel = directory;
|
||||
while (searchLevel.getParentFile() != null
|
||||
&& searchLevel.getParentFile() != searchLevel) {
|
||||
|
||||
File work = new File(searchLevel, "work");
|
||||
if (work.isDirectory() && work.exists()) {
|
||||
@@ -56,14 +74,16 @@ public class Settings {
|
||||
searchLevel = searchLevel.getParentFile();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("Cannot find work directory in %s or any parent directories", file.getAbsoluteFile()));
|
||||
throw new IllegalStateException(String.format(
|
||||
"Cannot find work directory in %s or any parent directories",
|
||||
directory.getAbsoluteFile()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the token to use during tests.
|
||||
*/
|
||||
public static VaultToken token() {
|
||||
return VaultToken.of(System.getProperty("vault.token", "00000000-0000-0000-0000-000000000000"));
|
||||
return VaultToken.of(System.getProperty("vault.token",
|
||||
"00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
CA_DIR=work/ca
|
||||
JKS_FILE=work/keystore.jks
|
||||
KEYSTORE_FILE=work/keystore.jks
|
||||
CLIENT_CERT_KEYSTORE=work/client-cert.jks
|
||||
if [[ -d work/ca ]] ; then
|
||||
rm -Rf ${CA_DIR}
|
||||
fi
|
||||
|
||||
if [[ -f ${JKS_FILE} ]] ; then
|
||||
rm -Rf ${JKS_FILE}
|
||||
if [[ -f ${KEYSTORE_FILE} ]] ; then
|
||||
rm -Rf ${KEYSTORE_FILE}
|
||||
fi
|
||||
|
||||
if [[ -f ${CLIENT_CERT_KEYSTORE} ]] ; then
|
||||
rm -Rf ${CLIENT_CERT_KEYSTORE}
|
||||
fi
|
||||
|
||||
mkdir -p ${CA_DIR}/private ${CA_DIR}/certs ${CA_DIR}/crl ${CA_DIR}/csr ${CA_DIR}/newcerts ${CA_DIR}/intermediate
|
||||
@@ -57,4 +62,42 @@ openssl ca -config ${DIR}/openssl.cnf \
|
||||
-in ${CA_DIR}/csr/localhost.csr.pem \
|
||||
-out ${CA_DIR}/certs/localhost.cert.pem
|
||||
|
||||
${JAVA_HOME}/bin/keytool -importcert -keystore ${JKS_FILE} -file ${CA_DIR}/certs/ca.cert.pem -noprompt -storepass changeit
|
||||
|
||||
echo "[INFO] Generating client auth private key"
|
||||
openssl genrsa -aes256 \
|
||||
-passout pass:changeit \
|
||||
-out ${CA_DIR}/private/client.key.pem 2048
|
||||
|
||||
openssl rsa -in ${CA_DIR}/private/client.key.pem \
|
||||
-out ${CA_DIR}/private/client.decrypted.key.pem \
|
||||
-passin pass:changeit
|
||||
|
||||
chmod 400 ${CA_DIR}/private/client.key.pem
|
||||
|
||||
echo "[INFO] Generating client certificate request"
|
||||
openssl req -config ${DIR}/openssl.cnf \
|
||||
-key ${CA_DIR}/private/client.key.pem \
|
||||
-passin pass:changeit \
|
||||
-new -sha256 -out ${CA_DIR}/csr/client.csr.pem \
|
||||
-subj "/C=NN/ST=Unknown/L=Unknown/O=spring-cloud-vault-config/CN=client"
|
||||
|
||||
echo "[INFO] Signing certificate request"
|
||||
openssl ca -config ${DIR}/openssl.cnf \
|
||||
-extensions usr_cert -days 375 -notext -md sha256 \
|
||||
-passin pass:changeit \
|
||||
-batch \
|
||||
-in ${CA_DIR}/csr/client.csr.pem \
|
||||
-out ${CA_DIR}/certs/client.cert.pem
|
||||
|
||||
echo "[INFO] Creating PKCS12 file with client certificate"
|
||||
openssl pkcs12 -export -clcerts \
|
||||
-in ${CA_DIR}/certs/client.cert.pem \
|
||||
-inkey ${CA_DIR}/private/client.decrypted.key.pem \
|
||||
-passout pass:changeit \
|
||||
-out ${CA_DIR}/client.p12
|
||||
|
||||
${JAVA_HOME}/bin/keytool -importcert -keystore ${KEYSTORE_FILE} -file ${CA_DIR}/certs/ca.cert.pem -noprompt -storepass changeit
|
||||
${JAVA_HOME}/bin/keytool -importkeystore \
|
||||
-srckeystore ${CA_DIR}/client.p12 -srcstoretype PKCS12 -srcstorepass changeit\
|
||||
-destkeystore ${CLIENT_CERT_KEYSTORE} -deststoretype JKS \
|
||||
-noprompt -storepass changeit
|
||||
|
||||
@@ -43,4 +43,4 @@ unzip ../download/${VAULT_ZIP}
|
||||
chmod a+x vault
|
||||
|
||||
# check
|
||||
./vault --version
|
||||
./vault --version
|
||||
|
||||
Reference in New Issue
Block a user