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:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user