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:
@@ -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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -193,6 +193,6 @@ public class VaultProperties {
|
||||
}
|
||||
|
||||
public enum AuthenticationMethod {
|
||||
TOKEN, APPID, AWS_EC2, CERT
|
||||
TOKEN, APPID, AWS_EC2, CERT, CUBBYHOLE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user