Add support for cubbyhole authentication.

We now support cubbyhole authentication to securely use tokens. Cubbyhole authentication uses Vault primitives to provide a secured authentication workflow. Cubbyhole authentication uses tokens as primary login method.
An ephemeral token is used to obtain a second, login VaultToken from Vault's Cubbyhole secret backend. The login token is usually longer-lived and used to interact with Vault. The login token will be retrieved from a wrapped response stored at `/cubbyhole/response`.

A wrapped token can be created with:
vault token-create -wrap-ttl="10m"

boostrap.yml:
spring.cloud.vault:
    authentication: CUBBYHOLE
    token: (the wrapping token)

Fixes gh-15.
This commit is contained in:
Mark Paluch
2016-09-21 22:21:31 +02:00
parent 2ef37e2f88
commit 7c7aa05ae8
4 changed files with 171 additions and 5 deletions

View File

@@ -250,6 +250,48 @@ trust-store.
See also: https://www.vaultproject.io/docs/auth/cert.html[Vault Documentation: Using the cert auth backend]
== Cubbyhole authentication
Cubbyhole authentication uses Vault primitives to provide a secured authentication
workflow. Cubbyhole authentication uses tokens as primary login method.
An ephemeral token is used to obtain a second, login VaultToken from Vault's
Cubbyhole secret backend. The login token is usually longer-lived and used to
interact with Vault. The login token will be retrieved from a wrapped
response stored at `/cubbyhole/response`.
*Creating a wrapped token*
NOTE: Response Wrapping for token creation requires Vault 0.6.0 or higher.
.Crating and storing tokens
====
[source,shell]
----
$ vault token-create -wrap-ttl="10m"
Key Value
--- -----
wrapping_token: 397ccb93-ff6c-b17b-9389-380b01ca2645
wrapping_token_ttl: 0h10m0s
wrapping_token_creation_time: 2016-09-18 20:29:48.652957077 +0200 CEST
wrapped_accessor: 46b6aebb-187f-932a-26d7-4f3d86a68319
----
====
[source,yaml]
.bootstrap.yml
----
spring.cloud.vault:
authentication: CUBBYHOLE
token: 397ccb93-ff6c-b17b-9389-380b01ca2645
----
See also:
* https://www.vaultproject.io/docs/concepts/tokens.html[Vault Documentation: Tokens]
* https://www.vaultproject.io/docs/secrets/cubbyhole/index.html[Vault Documentation:Cubbyhole Secret Backend]
* https://www.vaultproject.io/docs/concepts/response-wrapping.html[Vault Documentation: Response Wrapping]
== Backends
[[vault-client-generic]]

View File

@@ -38,6 +38,8 @@ import org.springframework.vault.authentication.AwsEc2Authentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ClientCertificateAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthenticationOptions;
import org.springframework.vault.authentication.DefaultSessionManager;
import org.springframework.vault.authentication.IpAddressUserId;
import org.springframework.vault.authentication.MacAddressUserId;
@@ -54,6 +56,7 @@ import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestTemplate;
/**
@@ -73,7 +76,8 @@ public class VaultBootstrapConfiguration {
private final Collection<VaultSecretBackend> vaultSecretBackends;
private final Collection<SecureBackendAccessorFactory<? super VaultSecretBackend>> factories;
public VaultBootstrapConfiguration(ApplicationContext applicationContext, VaultProperties vaultProperties) {
public VaultBootstrapConfiguration(ApplicationContext applicationContext,
VaultProperties vaultProperties) {
this.applicationContext = applicationContext;
this.vaultProperties = vaultProperties;
@@ -98,7 +102,6 @@ public class VaultBootstrapConfiguration {
vaultGenericBackendProperties, backendAccessors);
}
@Bean
@ConditionalOnMissingBean
public ClientAuthentication clientAuthentication() {
@@ -108,8 +111,8 @@ public class VaultBootstrapConfiguration {
switch (vaultProperties.getAuthentication()) {
case TOKEN:
Assert.hasText("Token (spring.cloud.vault.token) must not be empty",
vaultProperties.getToken());
Assert.hasText(vaultProperties.getToken(),
"Token (spring.cloud.vault.token) must not be empty");
return new TokenAuthentication(vaultProperties.getToken());
case APPID:
@@ -121,6 +124,9 @@ public class VaultBootstrapConfiguration {
case AWS_EC2:
return awsEc2Authentication(vaultProperties, vaultClient);
case CUBBYHOLE:
return cubbyholeAuthentication(vaultClient);
}
throw new UnsupportedOperationException(
@@ -189,6 +195,19 @@ public class VaultBootstrapConfiguration {
vaultClient.getRestTemplate());
}
private ClientAuthentication cubbyholeAuthentication(VaultClient vaultClient) {
Assert.hasText(vaultProperties.getToken(),
"Initial Token (spring.cloud.vault.token) for Cubbyhole authentication must not be empty");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder() //
.wrapped() //
.initialToken(VaultToken.of(vaultProperties.getToken())) //
.build();
return new CubbyholeAuthentication(options, vaultClient);
}
/**
* Creates a {@link ClientFactoryWrapper} containing a
* {@link ClientHttpRequestFactory}. {@link ClientHttpRequestFactory} is not exposed

View File

@@ -193,6 +193,6 @@ public class VaultProperties {
}
public enum AuthenticationMethod {
TOKEN, APPID, AWS_EC2, CERT
TOKEN, APPID, AWS_EC2, CERT, CUBBYHOLE;
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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 static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.vault.client.VaultResponseEntity;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.support.VaultResponse;
/**
* Integration test using config infrastructure with Cubbyhole 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)
@SpringBootTest(classes = VaultConfigCubbyholeAuthenticationTests.TestApplication.class, properties = {
"spring.cloud.vault.authentication=cubbyhole",
"spring.application.name=VaultConfigAppIdTests" })
public class VaultConfigCubbyholeAuthenticationTests {
@BeforeClass
public static void beforeClass() throws Exception {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
VaultOperations vaultOperations = vaultRule.prepare().getVaultOperations();
vaultOperations.write(
"secret/" + VaultConfigCubbyholeAuthenticationTests.class.getSimpleName(),
Collections.singletonMap("vault.value", "foo"));
VaultResponseEntity<VaultResponse> entity = vaultOperations.doWithVault(
new VaultOperations.SessionCallback<VaultResponseEntity<VaultResponse>>() {
@Override
public VaultResponseEntity<VaultResponse> doWithVault(
VaultOperations.VaultSession session) {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Vault-Wrap-TTL", "1h");
return session.exchange("auth/token/create", HttpMethod.POST,
new HttpEntity<Object>(headers), VaultResponse.class,
null);
}
});
String initialToken = entity.getBody().getWrapInfo().get("token");
System.setProperty("spring.cloud.vault.token", initialToken);
}
@AfterClass
public static void afterClass() {
System.clearProperty("spring.cloud.vault.token");
}
@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);
}
}
}