Switch to sys/wrapping endpoints for response unwrapping.

Cubbyhole and wrapped AppRole authentications now use sys/wrapping endpoints to unwrap responses by default. Both authentication mechanisms can be configured with UnwrappingEndpoints to switch back to cubbyhole.

Closes gh-163.
This commit is contained in:
Mark Paluch
2019-09-09 13:16:09 +02:00
parent fac92ded6a
commit 35079a54bb
18 changed files with 386 additions and 81 deletions

View File

@@ -43,6 +43,7 @@ import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.get;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.method;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
/**
@@ -136,9 +137,10 @@ public class AppRoleAuthentication
}
if (roleId instanceof Wrapped) {
return unwrapResponse(((Wrapped) roleId).getInitialToken())
.map(vaultResponse -> (String) vaultResponse.getRequiredData()
.get("role_id"));
return unwrapResponse(options.getUnwrappingEndpoints(),
((Wrapped) roleId).getInitialToken())
.map(vaultResponse -> (String) vaultResponse.getRequiredData()
.get("role_id"));
}
throw new IllegalArgumentException("Unknown RoleId configuration: " + roleId);
@@ -164,25 +166,24 @@ public class AppRoleAuthentication
if (secretId instanceof Wrapped) {
return unwrapResponse(((Wrapped) secretId).getInitialToken())
.map(vaultResponse -> (String) vaultResponse.getRequiredData()
.get("secret_id"));
return unwrapResponse(options.getUnwrappingEndpoints(),
((Wrapped) secretId).getInitialToken())
.map(vaultResponse -> (String) vaultResponse.getRequiredData()
.get("secret_id"));
}
throw new IllegalArgumentException("Unknown SecretId configuration: " + secretId);
}
private static Node<VaultResponse> unwrapResponse(VaultToken token) {
private static Node<VaultResponse> unwrapResponse(
UnwrappingEndpoints unwrappingEndpoints, VaultToken token) {
return AuthenticationSteps.fromHttpRequest(get("cubbyhole/response")
.with(createHttpHeaders(token)).as(VaultResponse.class))
.map(vaultResponse -> {
Map<String, Object> data = vaultResponse.getRequiredData();
return VaultResponses.unwrap((String) data.get("response"),
VaultResponse.class);
});
return AuthenticationSteps
.fromHttpRequest(method(unwrappingEndpoints.getUnwrapRequestMethod(),
unwrappingEndpoints.getPath()).with(createHttpHeaders(token))
.as(VaultResponse.class))
.map(unwrappingEndpoints::unwrap);
}
@Override
@@ -247,14 +248,14 @@ public class AppRoleAuthentication
VaultToken token = ((Wrapped) roleId).getInitialToken();
try {
UnwrappingEndpoints unwrappingEndpoints = options
.getUnwrappingEndpoints();
ResponseEntity<VaultResponse> entity = restOperations.exchange(
"cubbyhole/response", HttpMethod.GET, createHttpEntity(token),
VaultResponse.class);
unwrappingEndpoints.getPath(),
unwrappingEndpoints.getUnwrapRequestMethod(),
createHttpEntity(token), VaultResponse.class);
Map<String, Object> data = entity.getBody().getRequiredData();
VaultResponse response = VaultResponses
.unwrap((String) data.get("response"), VaultResponse.class);
VaultResponse response = unwrappingEndpoints.unwrap(entity.getBody());
return (String) response.getRequiredData().get("role_id");
}
@@ -299,13 +300,14 @@ public class AppRoleAuthentication
try {
UnwrappingEndpoints unwrappingEndpoints = options
.getUnwrappingEndpoints();
ResponseEntity<VaultResponse> entity = restOperations.exchange(
"cubbyhole/response", HttpMethod.GET, createHttpEntity(token),
VaultResponse.class);
unwrappingEndpoints.getPath(),
unwrappingEndpoints.getUnwrapRequestMethod(),
createHttpEntity(token), VaultResponse.class);
Map<String, Object> data = entity.getBody().getRequiredData();
VaultResponse response = VaultResponses
.unwrap((String) data.get("response"), VaultResponse.class);
VaultResponse response = unwrappingEndpoints.unwrap(entity.getBody());
return (String) response.getRequiredData().get("secret_id");
}

View File

@@ -60,6 +60,11 @@ public class AppRoleAuthenticationOptions {
@Nullable
private final String appRole;
/**
* Unwrapping endpoint to cater for functionality across various Vault versions.
*/
private final UnwrappingEndpoints unwrappingEndpoints;
/**
* Token associated for pull mode (retrieval of secretId/roleId).
* @deprecated since 2.0, use {@link RoleId#pull(VaultToken)}/
@@ -70,12 +75,14 @@ public class AppRoleAuthenticationOptions {
private final VaultToken initialToken;
private AppRoleAuthenticationOptions(String path, RoleId roleId, SecretId secretId,
@Nullable String appRole, @Nullable VaultToken initialToken) {
@Nullable String appRole, UnwrappingEndpoints unwrappingEndpoints,
@Nullable VaultToken initialToken) {
this.path = path;
this.roleId = roleId;
this.secretId = secretId;
this.appRole = appRole;
this.unwrappingEndpoints = unwrappingEndpoints;
this.initialToken = initialToken;
}
@@ -116,6 +123,14 @@ public class AppRoleAuthenticationOptions {
return appRole;
}
/**
* @return the endpoint configuration.
* @since 2.2
*/
public UnwrappingEndpoints getUnwrappingEndpoints() {
return unwrappingEndpoints;
}
/**
* @return the initial token for roleId/secretId retrieval in pull mode.
* @since 1.1
@@ -150,6 +165,8 @@ public class AppRoleAuthenticationOptions {
@Nullable
private String appRole;
private UnwrappingEndpoints unwrappingEndpoints = UnwrappingEndpoints.SysWrapping;
@Nullable
@Deprecated
private VaultToken initialToken;
@@ -249,6 +266,22 @@ public class AppRoleAuthenticationOptions {
return this;
}
/**
* Configure the {@link UnwrappingEndpoints} to use.
*
* @param endpoints must not be {@literal null}.
* @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}
* @since 2.2
*/
public AppRoleAuthenticationOptionsBuilder unwrappingEndpoints(
UnwrappingEndpoints endpoints) {
Assert.notNull(endpoints, "UnwrappingEndpoints must not be empty");
this.unwrappingEndpoints = endpoints;
return this;
}
/**
* Configure a {@code initialToken}.
*
@@ -310,7 +343,7 @@ public class AppRoleAuthenticationOptions {
}
return new AppRoleAuthenticationOptions(path, roleId, secretId, appRole,
initialToken);
unwrappingEndpoints, initialToken);
}
}

View File

@@ -346,6 +346,19 @@ public class AuthenticationSteps {
return new HttpRequestBuilder(HttpMethod.POST, uri);
}
/**
* Builder entry point to use {@link HttpMethod} for {@code uriTemplate}.
*
* @param uriTemplate must not be {@literal null} or empty.
* @param uriVariables the variables to expand the template.
* @return a new {@link HttpRequestBuilder}.
* @since 2.2
*/
public static HttpRequestBuilder method(HttpMethod method, String uriTemplate,
String... uriVariables) {
return new HttpRequestBuilder(method, uriTemplate, uriVariables);
}
private HttpRequestBuilder(HttpMethod method, URI uri) {
this.method = method;
this.uri = uri;

View File

@@ -28,14 +28,12 @@ import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.AuthenticationSteps.HttpRequest;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.VaultResponses;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.get;
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.method;
/**
* Cubbyhole {@link ClientAuthentication} implementation.
@@ -176,21 +174,26 @@ public class CubbyholeAuthentication
Assert.notNull(options, "CubbyholeAuthenticationOptions must not be null");
HttpRequest<VaultResponse> initialRequest = get(options.getPath()) //
.with(VaultHttpHeaders.from(options.getInitialToken())) //
String url = getRequestPath(options);
HttpMethod unwrapMethod = getRequestMethod(options);
HttpEntity<Object> requestEntity = getRequestEntity(options);
HttpRequest<VaultResponse> initialRequest = method(unwrapMethod, url) //
.with(requestEntity) //
.as(VaultResponse.class);
return AuthenticationSteps.fromHttpRequest(initialRequest) //
.map(VaultResponseSupport::getData) //
.login(map -> getToken(options, map));
.login(it -> getToken(options, it, url));
}
@Override
public VaultToken login() throws VaultException {
Map<String, Object> data = lookupToken();
String url = getRequestPath(options);
VaultResponse data = lookupToken(url);
VaultToken tokenToUse = getToken(this.options, data);
VaultToken tokenToUse = getToken(this.options, data, url);
if (shouldEnhanceTokenWithSelfLookup(tokenToUse)) {
@@ -209,18 +212,17 @@ public class CubbyholeAuthentication
}
@Nullable
private Map<String, Object> lookupToken() {
private VaultResponse lookupToken(String url) {
try {
ResponseEntity<VaultResponse> entity = restOperations.exchange(
options.getPath(), HttpMethod.GET,
new HttpEntity<>(VaultHttpHeaders.from(options.getInitialToken())),
VaultResponse.class);
HttpMethod unwrapMethod = getRequestMethod(options);
HttpEntity<Object> requestEntity = getRequestEntity(options);
ResponseEntity<VaultResponse> entity = restOperations.exchange(url,
unwrapMethod, requestEntity, VaultResponse.class);
Assert.state(entity.getBody() != null, "Auth response must not be null");
return entity.getBody().getData();
return entity.getBody();
}
catch (RestClientException e) {
throw VaultLoginException.create("Cubbyhole", e);
@@ -245,21 +247,43 @@ public class CubbyholeAuthentication
return true;
}
private static HttpEntity<Object> getRequestEntity(
CubbyholeAuthenticationOptions options) {
return new HttpEntity<>(VaultHttpHeaders.from(options.getInitialToken()));
}
private static HttpMethod getRequestMethod(CubbyholeAuthenticationOptions options) {
if (options.isWrappedToken()) {
return options.getUnwrappingEndpoints().getUnwrapRequestMethod();
}
return HttpMethod.GET;
}
private static String getRequestPath(CubbyholeAuthenticationOptions options) {
if (options.isWrappedToken()) {
return options.getUnwrappingEndpoints().getPath();
}
return options.getPath();
}
private static VaultToken getToken(CubbyholeAuthenticationOptions options,
@Nullable Map<String, Object> data) {
VaultResponse response, String url) {
if (options.isWrappedToken()) {
Assert.state(data != null, "Auth data must not be null");
VaultResponse responseToUse = options.getUnwrappingEndpoints()
.unwrap(response);
VaultResponse response = VaultResponses.unwrap((String) data.get("response"),
VaultResponse.class);
Assert.state(responseToUse.getAuth() != null, "Auth field must not be null");
Assert.state(response.getAuth() != null, "Auth field must not be null");
return LoginTokenUtil.from(response.getAuth());
return LoginTokenUtil.from(responseToUse.getAuth());
}
Map<String, Object> data = response.getData();
if (data == null || data.isEmpty()) {
throw new VaultLoginException(String.format(
"Cannot retrieve Token from Cubbyhole: Response at %s does not contain a token",
@@ -273,6 +297,6 @@ public class CubbyholeAuthentication
throw new VaultLoginException(String.format(
"Cannot retrieve Token from Cubbyhole: Response at %s does not contain an unique token",
options.getPath()));
url));
}
}

View File

@@ -41,6 +41,11 @@ public class CubbyholeAuthenticationOptions {
*/
private final String path;
/**
* Unwrapping endpoint to cater for functionality across various Vault versions.
*/
private final UnwrappingEndpoints unwrappingEndpoints;
/**
* Indicates whether the Cubbyhole contains a wrapped token.
*/
@@ -53,12 +58,14 @@ public class CubbyholeAuthenticationOptions {
private final boolean selfLookup;
private CubbyholeAuthenticationOptions(VaultToken initialToken, String path,
boolean wrappedToken, boolean selfLookup) {
UnwrappingEndpoints unwrappingEndpoints, boolean wrappedToken,
boolean selfLookup) {
this.initialToken = initialToken;
this.path = path;
this.wrappedToken = wrappedToken;
this.selfLookup = selfLookup;
this.unwrappingEndpoints = unwrappingEndpoints;
}
/**
@@ -82,6 +89,14 @@ public class CubbyholeAuthenticationOptions {
return path;
}
/**
* @return the endpoint configuration.
* @since 2.2
*/
public UnwrappingEndpoints getUnwrappingEndpoints() {
return unwrappingEndpoints;
}
/**
* @return {@literal true} indicates that the Cubbyhole response contains a wrapped
* token, otherwise {@literal false} to expect a token in the {@literal data}
@@ -113,6 +128,8 @@ public class CubbyholeAuthenticationOptions {
@Nullable
private String path;
private UnwrappingEndpoints endpoints = UnwrappingEndpoints.SysWrapping;
private boolean wrappedToken;
private boolean selfLookup = true;
@@ -150,6 +167,22 @@ public class CubbyholeAuthenticationOptions {
return this;
}
/**
* Configure the {@link UnwrappingEndpoints} to use.
*
* @param endpoints must not be {@literal null}.
* @return {@code this} {@link CubbyholeAuthenticationOptionsBuilder}
* @since 2.2
*/
public CubbyholeAuthenticationOptionsBuilder unwrappingEndpoints(
UnwrappingEndpoints endpoints) {
Assert.notNull(endpoints, "UnwrappingEndpoints must not be empty");
this.endpoints = endpoints;
return this;
}
/**
* Configure whether to use wrapped token responses.
*
@@ -157,7 +190,7 @@ public class CubbyholeAuthenticationOptions {
*/
public CubbyholeAuthenticationOptionsBuilder wrapped() {
this.path = "cubbyhole/response";
this.path = "";
this.wrappedToken = true;
return this;
}
@@ -188,8 +221,8 @@ public class CubbyholeAuthenticationOptions {
Assert.notNull(initialToken, "Initial Vault Token must not be null");
Assert.notNull(path, "Path must not be null");
return new CubbyholeAuthenticationOptions(initialToken, path, wrappedToken,
selfLookup);
return new CubbyholeAuthenticationOptions(initialToken, path, endpoints,
wrappedToken, selfLookup);
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 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.authentication;
import org.springframework.http.HttpMethod;
import org.springframework.vault.client.VaultResponses;
import org.springframework.vault.support.VaultResponse;
/**
* Version-specific endpoint implementations for response unwrapping. Uses either legacy
* cubbyhole or {@code sys/wrapping} endpoints.
*
* @author Mark Paluch
* @since 2.2
*/
public enum UnwrappingEndpoints {
/**
* Legacy cubbyhole endpoints prior to Vault 0.6.2 ({@literal cubbyhole/response}).
*/
Cubbyhole {
@Override
String getPath() {
return "cubbyhole/response";
}
@Override
VaultResponse unwrap(VaultResponse vaultResponse) {
return VaultResponses.unwrap(
(String) vaultResponse.getRequiredData().get("response"),
VaultResponse.class);
}
@Override
HttpMethod getUnwrapRequestMethod() {
return HttpMethod.GET;
}
},
/**
* Sys/wrapping endpoints for Vault 0.6.2 and higher
* ({@literal /sys/wrapping/unwrap}).
*/
SysWrapping {
@Override
String getPath() {
return "sys/wrapping/unwrap";
}
@Override
VaultResponse unwrap(VaultResponse vaultResponse) {
return vaultResponse;
}
@Override
HttpMethod getUnwrapRequestMethod() {
return HttpMethod.POST;
}
};
/**
* Retrieve the path of the unwrapping endpoint.
*
* @return the unwrapping endpoint path.
*/
abstract String getPath();
/**
* Unwrap the response data from {@link VaultResponses}.
*
* @param response the raw response entity.
* @return unwrapped {@link VaultResponse}.
*/
abstract VaultResponse unwrap(VaultResponse response);
/**
* Unwrapping request {@link HttpMethod method}.
*
* @return the unwrapping request {@link HttpMethod method}.
*/
abstract HttpMethod getUnwrapRequestMethod();
}

View File

@@ -66,7 +66,7 @@ public enum LeaseEndpoints {
},
/**
* Sys/lease endpoints for Vault 0.8 ans higher ({@literal /sys/leases/…}).
* Sys/lease endpoints for Vault 0.8 and higher ({@literal /sys/leases/…}).
*/
SysLeases {

View File

@@ -29,6 +29,7 @@ import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.RequiresVaultVersion;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.Version;
/**
* Integration tests for {@link AppRoleAuthentication}.
@@ -40,6 +41,7 @@ import org.springframework.vault.util.Settings;
class AppRoleAuthenticationIntegrationTestBase extends IntegrationTestSupport {
static final String SUITABLE_FOR_APP_ROLE_TESTS = "0.6.2";
static final Version sysUnwrapSince = Version.parse("0.6.2");
@BeforeEach
public void before() {
@@ -113,6 +115,15 @@ class AppRoleAuthenticationIntegrationTestBase extends IntegrationTestSupport {
});
}
UnwrappingEndpoints getUnwrappingEndpoints() {
return useSysWrapping() ? UnwrappingEndpoints.SysWrapping
: UnwrappingEndpoints.Cubbyhole;
}
private boolean useSysWrapping() {
return prepare().getVersion().isGreaterThanOrEqualTo(sysUnwrapSince);
}
private HttpEntity<String> getWrappingHeaders() {
HttpHeaders headers = new HttpHeaders();

View File

@@ -101,7 +101,8 @@ class AppRoleAuthenticationIntegrationTests
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.secretId(SecretId.wrapped(unwrappingToken))
.roleId(RoleId.provided(roleId)).build();
.roleId(RoleId.provided(roleId))
.unwrappingEndpoints(getUnwrappingEndpoints()).build();
AppRoleAuthentication authentication = new AppRoleAuthentication(options,
prepare().getRestTemplate());
@@ -117,7 +118,8 @@ class AppRoleAuthenticationIntegrationTests
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.secretId(SecretId.wrapped(secretIdToken))
.roleId(RoleId.wrapped(roleIdToken)).build();
.roleId(RoleId.wrapped(roleIdToken))
.unwrappingEndpoints(getUnwrappingEndpoints()).build();
AppRoleAuthentication authentication = new AppRoleAuthentication(options,
prepare().getRestTemplate());
@@ -133,7 +135,8 @@ class AppRoleAuthenticationIntegrationTests
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.secretId(SecretId.wrapped(VaultToken.of(unwrappingToken)))
.roleId(RoleId.provided(roleId)).build();
.roleId(RoleId.provided(roleId))
.unwrappingEndpoints(getUnwrappingEndpoints()).build();
AppRoleAuthentication authentication = new AppRoleAuthentication(options,
prepare().getRestTemplate());

View File

@@ -40,20 +40,21 @@ class AppRoleAuthenticationStepsIntegrationTests
extends AppRoleAuthenticationIntegrationTestBase {
@Test
void authenticationStepsShouldAuthenticateWithWrappedSecretId()
throws InterruptedException {
void authenticationStepsShouldAuthenticateWithWrappedSecretId() {
String roleId = getRoleId("with-secret-id");
VaultToken unwrappingToken = generateWrappedSecretIdResponse();
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.secretId(SecretId.wrapped(unwrappingToken))
.roleId(RoleId.provided(roleId)).build();
.roleId(RoleId.provided(roleId))
.unwrappingEndpoints(getUnwrappingEndpoints()).build();
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
AppRoleAuthentication.createAuthenticationSteps(options),
prepare().getRestTemplate());
assertThat(executor.login()).isNotNull();
}
@Test
@@ -68,7 +69,7 @@ class AppRoleAuthenticationStepsIntegrationTests
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.secretId(SecretId.provided(secretId)).roleId(RoleId.wrapped(roleIdToken))
.build();
.unwrappingEndpoints(getUnwrappingEndpoints()).build();
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
AppRoleAuthentication.createAuthenticationSteps(options),

View File

@@ -183,11 +183,12 @@ class AppRoleAuthenticationUnitTests {
}
@Test
void loginShouldUnwrapSecretIdResponse() throws Exception {
void loginShouldUnwrapCubbyholeSecretIdResponse() throws Exception {
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.roleId(RoleId.provided("my_role_id"))
.secretId(SecretId.wrapped(VaultToken.of("unwrapping_token"))).build();
.secretId(SecretId.wrapped(VaultToken.of("unwrapping_token")))
.unwrappingEndpoints(UnwrappingEndpoints.Cubbyhole).build();
String wrappedResponse = "{"
+ " \"request_id\": \"aad6a19b-a42b-b750-cafb-51087662f53e\","
@@ -226,4 +227,46 @@ class AppRoleAuthenticationUnitTests {
.isEqualTo(Duration.ofSeconds(10));
assertThat(((LoginToken) login).isRenewable()).isTrue();
}
@Test
void loginShouldUnwrapSecretIdResponse() throws Exception {
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.roleId(RoleId.provided("my_role_id"))
.secretId(SecretId.wrapped(VaultToken.of("unwrapping_token"))).build();
String wrappedResponse = "{"
+ " \"request_id\": \"aad6a19b-a42b-b750-cafb-51087662f53e\","
+ " \"lease_id\": \"\"," + " \"renewable\": false,"
+ " \"lease_duration\": 0," + " \"data\": {"
+ " \"secret_id\": \"my_secret_id\","
+ " \"secret_id_accessor\": \"my_secret_id_accessor\"" + " },"
+ " \"wrap_info\": null," + " \"warnings\": null," + " \"auth\": null"
+ "}";
// Expect a first request to unwrap the response
mockRest.expect(requestTo("/sys/wrapping/unwrap"))
.andExpect(header("X-Vault-Token", "unwrapping_token"))
.andExpect(method(HttpMethod.POST)).andRespond(withSuccess()
.contentType(MediaType.APPLICATION_JSON).body(wrappedResponse));
// Also expect a second request to retrieve a token
mockRest.expect(requestTo("/auth/approle/login"))
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath("$.role_id").value("my_role_id"))
.andExpect(jsonPath("$.secret_id").value("my_secret_id"))
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body("{"
+ "\"auth\":{\"client_token\":\"my-token\", \"lease_duration\": 10, \"renewable\": true}"
+ "}"));
AppRoleAuthentication auth = new AppRoleAuthentication(options, restTemplate);
VaultToken login = auth.login();
assertThat(login).isInstanceOf(LoginToken.class);
assertThat(login.getToken()).isEqualTo("my-token");
assertThat(((LoginToken) login).getLeaseDuration())
.isEqualTo(Duration.ofSeconds(10));
assertThat(((LoginToken) login).isRenewable()).isTrue();
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Version;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,6 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
abstract class CubbyholeAuthenticationIntegrationTestBase extends IntegrationTestSupport {
private static final Version sysUnwrapSince = Version.parse("0.6.2");
Map<String, String> prepareWrappedToken() {
ResponseEntity<VaultResponse> response = prepare().getVaultOperations()
@@ -42,7 +45,7 @@ abstract class CubbyholeAuthenticationIntegrationTestBase extends IntegrationTes
headers.add("X-Vault-Wrap-TTL", "10m");
return restOperations.exchange("auth/token/create", HttpMethod.POST,
new HttpEntity<Object>(headers), VaultResponse.class);
new HttpEntity<>(headers), VaultResponse.class);
});
Map<String, String> wrapInfo = response.getBody().getWrapInfo();
@@ -51,4 +54,13 @@ abstract class CubbyholeAuthenticationIntegrationTestBase extends IntegrationTes
assertThat(wrapInfo).isNotNull();
return wrapInfo;
}
UnwrappingEndpoints getUnwrappingEndpoints() {
return useSysWrapping() ? UnwrappingEndpoints.SysWrapping
: UnwrappingEndpoints.Cubbyhole;
}
private boolean useSysWrapping() {
return prepare().getVersion().isGreaterThanOrEqualTo(sysUnwrapSince);
}
}

View File

@@ -44,6 +44,7 @@ class CubbyholeAuthenticationIntegrationTests
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.unwrappingEndpoints(getUnwrappingEndpoints())
.initialToken(VaultToken.of(initialToken)).wrapped().build();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createSslConfiguration());
@@ -58,6 +59,7 @@ class CubbyholeAuthenticationIntegrationTests
void loginShouldFail() {
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.unwrappingEndpoints(getUnwrappingEndpoints())
.initialToken(VaultToken.of("Hello")).wrapped().build();
RestTemplate restTemplate = TestRestTemplateFactory
@@ -70,9 +72,7 @@ class CubbyholeAuthenticationIntegrationTests
fail("Missing VaultException");
}
catch (VaultException e) {
assertThat(e).hasMessageContaining("Cannot login using Cubbyhole")
.hasMessageContaining("permission denied");
assertThat(e).hasMessageContaining("Cannot login using Cubbyhole");
}
}
}

View File

@@ -47,6 +47,7 @@ class CubbyholeAuthenticationOperatorIntegrationTests
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.unwrappingEndpoints(getUnwrappingEndpoints())
.initialToken(VaultToken.of(initialToken)).wrapped().build();
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(

View File

@@ -43,6 +43,7 @@ class CubbyholeAuthenticationStepsIntegrationTests
String initialToken = wrapInfo.get("token");
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.unwrappingEndpoints(getUnwrappingEndpoints())
.initialToken(VaultToken.of(initialToken)).wrapped().build();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createSslConfiguration());

View File

@@ -62,7 +62,7 @@ class CubbyholeAuthenticationUnitTests {
}
@Test
void shouldLoginUsingWrappedLogin() throws Exception {
void shouldLoginUsingCubbyholeLogin() throws Exception {
String wrappedResponse = "{\"request_id\":\"058222ef-9ab9-ff39-f087-9d5bee64e46d\","
+ "\"auth\":{\"client_token\":\"5e6332cf-f003-6369-8cba-5bce2330f6cc\","
@@ -78,7 +78,8 @@ class CubbyholeAuthenticationUnitTests {
+ "} }"));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("hello")).wrapped().build();
.initialToken(VaultToken.of("hello"))
.unwrappingEndpoints(UnwrappingEndpoints.Cubbyhole).wrapped().build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
restTemplate);
@@ -94,20 +95,49 @@ class CubbyholeAuthenticationUnitTests {
}
@Test
void shouldLoginUsingWrappedLoginWithSelfLookup() throws Exception {
void shouldLoginUsingWrappedLogin() {
String wrappedResponse = "{\"request_id\":\"058222ef-9ab9-ff39-f087-9d5bee64e46d\","
+ "\"auth\":{\"client_token\":\"5e6332cf-f003-6369-8cba-5bce2330f6cc\","
+ "\"lease_duration\":0,"
+ "\"accessor\":\"46b6aebb-187f-932a-26d7-4f3d86a68319\"} }";
mockRest.expect(requestTo("/sys/wrapping/unwrap"))
.andExpect(method(HttpMethod.POST))
.andExpect(header(VaultHttpHeaders.VAULT_TOKEN, "hello"))
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
.body(wrappedResponse));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder()
.initialToken(VaultToken.of("hello"))
.unwrappingEndpoints(UnwrappingEndpoints.SysWrapping).wrapped().build();
CubbyholeAuthentication authentication = new CubbyholeAuthentication(options,
restTemplate);
VaultToken login = authentication.login();
assertThat(login).isInstanceOf(LoginToken.class);
assertThat(login.getToken()).isEqualTo("5e6332cf-f003-6369-8cba-5bce2330f6cc");
LoginToken loginToken = (LoginToken) login;
assertThat(loginToken.isRenewable()).isFalse();
assertThat(loginToken.getLeaseDuration()).isEqualTo(Duration.ZERO);
}
@Test
void shouldLoginUsingWrappedLoginWithSelfLookup() {
String wrappedResponse = "{\"request_id\":\"058222ef-9ab9-ff39-f087-9d5bee64e46d\","
+ "\"auth\":{\"client_token\":\"5e6332cf-f003-6369-8cba-5bce2330f6cc\","
+ "\"lease_duration\":10,"
+ "\"accessor\":\"46b6aebb-187f-932a-26d7-4f3d86a68319\"} }";
mockRest.expect(requestTo("/cubbyhole/response"))
.andExpect(method(HttpMethod.GET))
mockRest.expect(requestTo("/sys/wrapping/unwrap"))
.andExpect(method(HttpMethod.POST))
.andExpect(header(VaultHttpHeaders.VAULT_TOKEN, "hello"))
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
.body("{\"data\":{\"response\":"
+ OBJECT_MAPPER.writeValueAsString(wrappedResponse)
+ "} }"));
.body(wrappedResponse));
mockRest.expect(requestTo("/auth/token/lookup-self"))
.andExpect(method(HttpMethod.GET))

View File

@@ -10,6 +10,7 @@
* `LifecycleAwareSessionManager` and `ReactiveLifecycleAwareSessionManager` emit now ``AuthenticationEvent``s.
* <<vault.authentication.pcf>>.
* Deprecation of `AppIdAuthentication`. Use `AppRoleAuthentication` instead as recommended by HashiCorp Vault.
* `CubbyholeAuthentication` and wrapped `AppRoleAuthentication` now use `sys/wrapping/unwrap` endpoints by default.
[[new-features.2-1-0]]
=== What's new in Spring Vault 2.1

View File

@@ -25,7 +25,7 @@ OS access levels.
====
[source, java]
----
@PropertySource("configuration.properties"),
@PropertySource("configuration.properties")
@Configuration
public class Config extends AbstractVaultConfiguration {
@@ -669,7 +669,7 @@ class AppConfig extends AbstractVaultConfiguration {
====
[source,shell]
----
$ vault token-create
$ vault token create
Key Value
--- -----
token f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
@@ -678,7 +678,7 @@ token_duration 0s
token_renewable false
token_policies [root]
$ token-create -use-limit=2 -orphan -no-default-policy -policy=none
$ vault token create -use-limit=2 -orphan -no-default-policy -policy=none
Key Value
--- -----
token 895cb88b-aef4-0e33-ba65-d50007290780