Support 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 can be retrieved either from a wrapped response or from the data section.

Fixes gh-3
This commit is contained in:
Mark Paluch
2016-09-18 22:37:15 +02:00
parent 5637f440f3
commit bca07a916c
7 changed files with 719 additions and 12 deletions

View File

@@ -0,0 +1,178 @@
/*
* 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.vault.authentication;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.vault.client.VaultClient;
import org.springframework.vault.client.VaultException;
import org.springframework.vault.client.VaultResponseEntity;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
/**
* Cubbyhole {@link ClientAuthentication} implementation.
* <p>
* Cubbyhole authentication uses Vault primitives to provide a secured authentication workflow. Cubbyhole authentication
* uses {@link VaultToken tokens} as primary login method. An ephemeral token is used to obtain a second, login
* {@link VaultToken} from Vault's Cubbyhole secret backend. The login token is usually longer-lived and used to
* interact with Vault. The login token can be retrieved either from a wrapped response or from the {@code data}
* section.
* <p>
* <h2>Wrapped token response usage</h2> <strong>Create a Token</strong>
*
* <pre>
* <code>
$ 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
* </code>
* </pre>
*
* <strong>Setup {@link CubbyholeAuthentication}</strong>
*
* <pre>
* <code>
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
.builder()
.initialToken(VaultToken.of("397ccb93-ff6c-b17b-9389-380b01ca2645"))
.wrapped()
.build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
* </code>
* </pre>
*
* <h2>Stored token response usage</h2> <strong>Create a Token</strong>
*
* <pre>
* <code>
$ vault token-create
Key Value
--- -----
token f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
token_accessor 4eee9bd9-81bb-06d6-af01-723c54a72148
token_duration 0s
token_renewable false
token_policies [root]
$ token-create -use-limit=2 -orphan -no-default-policy -policy=none
Key Value
--- -----
token 895cb88b-aef4-0e33-ba65-d50007290780
token_accessor e84b661c-8aa8-2286-b788-f258f30c8325
token_duration 0s
token_renewable false
token_policies [none]
$ export VAULT_TOKEN=895cb88b-aef4-0e33-ba65-d50007290780
$ vault write cubbyhole/token token=f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
* </code>
* </pre>
*
* <strong>Setup {@link CubbyholeAuthentication}</strong>
*
* <pre>
* <code>
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
.builder()
.initialToken(VaultToken.of("895cb88b-aef4-0e33-ba65-d50007290780"))
.path("cubbyhole/token")
.build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
* </code>
* </pre>
*
* @author Mark Paluch
* @see CubbyholeAuthenticationOptions
* @see <a href="https://www.vaultproject.io/docs/auth/token.html">Auth Backend: Token</a>
* @see <a href="https://www.vaultproject.io/docs/secrets/cubbyhole/index.html">Cubbyhole Secret Backend</a>
* @see <a href="https://www.vaultproject.io/docs/concepts/response-wrapping.html">Response Wrapping</a>
*/
public class CubbyholeAuthentication implements ClientAuthentication {
private final static Logger logger = LoggerFactory.getLogger(CubbyholeAuthentication.class);
private final CubbyholeAuthenticationOptions options;
private final VaultClient vaultClient;
/**
* Create a new {@link CubbyholeAuthentication} given {@link CubbyholeAuthenticationOptions} and {@link VaultClient}.
*
* @param options must not be {@literal null}.
* @param vaultClient must not be {@literal null}.
*/
public CubbyholeAuthentication(CubbyholeAuthenticationOptions options, VaultClient vaultClient) {
Assert.notNull(options, "CubbyholeAuthenticationOptions must not be null");
Assert.notNull(vaultClient, "VaultClient must not be null");
this.options = options;
this.vaultClient = vaultClient;
}
@Override
public VaultToken login() throws VaultException {
VaultResponseEntity<VaultResponse> entity = vaultClient.getForEntity(options.getPath(), options.getInitialToken(),
VaultResponse.class);
if (entity.isSuccessful() && entity.hasBody()) {
VaultResponse body = entity.getBody();
Map<String, Object> data = body.getData();
VaultToken token = getToken(entity, data);
if (token != null) {
logger.debug("Login successful using Cubbyhole authentication");
return token;
}
}
throw new VaultException(
String.format("Cannot retrieve Token from cubbyhole: %s %s", entity.getStatusCode(), entity.getMessage()));
}
private VaultToken getToken(VaultResponseEntity<VaultResponse> entity, Map<String, Object> data) {
if (options.isWrappedToken()) {
VaultResponse response = vaultClient.unwrap((String) data.get("response"), VaultResponse.class);
return VaultToken.of((String) response.getAuth().get("client_token"));
}
if (data == null || data.isEmpty()) {
throw new VaultException(String
.format("Cannot retrieve Token from cubbyhole: Response at %s does not contain a token", entity.getUri()));
}
if (data.size() == 1) {
String token = (String) data.get(data.keySet().iterator().next());
return VaultToken.of(token);
}
throw new VaultException(String.format(
"Cannot retrieve Token from cubbyhole: Response at %s does not contain an unique token", entity.getUri()));
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.vault.authentication;
import org.springframework.util.Assert;
import org.springframework.vault.support.VaultToken;
/**
* Authentication options for {@link CubbyholeAuthentication}.
* <p>
* Authentication options provide the path below cubbyhole and the cubbyhole mode. Instances of this class are immutable
* once constructed.
*
* @author Mark Paluch
* @see CubbyholeAuthentication
* @see #builder()
*/
public class CubbyholeAuthenticationOptions {
/**
* Initial {@link VaultToken} to access Cubbyhole.
*/
private final VaultToken initialToken;
/**
* Path of the Cubbyhole response path.
*/
private final String path;
/**
* Indicates whether the Cubbyhole contains a wrapped token.
*/
private final boolean wrappedToken;
private CubbyholeAuthenticationOptions(VaultToken initialToken, String path, boolean wrappedToken) {
this.initialToken = initialToken;
this.path = path;
this.wrappedToken = wrappedToken;
}
/**
* @return a new {@link CubbyholeAuthenticationOptionsBuilder}.
*/
public static CubbyholeAuthenticationOptionsBuilder builder() {
return new CubbyholeAuthenticationOptionsBuilder();
}
/**
* @return the initial {@link VaultToken} to access Cubbyhole.
*/
public VaultToken getInitialToken() {
return initialToken;
}
/**
* @return the path of the Cubbyhole response path.
*/
public String getPath() {
return path;
}
/**
* @return {@literal true} indicates that the Cubbyhole response contains a wrapped token, otherwise {@literal false}
* to expect a token in the {@literal data} response.
*/
public boolean isWrappedToken() {
return wrappedToken;
}
/**
* Builder for {@link CubbyholeAuthenticationOptions}.
*/
public static class CubbyholeAuthenticationOptionsBuilder {
private VaultToken initialToken;
private String path;
private boolean wrappedToken;
CubbyholeAuthenticationOptionsBuilder() {}
/**
* Configures the initial {@link VaultToken} to access Cubbyhole.
*
* @param initialToken must not be {@literal null}.
* @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}.
*/
public CubbyholeAuthenticationOptionsBuilder initialToken(VaultToken initialToken) {
Assert.notNull(initialToken, "Initial Vault Token must not be null");
this.initialToken = initialToken;
return this;
}
/**
* Configures the cubbyhole path, such as {@code cubbyhole/token}. Expects a token in the {@code data} response.
*
* @param path must not be empty or {@literal null}.
* @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}.
*/
public CubbyholeAuthenticationOptionsBuilder path(String path) {
Assert.hasText(path, "Path must not be empty");
this.path = path;
return this;
}
/**
* Configures whether to use wrapped token responses.
*
* @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}.
*/
public CubbyholeAuthenticationOptionsBuilder wrapped() {
this.path = "cubbyhole/response";
this.wrappedToken = true;
return this;
}
/**
* Builds a new {@link CubbyholeAuthenticationOptions} instance. Requires {@link #path(String)} or
* {@link #wrapped()} to be configured.
*
* @return a new {@link CubbyholeAuthenticationOptions}.
*/
public CubbyholeAuthenticationOptions build() {
Assert.notNull(initialToken, "Initial Vault Token must not be null");
Assert.hasText(path, "Path must not be empty");
return new CubbyholeAuthenticationOptions(initialToken, path, wrappedToken);
}
}
}

View File

@@ -15,13 +15,18 @@
*/
package org.springframework.vault.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultToken;
@@ -33,10 +38,9 @@ import org.springframework.web.client.RestTemplate;
* {@link HttpMethod HTTP methods}. {@link VaultClient} is configured with an {@link VaultEndpoint} and
* {@link RestTemplate}. It does not maintain any session or token state. See {@link VaultTemplate} and
* {@link org.springframework.vault.authentication.SessionManager} for authenticated and stateful Vault access.
* <p>
* {@link VaultClient} encapsulates base URI and path construction and uses {@link VaultAccessor} for request and error
* handling by returning {@link VaultResponseEntity} for requests.
*
*
* @author Mark Paluch
* @see VaultResponseEntity
* @see VaultTemplate
@@ -45,11 +49,13 @@ public class VaultClient extends VaultAccessor {
public static final String VAULT_TOKEN = "X-Vault-Token";
private static final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
private final VaultEndpoint endpoint;
/**
* Creates a new {@link VaultClient} with a default a {@link RestTemplate} and {@link VaultEndpoint}.
*
*
* @see VaultEndpoint
*/
public VaultClient() {
@@ -99,7 +105,7 @@ public class VaultClient extends VaultAccessor {
/**
* Issue a POST request using the given object to the path, and returns the response as {@link VaultResponseEntity}.
*
*
* @param path the path.
* @param request the Object to be POSTed, may be {@code null}.
* @param responseType the type of the return value
@@ -176,9 +182,8 @@ public class VaultClient extends VaultAccessor {
/**
* Execute the HTTP method to the given URI template, writing the given request entity to the request, and returns the
* response as {@link VaultResponseEntity}.
* <p>
* URI Template variables are using the given URI variables, if any.
*
*
* @param pathTemplate the path template.
* @param method the HTTP method (GET, POST, etc).
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}.
@@ -198,12 +203,12 @@ public class VaultClient extends VaultAccessor {
* Execute the HTTP method to the given path template, writing the given request entity to the request, and returns
* the response as {@link VaultResponseEntity}. The given {@link ParameterizedTypeReference} is used to pass generic
* type information:
*
*
* <pre class="code">
* ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt; myBean = new ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt;() {};
* ResponseEntity&lt;List&lt;MyBean&gt;&gt; response = client.exchange(&quot;http://example.com&quot;, HttpMethod.GET, null, myBean, null);
* </pre>
*
*
* @param pathTemplate the path template.
* @param method the HTTP method (GET, POST, etc).
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}.
@@ -249,8 +254,8 @@ public class VaultClient extends VaultAccessor {
*
* @param pathTemplate must not be empty or {@literal null}.
* @param uriVariables must not be {@literal null}.
* @see org.springframework.web.util.UriComponentsBuilder
* @return
* @see org.springframework.web.util.UriComponentsBuilder
*/
protected URI buildUri(String pathTemplate, Map<String, ?> uriVariables) {
@@ -261,7 +266,7 @@ public class VaultClient extends VaultAccessor {
/**
* Create {@link HttpHeaders} for a {@link VaultToken}.
*
*
* @param vaultToken must not be {@literal null}.
* @return {@link HttpHeaders} for a {@link VaultToken}.
*/
@@ -273,4 +278,32 @@ public class VaultClient extends VaultAccessor {
headers.add(VAULT_TOKEN, vaultToken.getToken());
return headers;
}
/**
* Unwrap a wrapped response created by Vault Response Wrapping
* @param wrappedResponse the wrapped response , must not be empty or {@literal null}.
* @param responseType the type of the return value.
* @return the unwrapped response.
*/
@SuppressWarnings("unchecked")
public <T> T unwrap(final String wrappedResponse, Class<T> responseType) {
Assert.hasText(wrappedResponse, "Wrapped response must not be empty");
try {
return (T) converter.read(responseType, new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(wrappedResponse.getBytes());
}
@Override
public HttpHeaders getHeaders() {
return new HttpHeaders();
}
});
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}

View File

@@ -367,10 +367,13 @@ public class VaultTemplate implements InitializingBean, VaultOperations {
}
private HttpEntity<?> getHttpEntity(HttpEntity<?> requestEntity) {
HttpHeaders httpHeaders = VaultClient.createHeaders(sessionManager.getSessionToken());
HttpHeaders httpHeaders = VaultClient.createHeaders(sessionManager.getSessionToken());
HttpEntity<?> requestEntityToUse = requestEntity;
if (requestEntityToUse != null) {
httpHeaders.putAll(requestEntity.getHeaders());
requestEntityToUse = new HttpEntity<Object>(requestEntityToUse.getBody(), httpHeaders);
} else {
requestEntityToUse = new HttpEntity<Object>(httpHeaders);

View File

@@ -0,0 +1,97 @@
/*
* 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.vault.authentication;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.assumeNotNull;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.vault.client.VaultClient;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultException;
import org.springframework.vault.client.VaultResponseEntity;
import org.springframework.vault.core.VaultOperations.SessionCallback;
import org.springframework.vault.core.VaultOperations.VaultSession;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
/**
* Integration tests for {@link CubbyholeAuthentication}.
*
* @author Mark Paluch
*/
public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupport {
@Test
public void shouldCreateWrappedToken() throws Exception {
VaultResponseEntity<VaultResponse> response = prepare().getVaultOperations()
.doWithVault(new SessionCallback<VaultResponseEntity<VaultResponse>>() {
@Override
public VaultResponseEntity<VaultResponse> doWithVault(VaultSession session) {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Vault-Wrap-TTL", "10m");
return session.exchange("auth/token/create", HttpMethod.POST, new HttpEntity<Object>(headers),
VaultResponse.class, null);
}
});
Map<String, String> wrapInfo = response.getBody().getWrapInfo();
// Response Wrapping requires Vault 0.6.0+
assumeNotNull(wrapInfo);
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of(initialToken)).wrapped().build();
VaultClient vaultClient = new VaultClient(TestRestTemplateFactory.create(Settings.createSslConfiguration()),
new VaultEndpoint());
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
VaultToken login = authentication.login();
assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
}
@Test
public void loginShouldFail() throws Exception {
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("Hello")).wrapped().build();
VaultClient vaultClient = new VaultClient(TestRestTemplateFactory.create(Settings.createSslConfiguration()),
new VaultEndpoint());
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
try {
authentication.login();
fail("Missing VaultException");
} catch (VaultException e) {
assertThat(e).hasMessageContaining("Cannot retrieve Token from cubbyhole").hasMessageContaining("permission denied");
}
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.vault.authentication;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.vault.client.VaultClient;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultException;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestTemplate;
/**
* Unit tests for {@link CubbyholeAuthentication}.
*
* @author Mark Paluch
*/
public class CubbyholeAuthenticationUnitTests {
private VaultClient vaultClient;
private MockRestServiceServer mockRest;
@Before
public void before() throws Exception {
RestTemplate restTemplate = new RestTemplate();
mockRest = MockRestServiceServer.createServer(restTemplate);
vaultClient = new VaultClient(restTemplate, new VaultEndpoint());
}
@Test
public void shouldLoginUsingWrappedLogin() throws Exception {
mockRest.expect(requestTo("https://localhost:8200/v1/cubbyhole/response")) //
.andExpect(method(HttpMethod.GET)) //
.andExpect(header(VaultClient.VAULT_TOKEN, "hello")) //
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
.body("{\"data\":{\"response\":\"{\\\"request_id\\\":\\\"058222ef-9ab9-ff39-f087-9d5bee64e46d\\\","
+ "\\\"auth\\\":{\\\"client_token\\\":\\\"5e6332cf-f003-6369-8cba-5bce2330f6cc\\\","
+ "\\\"accessor\\\":\\\"46b6aebb-187f-932a-26d7-4f3d86a68319\\\"}}\" } }"));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("hello")).wrapped().build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
VaultToken vaultToken = authentication.login();
assertThat(vaultToken.getToken()).isEqualTo("5e6332cf-f003-6369-8cba-5bce2330f6cc");
}
@Test
public void shouldLoginUsingStoredLogin() throws Exception {
mockRest.expect(requestTo("https://localhost:8200/v1/cubbyhole/token")) //
.andExpect(method(HttpMethod.GET)) //
.andExpect(header(VaultClient.VAULT_TOKEN, "hello")) //
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
.body("{\"data\":{\"mytoken\":\"058222ef-9ab9-ff39-f087-9d5bee64e46d\"} }"));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("hello")).path("cubbyhole/token").build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
VaultToken vaultToken = authentication.login();
assertThat(vaultToken.getToken()).isEqualTo("058222ef-9ab9-ff39-f087-9d5bee64e46d");
}
@Test
public void shouldFailUsingStoredLoginNoData() throws Exception {
mockRest.expect(requestTo("https://localhost:8200/v1/cubbyhole/token")) //
.andExpect(method(HttpMethod.GET)) //
.andExpect(header(VaultClient.VAULT_TOKEN, "hello")) //
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body("{\"data\":{} }"));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("hello")).path("cubbyhole/token").build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
try {
authentication.login();
fail("Missing VaultException");
} catch (VaultException e) {
assertThat(e).hasMessageContaining("does not contain a token");
}
}
@Test
public void shouldFailUsingStoredMultipleEntries() throws Exception {
mockRest.expect(requestTo("https://localhost:8200/v1/cubbyhole/token")) //
.andExpect(method(HttpMethod.GET)) //
.andExpect(header(VaultClient.VAULT_TOKEN, "hello")) //
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body("{\"data\":{\"key1\":1, \"key2\":2} }"));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("hello")).path("cubbyhole/token").build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options, vaultClient);
try {
authentication.login();
fail("Missing VaultException");
} catch (VaultException e) {
assertThat(e).hasMessageContaining("does not contain an unique token");
}
}
}

