Maintain login token lifecycle.
We now maintain the login token lifecycle throughout the whole application lifetime. A token is obtain from a login method on its first VaultTemplate access. Renewable tokens are refreshed if using LifecycleAwareSessionManager (configured by default) until max-ttl is reached and the token is disposed. Fixes gh-13.
This commit is contained in:
@@ -76,12 +76,9 @@ public class AppIdAuthentication implements ClientAuthentication {
|
||||
throw new VaultException(String.format("Cannot login using app-id: %s", entity.getMessage()));
|
||||
}
|
||||
|
||||
VaultResponse body = entity.getBody();
|
||||
String token = (String) body.getAuth().get("client_token");
|
||||
|
||||
logger.debug("Login successful using AppId authentication");
|
||||
|
||||
return VaultToken.of(token, body.getLeaseDuration());
|
||||
return LoginTokenUtil.from(entity.getBody().getAuth());
|
||||
}
|
||||
|
||||
private Map<String, String> getAppIdLogin(String appId, String userId) {
|
||||
|
||||
@@ -102,7 +102,6 @@ public class AwsEc2Authentication implements ClientAuthentication {
|
||||
}
|
||||
|
||||
VaultResponse body = entity.getBody();
|
||||
String token = (String) body.getAuth().get("client_token");
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -115,7 +114,7 @@ public class AwsEc2Authentication implements ClientAuthentication {
|
||||
}
|
||||
}
|
||||
|
||||
return VaultToken.of(token, body.getLeaseDuration());
|
||||
return LoginTokenUtil.from(entity.getBody().getAuth());
|
||||
}
|
||||
|
||||
protected Map<String, String> getEc2Login() {
|
||||
|
||||
@@ -56,18 +56,15 @@ public class ClientCertificateAuthentication implements ClientAuthentication {
|
||||
|
||||
private VaultToken createTokenUsingTlsCertAuthentication(String path) {
|
||||
|
||||
VaultResponseEntity<VaultResponse> response = vaultClient.postForEntity(String.format("auth/%s/login", path),
|
||||
VaultResponseEntity<VaultResponse> entity = vaultClient.postForEntity(String.format("auth/%s/login", path),
|
||||
Collections.emptyMap(), VaultResponse.class);
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new VaultException(String.format("Cannot login using TLS certificates: %s", response.getMessage()));
|
||||
if (!entity.isSuccessful()) {
|
||||
throw new VaultException(String.format("Cannot login using TLS certificates: %s", entity.getMessage()));
|
||||
}
|
||||
|
||||
VaultResponse body = response.getBody();
|
||||
String token = (String) body.getAuth().get("client_token");
|
||||
|
||||
logger.debug("Login successful using TLS certificates");
|
||||
|
||||
return VaultToken.of(token, body.getLeaseDuration());
|
||||
return LoginTokenUtil.from(entity.getBody().getAuth());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public class CubbyholeAuthentication implements ClientAuthentication {
|
||||
if (options.isWrappedToken()) {
|
||||
|
||||
VaultResponse response = vaultClient.unwrap((String) data.get("response"), VaultResponse.class);
|
||||
return VaultToken.of((String) response.getAuth().get("client_token"));
|
||||
return LoginTokenUtil.from(response.getAuth());
|
||||
}
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.client.VaultClient;
|
||||
import org.springframework.vault.client.VaultException;
|
||||
import org.springframework.vault.client.VaultResponseEntity;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
|
||||
/**
|
||||
* Lifecycle-aware Session Manager. This {@link SessionManager} obtains tokens from a {@link ClientAuthentication} upon
|
||||
* {@link #getSessionToken() request}. Tokens are renewed asynchronously if a token has a lease duration. This happens 5
|
||||
* seconds before the token expires, see {@link #REFRESH_PERIOD_BEFORE_EXPIRY}.
|
||||
* <p>
|
||||
* This {@link SessionManager} also implements {@link DisposableBean} to revoke the {@link LoginToken} once it's not
|
||||
* required anymore. Token revocation will stop regular token refresh.
|
||||
* <p>
|
||||
* If Token renewal runs into a client-side error, it assumes the token was revoked/expired and discards the token state
|
||||
* so the next attempt will lead to another login attempt.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see LoginToken
|
||||
* @see SessionManager
|
||||
* @see AsyncTaskExecutor
|
||||
*/
|
||||
public class LifecycleAwareSessionManager implements SessionManager, DisposableBean {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(LifecycleAwareSessionManager.class);
|
||||
public static final int REFRESH_PERIOD_BEFORE_EXPIRY = 5;
|
||||
|
||||
private final ClientAuthentication clientAuthentication;
|
||||
private final VaultClient vaultClient;
|
||||
private final AsyncTaskExecutor taskExecutor;
|
||||
private final Object lock = new Object();
|
||||
|
||||
private volatile VaultToken token;
|
||||
|
||||
/**
|
||||
* Create a {@link LifecycleAwareSessionManager} given {@link ClientAuthentication}, {@link AsyncTaskExecutor} and
|
||||
* {@link VaultClient}.
|
||||
*
|
||||
* @param clientAuthentication must not be {@literal null}.
|
||||
* @param taskExecutor must not be {@literal null}.
|
||||
* @param vaultClient must not be {@literal null}.
|
||||
*/
|
||||
public LifecycleAwareSessionManager(ClientAuthentication clientAuthentication, AsyncTaskExecutor taskExecutor,
|
||||
VaultClient vaultClient) {
|
||||
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
Assert.notNull(taskExecutor, "AsyncTaskExecutor must not be null");
|
||||
Assert.notNull(vaultClient, "VaultClient must not be null");
|
||||
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.vaultClient = vaultClient;
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
VaultToken token = this.token;
|
||||
this.token = null;
|
||||
|
||||
if (token instanceof LoginToken) {
|
||||
VaultResponseEntity<Map> response = vaultClient.postForEntity("auth/token/revoke-self", token, null, Map.class);
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
logger.warn("Cannot revoke VaultToken: {}", buildExceptionMessage(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a token refresh. Creates a new token if no token was obtained before. If a token was obtained before, it
|
||||
* uses self-renewal to renew the current token. Client-side errors (like permission denied) indicate the token cannot
|
||||
* be renewed because it's expired or simply not found.
|
||||
*
|
||||
* @return {@literal true} if the refresh was successful. {@literal false} if a new token was obtained or refresh
|
||||
* failed.
|
||||
*/
|
||||
protected boolean renewToken() {
|
||||
|
||||
if (token == null) {
|
||||
getSessionToken();
|
||||
return false;
|
||||
}
|
||||
|
||||
VaultResponseEntity<Map> response = vaultClient.postForEntity("auth/token/renew-self", token, null, Map.class);
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
|
||||
if (response.getStatusCode().is4xxClientError()) {
|
||||
logger.debug("Cannot refresh token, resetting token and performing re-login: {}",
|
||||
buildExceptionMessage(response));
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new VaultException(buildExceptionMessage(response));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultToken getSessionToken() {
|
||||
|
||||
if (token == null) {
|
||||
|
||||
synchronized (lock) {
|
||||
|
||||
if (token == null) {
|
||||
token = clientAuthentication.login();
|
||||
|
||||
if (isTokenRenewable()) {
|
||||
scheduleRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private boolean isTokenRenewable() {
|
||||
|
||||
if (token instanceof LoginToken) {
|
||||
|
||||
LoginToken loginToken = (LoginToken) token;
|
||||
return loginToken.getLeaseDuration() > 0 && loginToken.isRenewable();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void scheduleRefresh() {
|
||||
|
||||
LoginToken loginToken = (LoginToken) token;
|
||||
int seconds = NumberUtils.convertNumberToTargetClass(
|
||||
Math.max(1, loginToken.getLeaseDuration() - REFRESH_PERIOD_BEFORE_EXPIRY), Integer.class);
|
||||
|
||||
taskExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (LifecycleAwareSessionManager.this.token != null && isTokenRenewable()) {
|
||||
if (renewToken()) {
|
||||
scheduleRefresh();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Cannot refresh VaultToken", e);
|
||||
}
|
||||
}
|
||||
}, TimeUnit.SECONDS.toMillis(seconds));
|
||||
}
|
||||
|
||||
private static String buildExceptionMessage(VaultResponseEntity<?> response) {
|
||||
|
||||
if (StringUtils.hasText(response.getMessage())) {
|
||||
return String.format("Status %s URI %s: %s", response.getStatusCode(), response.getUri(), response.getMessage());
|
||||
}
|
||||
|
||||
return String.format("Status %s URI %s", response.getStatusCode(), response.getUri());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Value object for a Vault token obtained by a login method.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ToString(exclude = "token")
|
||||
class LoginToken extends VaultToken {
|
||||
|
||||
private final boolean renewable;
|
||||
|
||||
private final long leaseDuration;
|
||||
|
||||
private LoginToken(String token, long leaseDuration, boolean renewable) {
|
||||
|
||||
super(token);
|
||||
|
||||
this.leaseDuration = leaseDuration;
|
||||
this.renewable = renewable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LoginToken}.
|
||||
*
|
||||
* @param token must not be {@literal null}.
|
||||
* @return the created {@link VaultToken}
|
||||
*/
|
||||
public static LoginToken of(String token) {
|
||||
return of(token, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LoginToken} with a {@code leaseDuration}.
|
||||
*
|
||||
* @param token must not be {@literal null}.
|
||||
* @param leaseDuration the lease duration.
|
||||
* @return the created {@link VaultToken}
|
||||
*/
|
||||
public static LoginToken of(String token, long leaseDuration) {
|
||||
|
||||
Assert.hasText(token, "Token must not be empty");
|
||||
|
||||
return new LoginToken(token, leaseDuration, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new renewable {@link LoginToken} with a {@code leaseDuration}.
|
||||
*
|
||||
* @param token must not be {@literal null}.
|
||||
* @param leaseDuration the lease duration.
|
||||
* @return the created {@link VaultToken}
|
||||
*/
|
||||
public static LoginToken renewable(String token, long leaseDuration) {
|
||||
|
||||
Assert.hasText(token, "Token must not be empty");
|
||||
|
||||
return new LoginToken(token, leaseDuration, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lease duration. May be {@literal 0} if none.
|
||||
*/
|
||||
public long getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if this token is renewable; {@literal false} otherwise.
|
||||
*/
|
||||
public boolean isRenewable() {
|
||||
return renewable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* Utility class for {@link LoginToken}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@UtilityClass
|
||||
class LoginTokenUtil {
|
||||
|
||||
/**
|
||||
* Construct a {@link LoginToken} from an auth response.
|
||||
*
|
||||
* @param auth {@link Map} holding a login response.
|
||||
* @return the {@link LoginToken}
|
||||
*/
|
||||
static LoginToken from(Map<String, Object> auth) {
|
||||
|
||||
String token = (String) auth.get("client_token");
|
||||
Boolean renewable = (Boolean) auth.get("renewable");
|
||||
Number leaseDuration = (Number) auth.get("lease_duration");
|
||||
|
||||
if (renewable != null && renewable) {
|
||||
return LoginToken.renewable(token, leaseDuration.longValue());
|
||||
}
|
||||
|
||||
if (leaseDuration != null) {
|
||||
return LoginToken.of(token, leaseDuration.longValue());
|
||||
}
|
||||
|
||||
return LoginToken.of(token);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import org.springframework.vault.support.VaultToken;
|
||||
* Implementing classes usually use {@link ClientAuthentication} to log into Vault and obtain tokens.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see DefaultSessionManager
|
||||
* @see SimpleSessionManager
|
||||
* @see ClientAuthentication
|
||||
*/
|
||||
public interface SessionManager {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
|
||||
/**
|
||||
@@ -27,7 +28,7 @@ import org.springframework.vault.support.VaultToken;
|
||||
* @see ClientAuthentication
|
||||
* @see VaultToken
|
||||
*/
|
||||
public class DefaultSessionManager implements SessionManager {
|
||||
public class SimpleSessionManager implements SessionManager {
|
||||
|
||||
private final ClientAuthentication clientAuthentication;
|
||||
|
||||
@@ -36,11 +37,14 @@ public class DefaultSessionManager implements SessionManager {
|
||||
private volatile VaultToken token;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultSessionManager} using a {@link ClientAuthentication}.
|
||||
* Creates a new {@link SimpleSessionManager} using a {@link ClientAuthentication}.
|
||||
*
|
||||
* @param clientAuthentication must not be {@literal null}.
|
||||
*/
|
||||
public DefaultSessionManager(ClientAuthentication clientAuthentication) {
|
||||
public SimpleSessionManager(ClientAuthentication clientAuthentication) {
|
||||
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class VaultResponseEntity<T> {
|
||||
|
||||
private final String message;
|
||||
|
||||
VaultResponseEntity(T body, HttpStatus statusCode, URI uri, String message) {
|
||||
protected VaultResponseEntity(T body, HttpStatus statusCode, URI uri, String message) {
|
||||
this.body = body;
|
||||
this.statusCode = statusCode;
|
||||
this.uri = uri;
|
||||
|
||||
@@ -13,17 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.vault.config;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.authentication.ClientAuthentication;
|
||||
import org.springframework.vault.authentication.DefaultSessionManager;
|
||||
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
|
||||
import org.springframework.vault.authentication.SessionManager;
|
||||
import org.springframework.vault.client.VaultClient;
|
||||
import org.springframework.vault.client.VaultEndpoint;
|
||||
@@ -56,19 +57,36 @@ public abstract class AbstractVaultConfiguration {
|
||||
public abstract ClientAuthentication clientAuthentication();
|
||||
|
||||
/**
|
||||
* Annotate with {@link Bean} in case you want to expose a {@link SessionManager} instance to the
|
||||
* {@link org.springframework.context.ApplicationContext}.
|
||||
* Create a {@link AsyncTaskExecutor} used by {@link LifecycleAwareSessionManager}. Annotate with {@link Bean} in case
|
||||
* you want to expose a {@link AsyncTaskExecutor} instance to the
|
||||
* {@link org.springframework.context.ApplicationContext}. This might be useful to supply managed executor instances
|
||||
* or {@link AsyncTaskExecutor}s using a queue/pooled threads.
|
||||
*
|
||||
* @return the {@link AsyncTaskExecutor} to use. Must not be {@literal null}.
|
||||
* @see AsyncTaskExecutor
|
||||
*/
|
||||
public AsyncTaskExecutor asyncTaskExecutor() {
|
||||
return new SimpleAsyncTaskExecutor("spring-vault-SimpleAsyncTaskExecutor-");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link LifecycleAwareSessionManager} using {@link #clientAuthentication()} and {@link #vaultClient()}.
|
||||
* This {@link SessionManager} uses {@link #asyncTaskExecutor()}.
|
||||
*
|
||||
* @return the {@link SessionManager} for Vault session management.
|
||||
* @see SessionManager
|
||||
* @see DefaultSessionManager
|
||||
* @see LifecycleAwareSessionManager
|
||||
* @see #clientAuthentication()
|
||||
* @see #asyncTaskExecutor() ()
|
||||
* @see #vaultClient()
|
||||
*/
|
||||
@Bean
|
||||
public SessionManager sessionManager() {
|
||||
|
||||
ClientAuthentication clientAuthentication = clientAuthentication();
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
|
||||
return new DefaultSessionManager(clientAuthentication);
|
||||
return new LifecycleAwareSessionManager(clientAuthentication, asyncTaskExecutor(), vaultClient());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +107,7 @@ public abstract class AbstractVaultConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ClientFactoryWrapper} containing a {@link ClientHttpRequestFactory}.
|
||||
* Create a {@link ClientFactoryWrapper} containing a {@link ClientHttpRequestFactory}.
|
||||
* {@link ClientHttpRequestFactory} is not exposed as root bean because {@link ClientHttpRequestFactory} is configured
|
||||
* with {@link ClientOptions} and {@link SslConfiguration} which are not necessarily applicable for the whole
|
||||
* application.
|
||||
@@ -125,7 +143,7 @@ public abstract class AbstractVaultConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link VaultTemplate}.
|
||||
* Create a {@link VaultTemplate}.
|
||||
*
|
||||
* @return the {@link VaultTemplate}.
|
||||
* @see #vaultClientFactory()
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -30,8 +31,8 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.authentication.ClientAuthentication;
|
||||
import org.springframework.vault.authentication.DefaultSessionManager;
|
||||
import org.springframework.vault.authentication.SessionManager;
|
||||
import org.springframework.vault.authentication.SimpleSessionManager;
|
||||
import org.springframework.vault.client.VaultAccessor.RestTemplateCallback;
|
||||
import org.springframework.vault.client.VaultClient;
|
||||
import org.springframework.vault.client.VaultException;
|
||||
@@ -47,16 +48,20 @@ import org.springframework.vault.support.VaultResponseSupport;
|
||||
* @see VaultClientFactory
|
||||
* @see SessionManager
|
||||
*/
|
||||
public class VaultTemplate implements InitializingBean, VaultOperations {
|
||||
public class VaultTemplate implements InitializingBean, VaultOperations, DisposableBean {
|
||||
|
||||
private VaultClientFactory vaultClientFactory;
|
||||
|
||||
private SessionManager sessionManager;
|
||||
|
||||
private final boolean dedicatedSessionManager;
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultTemplate} without setting {@link VaultClientFactory} and {@link SessionManager}.
|
||||
*/
|
||||
public VaultTemplate() {}
|
||||
public VaultTemplate() {
|
||||
this.dedicatedSessionManager = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultTemplate} with a {@link VaultClient} and {@link ClientAuthentication}.
|
||||
@@ -70,7 +75,8 @@ public class VaultTemplate implements InitializingBean, VaultOperations {
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
|
||||
this.vaultClientFactory = new DefaultVaultClientFactory(vaultClient);
|
||||
this.sessionManager = new DefaultSessionManager(clientAuthentication);
|
||||
this.sessionManager = new SimpleSessionManager(clientAuthentication);
|
||||
this.dedicatedSessionManager = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +92,7 @@ public class VaultTemplate implements InitializingBean, VaultOperations {
|
||||
|
||||
this.vaultClientFactory = vaultClientFactory;
|
||||
this.sessionManager = sessionManager;
|
||||
this.dedicatedSessionManager = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +126,15 @@ public class VaultTemplate implements InitializingBean, VaultOperations {
|
||||
Assert.notNull(sessionManager, "SessionManager must not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
|
||||
if (dedicatedSessionManager && sessionManager instanceof DisposableBean) {
|
||||
((DisposableBean) sessionManager).destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultSysOperations opsForSys() {
|
||||
return new VaultSysTemplate(this);
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.vault.support;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Value object for a Vault token.
|
||||
@@ -26,39 +27,26 @@ import lombok.EqualsAndHashCode;
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@ToString(exclude = "token")
|
||||
public class VaultToken {
|
||||
|
||||
private final String token;
|
||||
|
||||
private final long leaseDuration;
|
||||
protected VaultToken(String token) {
|
||||
|
||||
Assert.hasText(token, "Token must not be empty");
|
||||
|
||||
private VaultToken(String token, long leaseDuration) {
|
||||
this.token = token;
|
||||
this.leaseDuration = leaseDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultToken}.
|
||||
*
|
||||
* @param token must not be {@literal null}.
|
||||
* @param token must not be empty or {@literal null}.
|
||||
* @return the created {@link VaultToken}
|
||||
*/
|
||||
public static VaultToken of(String token) {
|
||||
return of(token, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultToken} with a {@code leaseDuration}.
|
||||
*
|
||||
* @param token must not be {@literal null}.
|
||||
* @param leaseDuration the lease duration.
|
||||
* @return the created {@link VaultToken}
|
||||
*/
|
||||
public static VaultToken of(String token, long leaseDuration) {
|
||||
|
||||
Assert.hasText(token, "Token must not be empty");
|
||||
|
||||
return new VaultToken(token, leaseDuration);
|
||||
return new VaultToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,11 +56,4 @@ public class VaultToken {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lease duration. May be {@literal 0} if none.
|
||||
*/
|
||||
public long getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ public class VaultTokenResponse extends VaultResponse {
|
||||
* @return the {@link VaultToken}.
|
||||
*/
|
||||
public VaultToken getToken() {
|
||||
return VaultToken.of((String) getAuth().get("client_token"),
|
||||
((Number) getAuth().get("lease_duration")).longValue());
|
||||
return VaultToken.of((String) getAuth().get("client_token"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public class AppIdAuthenticationUnitTests {
|
||||
AppIdAuthentication authentication = new AppIdAuthentication(options, vaultClient);
|
||||
|
||||
VaultToken login = authentication.login();
|
||||
assertThat(login).isInstanceOf(LoginToken.class);
|
||||
assertThat(login.getToken()).isEqualTo("my-token");
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ public class AwsEc2AuthenticationUnitTests {
|
||||
.andExpect(method(HttpMethod.POST)) //
|
||||
.andExpect(jsonPath("$.pkcs7").value("value")) //
|
||||
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
|
||||
.body("{" + "\"auth\":{\"client_token\":\"my-token\"}" + "}"));
|
||||
.body("{" + "\"auth\":{\"client_token\":\"my-token\", \"lease_duration\":20}" + "}"));
|
||||
|
||||
AwsEc2Authentication authentication = new AwsEc2Authentication(vaultClient) {
|
||||
@Override
|
||||
@@ -96,9 +96,12 @@ public class AwsEc2AuthenticationUnitTests {
|
||||
}
|
||||
};
|
||||
|
||||
VaultToken vaultToken = authentication.login();
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(vaultToken.getToken()).isEqualTo("my-token");
|
||||
assertThat(login).isInstanceOf(LoginToken.class);
|
||||
assertThat(login.getToken()).isEqualTo("my-token");
|
||||
assertThat(((LoginToken) login).getLeaseDuration()).isEqualTo(20);
|
||||
assertThat(((LoginToken) login).isRenewable()).isFalse();
|
||||
}
|
||||
|
||||
@Test(expected = VaultException.class)
|
||||
|
||||
@@ -40,8 +40,6 @@ public class ClientCertificateAuthenticationUnitTests {
|
||||
private VaultClient vaultClient;
|
||||
private MockRestServiceServer mockRest;
|
||||
|
||||
private AppIdAuthentication sut;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
|
||||
@@ -56,12 +54,16 @@ public class ClientCertificateAuthenticationUnitTests {
|
||||
mockRest.expect(requestTo("https://localhost:8200/v1/auth/cert/login")) //
|
||||
.andExpect(method(HttpMethod.POST)) //
|
||||
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
|
||||
.body("{" + "\"auth\":{\"client_token\":\"my-token\"}" + "}"));
|
||||
.body("{" + "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}" + "}"));
|
||||
|
||||
ClientCertificateAuthentication sut = new ClientCertificateAuthentication(vaultClient);
|
||||
|
||||
VaultToken login = sut.login();
|
||||
|
||||
assertThat(login).isInstanceOf(LoginToken.class);
|
||||
assertThat(login.getToken()).isEqualTo("my-token");
|
||||
assertThat(((LoginToken) login).getLeaseDuration()).isEqualTo(10);
|
||||
assertThat(((LoginToken) login).isRenewable()).isTrue();
|
||||
}
|
||||
|
||||
@Test(expected = VaultException.class)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -91,7 +91,8 @@ public class CubbyholeAuthenticationIntegrationTests extends IntegrationTestSupp
|
||||
authentication.login();
|
||||
fail("Missing VaultException");
|
||||
} catch (VaultException e) {
|
||||
assertThat(e).hasMessageContaining("Cannot retrieve Token from cubbyhole").hasMessageContaining("permission denied");
|
||||
assertThat(e).hasMessageContaining("Cannot retrieve Token from cubbyhole")
|
||||
.hasMessageContaining("permission denied");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +63,11 @@ public class CubbyholeAuthenticationUnitTests {
|
||||
.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");
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(login).isInstanceOf(LoginToken.class);
|
||||
assertThat(login.getToken()).isEqualTo("5e6332cf-f003-6369-8cba-5bce2330f6cc");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,9 +83,11 @@ public class CubbyholeAuthenticationUnitTests {
|
||||
.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");
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(login).isNotInstanceOf(LoginToken.class);
|
||||
assertThat(login.getToken()).isEqualTo("058222ef-9ab9-ff39-f087-9d5bee64e46d");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.vault.client.VaultResponseEntity;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.core.VaultTokenOperations;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.vault.support.VaultTokenRequest;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link LifecycleAwareSessionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class LifecycleAwareSessionManagerIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
|
||||
@Test
|
||||
public void shouldLogin() {
|
||||
|
||||
LoginToken loginToken = createLoginToken();
|
||||
TokenAuthentication tokenAuthentication = new TokenAuthentication(loginToken);
|
||||
|
||||
LifecycleAwareSessionManager sessionManager = new LifecycleAwareSessionManager(tokenAuthentication, taskExecutor,
|
||||
prepare().getVaultClient());
|
||||
|
||||
assertThat(sessionManager.getSessionToken()).isSameAs(loginToken);
|
||||
|
||||
}
|
||||
|
||||
// Expect no exception to be thrown.
|
||||
@Test
|
||||
public void shouldRenewToken() {
|
||||
|
||||
VaultTokenOperations tokenOperations = prepare().getVaultOperations().opsForToken();
|
||||
|
||||
VaultTokenRequest tokenRequest = new VaultTokenRequest();
|
||||
tokenRequest.setRenewable(true);
|
||||
tokenRequest.setTtl("1h");
|
||||
tokenRequest.setExplicitMaxTtl("10h");
|
||||
|
||||
VaultToken token = tokenOperations.createOrphan(tokenRequest).getToken();
|
||||
|
||||
TokenAuthentication tokenAuthentication = new TokenAuthentication(LoginToken.renewable(token.getToken(), 0));
|
||||
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
LifecycleAwareSessionManager sessionManager = new LifecycleAwareSessionManager(tokenAuthentication, taskExecutor,
|
||||
prepare().getVaultClient()) {
|
||||
@Override
|
||||
public VaultToken getSessionToken() {
|
||||
|
||||
if (counter.getAndIncrement() > 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return super.getSessionToken();
|
||||
}
|
||||
};
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
sessionManager.renewToken();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevokeOnDisposal() {
|
||||
|
||||
final LoginToken loginToken = createLoginToken();
|
||||
TokenAuthentication tokenAuthentication = new TokenAuthentication(loginToken);
|
||||
|
||||
LifecycleAwareSessionManager sessionManager = new LifecycleAwareSessionManager(tokenAuthentication, taskExecutor,
|
||||
prepare().getVaultClient());
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
sessionManager.destroy();
|
||||
|
||||
prepare().getVaultOperations().doWithVault(new VaultOperations.SessionCallback<Object>() {
|
||||
|
||||
@Override
|
||||
public Object doWithVault(VaultOperations.VaultSession session) {
|
||||
|
||||
VaultResponseEntity<Map> entity = session
|
||||
.getForEntity(String.format("auth/token/lookup/%s", loginToken.getToken()), Map.class);
|
||||
|
||||
assertThat(entity.getStatusCode()).isIn(HttpStatus.NOT_FOUND, HttpStatus.FORBIDDEN);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private LoginToken createLoginToken() {
|
||||
|
||||
VaultTokenOperations tokenOperations = prepare().getVaultOperations().opsForToken();
|
||||
VaultToken token = tokenOperations.createOrphan().getToken();
|
||||
|
||||
return LoginToken.of(token.getToken());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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.mockito.Mockito.*;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.vault.client.VaultClient;
|
||||
import org.springframework.vault.client.VaultResponseEntity;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LifecycleAwareSessionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LifecycleAwareSessionManagerUnitTests {
|
||||
|
||||
@Mock private ClientAuthentication clientAuthentication;
|
||||
|
||||
@Mock private AsyncTaskExecutor taskExecutor;
|
||||
|
||||
@Mock private VaultClient vaultClient;
|
||||
|
||||
private LifecycleAwareSessionManager sessionManager;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
sessionManager = new LifecycleAwareSessionManager(clientAuthentication, taskExecutor, vaultClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldObtainTokenFromClientAuthentication() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.of("login"));
|
||||
|
||||
assertThat(sessionManager.getSessionToken()).isEqualTo(LoginToken.of("login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevokeLoginTokenOnDestroy() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.of("login"));
|
||||
|
||||
when(vaultClient.postForEntity(eq("auth/token/revoke-self"), eq(LoginToken.of("login")), ArgumentMatchers.any(),
|
||||
any(Class.class))).thenReturn(new ResponseEntity<Object>(null, HttpStatus.OK, null, null));
|
||||
|
||||
sessionManager.renewToken();
|
||||
sessionManager.destroy();
|
||||
|
||||
verify(vaultClient).postForEntity(eq("auth/token/revoke-self"), eq(LoginToken.of("login")), ArgumentMatchers.any(),
|
||||
any(Class.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRevokeRegularTokenOnDestroy() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(VaultToken.of("login"));
|
||||
|
||||
sessionManager.renewToken();
|
||||
sessionManager.destroy();
|
||||
|
||||
verifyZeroInteractions(vaultClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotThrowExceptionsOnRevokeErrors() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.of("login"));
|
||||
|
||||
when(vaultClient.postForEntity(eq("auth/token/revoke-self"), eq(LoginToken.of("login")), ArgumentMatchers.any(),
|
||||
any(Class.class))).thenReturn(new ResponseEntity<Object>(null, HttpStatus.INTERNAL_SERVER_ERROR, null, null));
|
||||
|
||||
sessionManager.renewToken();
|
||||
sessionManager.destroy();
|
||||
|
||||
verify(vaultClient).postForEntity(eq("auth/token/revoke-self"), eq(LoginToken.of("login")), ArgumentMatchers.any(),
|
||||
any(Class.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldScheduleTokenRenewal() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.renewable("login", 10));
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
|
||||
verify(taskExecutor).execute(any(Runnable.class), eq(5000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRunTokenRenewal() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.renewable("login", 10));
|
||||
when(vaultClient.postForEntity(eq("auth/token/renew-self"), eq(LoginToken.renewable("login", 10)),
|
||||
ArgumentMatchers.any(), any(Class.class)))
|
||||
.thenReturn(new ResponseEntity<Object>(null, HttpStatus.OK, null, null));
|
||||
|
||||
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
verify(taskExecutor).execute(runnableCaptor.capture(), eq(5000L));
|
||||
|
||||
runnableCaptor.getValue().run();
|
||||
verify(vaultClient).postForEntity(eq("auth/token/renew-self"), eq(LoginToken.renewable("login", 10)),
|
||||
ArgumentMatchers.any(), any(Class.class));
|
||||
verify(clientAuthentication, times(1)).login();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReScheduleTokenRenewalAfterSucessfulRenewal() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.renewable("login", 10));
|
||||
when(vaultClient.postForEntity(eq("auth/token/renew-self"), eq(LoginToken.renewable("login", 10)),
|
||||
ArgumentMatchers.any(), any(Class.class)))
|
||||
.thenReturn(new ResponseEntity<Object>(null, HttpStatus.OK, null, null));
|
||||
|
||||
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
verify(taskExecutor).execute(runnableCaptor.capture(), eq(5000L));
|
||||
|
||||
runnableCaptor.getValue().run();
|
||||
|
||||
verify(taskExecutor, times(2)).execute(any(Runnable.class), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotReScheduleTokenRenewalAfterFailedRenewal() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.renewable("login", 10));
|
||||
when(vaultClient.postForEntity(eq("auth/token/renew-self"), eq(LoginToken.renewable("login", 10)),
|
||||
ArgumentMatchers.any(), any(Class.class)))
|
||||
.thenReturn(new ResponseEntity<Object>(null, HttpStatus.INTERNAL_SERVER_ERROR, null, null));
|
||||
|
||||
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
verify(taskExecutor).execute(runnableCaptor.capture(), eq(5000L));
|
||||
|
||||
runnableCaptor.getValue().run();
|
||||
|
||||
verify(taskExecutor, times(1)).execute(any(Runnable.class), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldObtainTokenIfNoTokenAvailable() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.renewable("login", 10));
|
||||
|
||||
sessionManager.renewToken();
|
||||
|
||||
assertThat(sessionManager.getSessionToken()).isEqualTo(LoginToken.renewable("login", 10));
|
||||
verify(clientAuthentication, times(1)).login();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renewShouldReportFalseIfTokenRenewalFails() {
|
||||
|
||||
when(clientAuthentication.login()).thenReturn(LoginToken.renewable("login", 10));
|
||||
when(vaultClient.postForEntity(anyString(), any(VaultToken.class), ArgumentMatchers.any(), any(Class.class)))
|
||||
.thenReturn(new ResponseEntity<Object>(null, HttpStatus.BAD_REQUEST, null, null));
|
||||
|
||||
sessionManager.getSessionToken();
|
||||
|
||||
assertThat(sessionManager.renewToken()).isFalse();
|
||||
verify(clientAuthentication, times(1)).login();
|
||||
}
|
||||
|
||||
static class ResponseEntity<T> extends VaultResponseEntity<T> {
|
||||
|
||||
protected ResponseEntity(T body, HttpStatus statusCode, URI uri, String message) {
|
||||
super(body, statusCode, uri, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LoginToken}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class LoginTokenUnitTests {
|
||||
|
||||
@Test
|
||||
public void shouldConstructLoginToken() {
|
||||
|
||||
assertThat(LoginToken.of("token")).isInstanceOf(LoginToken.class);
|
||||
assertThat(LoginToken.of("token", 1)).isInstanceOf(LoginToken.class);
|
||||
assertThat(LoginToken.renewable("token", 1)).isInstanceOf(LoginToken.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringShouldPrintFields() {
|
||||
|
||||
assertThat(LoginToken.of("token").toString()).isEqualTo("LoginToken(renewable=false, leaseDuration=0)");
|
||||
assertThat(LoginToken.of("token", 1).toString()).isEqualTo("LoginToken(renewable=false, leaseDuration=1)");
|
||||
assertThat(LoginToken.renewable("token", 1).toString()).isEqualTo("LoginToken(renewable=true, leaseDuration=1)");
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.vault.util;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.client.VaultClient;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.core.VaultSysOperations;
|
||||
import org.springframework.vault.support.VaultInitializationRequest;
|
||||
@@ -35,12 +36,15 @@ import org.springframework.vault.support.VaultUnsealStatus;
|
||||
*/
|
||||
public class PrepareVault {
|
||||
|
||||
private final VaultClient vaultClient;
|
||||
|
||||
private final VaultOperations vaultOperations;
|
||||
|
||||
private final VaultSysOperations adminOperations;
|
||||
|
||||
public PrepareVault(VaultOperations vaultOperations) {
|
||||
public PrepareVault(VaultClient vaultClient, VaultOperations vaultOperations) {
|
||||
|
||||
this.vaultClient = vaultClient;
|
||||
this.vaultOperations = vaultOperations;
|
||||
this.adminOperations = vaultOperations.opsForSys();
|
||||
}
|
||||
@@ -151,4 +155,8 @@ public class PrepareVault {
|
||||
public VaultOperations getVaultOperations() {
|
||||
return vaultOperations;
|
||||
}
|
||||
|
||||
public VaultClient getVaultClient() {
|
||||
return vaultClient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class VaultRule extends ExternalResource {
|
||||
VaultTemplate vaultTemplate = new VaultTemplate(clientFactory, new PreparingSessionManager());
|
||||
|
||||
this.token = Settings.token();
|
||||
this.prepareVault = new PrepareVault(vaultTemplate);
|
||||
this.prepareVault = new PrepareVault(vaultClient, vaultTemplate);
|
||||
this.vaultEndpoint = vaultEndpoint;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,8 +220,8 @@ class AppConfig {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultSessionManager sessionManager() {
|
||||
return new DefaultSessionManager(new TokenAuthentication("…"));
|
||||
public SimpleSessionManager sessionManager() {
|
||||
return new SimpleSessionManager(new TokenAuthentication("…"));
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -232,6 +232,14 @@ There are several overloaded constructors of `VaultTemplate`. These are
|
||||
* `VaultTemplate(VaultClient, ClientAuthentication)` - takes the `VaultClient` object and client authentication
|
||||
* `VaultTemplate(VaultClientFactory, SessionManager)` - takes a client factory for resource management and a `SessionManager`.
|
||||
|
||||
[[vault.core.template.sessionmanagement]]
|
||||
=== Session Management
|
||||
|
||||
Spring Vault requires a `ClientAuthentication` to login and access Vault. See <<vault.core.authentication>> on details regarding authentication. Vault login should not occur on each authenticated Vault interaction but must be reused throughout a session. This aspect is handled by a `SessionManager` implementation. A `SessionManager` decides how often it obtains a token, about revocation and renewal. Spring Vault comes with two implementations:
|
||||
|
||||
* `SimpleSessionManager`: Just obtains tokens from the supplied `ClientAuthentication` without refresh and revocation
|
||||
* `LifecycleAwareSessionManager`: This `SessionManager` schedules token renewal if a token is renewable and revoke a login token on disposal. Renewal is scheduled with an `AsyncTaskExecutor`. `LifecycleAwareSessionManager` is configured by default if using `AbstractVaultConfiguration`.
|
||||
|
||||
[[vault.client-ssl]]
|
||||
== Vault Client SSL configuration
|
||||
|
||||
|
||||
Reference in New Issue
Block a user