diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppIdAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppIdAuthentication.java index 9d2fb206..cf698ea0 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppIdAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppIdAuthentication.java @@ -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 getAppIdLogin(String appId, String userId) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java index 030a4090..dc119dd8 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsEc2Authentication.java @@ -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 getEc2Login() { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java index f09883da..71d68b3b 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java @@ -56,18 +56,15 @@ public class ClientCertificateAuthentication implements ClientAuthentication { private VaultToken createTokenUsingTlsCertAuthentication(String path) { - VaultResponseEntity response = vaultClient.postForEntity(String.format("auth/%s/login", path), + VaultResponseEntity 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()); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java index 4c3b410e..a590c6ae 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CubbyholeAuthentication.java @@ -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()) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java new file mode 100644 index 00000000..aeb10653 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java @@ -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}. + *

+ * 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. + *

+ * 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 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 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()); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java new file mode 100644 index 00000000..d29101e3 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java @@ -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; + } + +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java new file mode 100644 index 00000000..71e1900e --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java @@ -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 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); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/SessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/SessionManager.java index 9c39784e..1099b83b 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/SessionManager.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/SessionManager.java @@ -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 { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/DefaultSessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/SimpleSessionManager.java similarity index 81% rename from spring-vault-core/src/main/java/org/springframework/vault/authentication/DefaultSessionManager.java rename to spring-vault-core/src/main/java/org/springframework/vault/authentication/SimpleSessionManager.java index f5833610..99744661 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/DefaultSessionManager.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/SimpleSessionManager.java @@ -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; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponseEntity.java b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponseEntity.java index 6a4bcf2e..94670f58 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponseEntity.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/VaultResponseEntity.java @@ -35,7 +35,7 @@ public class VaultResponseEntity { 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; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java index ce6e004a..25122712 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java @@ -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() diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java index 845c8996..7bf5b173 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java @@ -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); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java index 95172104..edd4742e 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java @@ -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; - } - } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java index 9ddfac9a..21ba366d 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java @@ -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")); } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationUnitTests.java index 4e7eac9f..cc1300aa 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppIdAuthenticationUnitTests.java @@ -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"); } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java index bd10946c..86caab70 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsEc2AuthenticationUnitTests.java @@ -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) diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationUnitTests.java index cb997a6a..f2ad57aa 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationUnitTests.java @@ -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) diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java index 53245af6..9c3af1b5 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationIntegrationTests.java @@ -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"); } } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationUnitTests.java index 99b993ee..1aeeff16 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/CubbyholeAuthenticationUnitTests.java @@ -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 diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerIntegrationTests.java new file mode 100644 index 00000000..7d5c5243 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerIntegrationTests.java @@ -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() { + + @Override + public Object doWithVault(VaultOperations.VaultSession session) { + + VaultResponseEntity 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()); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerUnitTests.java new file mode 100644 index 00000000..dced5e8c --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerUnitTests.java @@ -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(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(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(null, HttpStatus.OK, null, null)); + + ArgumentCaptor 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(null, HttpStatus.OK, null, null)); + + ArgumentCaptor 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(null, HttpStatus.INTERNAL_SERVER_ERROR, null, null)); + + ArgumentCaptor 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(null, HttpStatus.BAD_REQUEST, null, null)); + + sessionManager.getSessionToken(); + + assertThat(sessionManager.renewToken()).isFalse(); + verify(clientAuthentication, times(1)).login(); + } + + static class ResponseEntity extends VaultResponseEntity { + + protected ResponseEntity(T body, HttpStatus statusCode, URI uri, String message) { + super(body, statusCode, uri, message); + } + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LoginTokenUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LoginTokenUnitTests.java new file mode 100644 index 00000000..30653ef5 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LoginTokenUnitTests.java @@ -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)"); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/util/PrepareVault.java b/spring-vault-core/src/test/java/org/springframework/vault/util/PrepareVault.java index ec57fe28..99cce702 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/util/PrepareVault.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/util/PrepareVault.java @@ -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; + } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/util/VaultRule.java b/spring-vault-core/src/test/java/org/springframework/vault/util/VaultRule.java index 69c514f2..788204f4 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/util/VaultRule.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/util/VaultRule.java @@ -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; } diff --git a/src/main/asciidoc/reference/getting-started.adoc b/src/main/asciidoc/reference/getting-started.adoc index 709fd4a5..5085a97c 100644 --- a/src/main/asciidoc/reference/getting-started.adoc +++ b/src/main/asciidoc/reference/getting-started.adoc @@ -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 <> 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