View File

@@ -381,7 +381,7 @@ and the `createUserId` method. Spring Vault will obtain the UserId
by calling `createUserId` each time it authenticates using AppId to
obtain a token.
====
[source,java]
.MyUserIdMechanism.java
----
@@ -394,6 +394,7 @@ public class MyUserIdMechanism implements AppIdUserIdMechanism {
}
}
----
====
See also: https://www.vaultproject.io/docs/auth/app-id.html[Vault Documentation: Using the App ID auth backend]
@@ -477,6 +478,120 @@ class AppConfig extends AbstractVaultConfiguration {
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 can be retrieved either from a wrapped
response or from the `data` section.
*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
----
====
.Wrapped token response usage
====
[source,java]
----
@Configuration
class AppConfig extends AbstractVaultConfiguration {
// …
@Override
public ClientAuthentication clientAuthentication() {
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
.builder()
.initialToken(VaultToken.of("…"))
.wrapped()
.build();
return new CubbyholeAuthentication(options, vaultClient());
}
// …
}
----
====
*Using stored tokens*
.Crating and storing tokens
====
[source,shell]
----
$ vault token-create
Key Value
--- -----
token f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
token_accessor 4eee9bd9-81bb-06d6-af01-723c54a72148
token_duration 0s
token_renewable false
token_policies [root]
$ token-create -use-limit=2 -orphan -no-default-policy -policy=none
Key Value
--- -----
token 895cb88b-aef4-0e33-ba65-d50007290780
token_accessor e84b661c-8aa8-2286-b788-f258f30c8325
token_duration 0s
token_renewable false
token_policies [none]
$ export VAULT_TOKEN=895cb88b-aef4-0e33-ba65-d50007290780
$ vault write cubbyhole/token token=f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
----
====
.Stored token response usage
====
[source,java]
----
@Configuration
class AppConfig extends AbstractVaultConfiguration {
// …
@Override
public ClientAuthentication clientAuthentication() {
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
.builder()
.initialToken(VaultToken.of("…"))
.path("cubbyhole/token")
.build();
return new CubbyholeAuthentication(options, vaultClient());
}
// …
}
----
====
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]
[[vault.client-ssl]]
== Vault Client SSL configuration