Skip to content

Commit 6c08ff3

Browse files
committed
Consider LeaseStrategy outcome whether to retry session token renewal.
LeaseStrategy.shouldDrop(…) now controls whether to retry the session token. Dropping the token terminates renewals while retaining the token leads to another renewal. Additionally, we introduced LeaseStrategy.retainOnIoError() to retain tokens on network failures (IOException). Closes gh-646
1 parent 306f2d8 commit 6c08ff3

File tree

5 files changed

+94
-65
lines changed

5 files changed

+94
-65
lines changed

spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,7 @@
2525
import org.springframework.util.Assert;
2626
import org.springframework.util.ClassUtils;
2727
import org.springframework.vault.VaultException;
28-
import org.springframework.vault.authentication.event.AfterLoginEvent;
29-
import org.springframework.vault.authentication.event.AfterLoginTokenRenewedEvent;
30-
import org.springframework.vault.authentication.event.AfterLoginTokenRevocationEvent;
31-
import org.springframework.vault.authentication.event.AuthenticationErrorEvent;
32-
import org.springframework.vault.authentication.event.AuthenticationErrorListener;
33-
import org.springframework.vault.authentication.event.AuthenticationListener;
34-
import org.springframework.vault.authentication.event.BeforeLoginTokenRenewedEvent;
35-
import org.springframework.vault.authentication.event.BeforeLoginTokenRevocationEvent;
36-
import org.springframework.vault.authentication.event.LoginFailedEvent;
37-
import org.springframework.vault.authentication.event.LoginTokenExpiredEvent;
38-
import org.springframework.vault.authentication.event.LoginTokenRenewalFailedEvent;
39-
import org.springframework.vault.authentication.event.LoginTokenRevocationFailedEvent;
28+
import org.springframework.vault.authentication.event.*;
4029
import org.springframework.vault.client.VaultHttpHeaders;
4130
import org.springframework.vault.client.VaultResponses;
4231
import org.springframework.vault.support.VaultResponse;
@@ -192,13 +181,17 @@ protected void revoke(VaultToken token) {
192181
* token was obtained or refresh failed.
193182
*/
194183
public boolean renewToken() {
184+
return tryRenewToken().successful;
185+
}
186+
187+
private RenewOutcome tryRenewToken() {
195188

196189
this.logger.info("Renewing token");
197190

198191
Optional<TokenWrapper> token = getToken();
199192
if (!token.isPresent()) {
200193
getSessionToken();
201-
return false;
194+
return RenewOutcome.TERMINAL_ERROR;
202195
}
203196

204197
TokenWrapper tokenWrapper = token.get();
@@ -209,7 +202,8 @@ public boolean renewToken() {
209202

210203
VaultTokenRenewalException exception = new VaultTokenRenewalException(format("Cannot renew token", e), e);
211204

212-
if (getLeaseStrategy().shouldDrop(exception)) {
205+
boolean shouldDrop = getLeaseStrategy().shouldDrop(exception);
206+
if (shouldDrop) {
213207
setToken(Optional.empty());
214208
}
215209

@@ -221,11 +215,11 @@ public boolean renewToken() {
221215
}
222216

223217
dispatch(new LoginTokenRenewalFailedEvent(tokenWrapper.getToken(), exception));
224-
return false;
218+
return shouldDrop ? RenewOutcome.TERMINAL_ERROR : RenewOutcome.RENEWABLE_ERROR;
225219
}
226220
}
227221

228-
private boolean doRenew(TokenWrapper wrapper) {
222+
private RenewOutcome doRenew(TokenWrapper wrapper) {
229223

230224
dispatch(new BeforeLoginTokenRenewedEvent(wrapper.getToken()));
231225
VaultResponse vaultResponse = this.restOperations.postForObject("auth/token/renew-self",
@@ -246,13 +240,13 @@ private boolean doRenew(TokenWrapper wrapper) {
246240

247241
setToken(Optional.empty());
248242
dispatch(new LoginTokenExpiredEvent(renewed));
249-
return false;
243+
return RenewOutcome.TERMINAL_ERROR;
250244
}
251245

252246
setToken(Optional.of(new TokenWrapper(renewed, wrapper.revocable)));
253247
dispatch(new AfterLoginTokenRenewedEvent(renewed));
254248

255-
return true;
249+
return RenewOutcome.SUCCESS;
256250
}
257251

258252
@Override
@@ -314,13 +308,11 @@ protected VaultToken login() {
314308
*/
315309
protected boolean isTokenRenewable() {
316310

317-
return getToken().map(TokenWrapper::getToken).filter(LoginToken.class::isInstance)
318-
//
319-
.filter(it -> {
311+
return getToken().map(TokenWrapper::getToken).filter(LoginToken.class::isInstance).filter(it -> {
320312

321-
LoginToken loginToken = (LoginToken) it;
322-
return !loginToken.getLeaseDuration().isZero() && loginToken.isRenewable();
323-
}).isPresent();
313+
LoginToken loginToken = (LoginToken) it;
314+
return !loginToken.getLeaseDuration().isZero() && loginToken.isRenewable();
315+
}).isPresent();
324316
}
325317

326318
private void scheduleRenewal() {
@@ -338,7 +330,8 @@ private void scheduleRenewal() {
338330

339331
try {
340332
if (isTokenRenewable()) {
341-
if (renewToken()) {
333+
RenewOutcome result = tryRenewToken();
334+
if (result.shouldRenew()) {
342335
scheduleRenewal();
343336
}
344337
}
@@ -398,4 +391,27 @@ public boolean isRevocable() {
398391

399392
}
400393

394+
static class RenewOutcome {
395+
396+
private static final RenewOutcome SUCCESS = new RenewOutcome(false, true);
397+
398+
private static final RenewOutcome TERMINAL_ERROR = new RenewOutcome(true, false);
399+
400+
private static final RenewOutcome RENEWABLE_ERROR = new RenewOutcome(false, false);
401+
402+
private final boolean terminalError;
403+
404+
private final boolean successful;
405+
406+
private RenewOutcome(boolean terminalError, boolean successful) {
407+
this.terminalError = terminalError;
408+
this.successful = successful;
409+
}
410+
411+
public boolean shouldRenew() {
412+
return !terminalError;
413+
}
414+
415+
}
416+
401417
}

spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,7 @@
2727
import org.springframework.util.Assert;
2828
import org.springframework.util.ClassUtils;
2929
import org.springframework.vault.VaultException;
30-
import org.springframework.vault.authentication.event.AfterLoginEvent;
31-
import org.springframework.vault.authentication.event.AfterLoginTokenRenewedEvent;
32-
import org.springframework.vault.authentication.event.AfterLoginTokenRevocationEvent;
33-
import org.springframework.vault.authentication.event.AuthenticationErrorEvent;
34-
import org.springframework.vault.authentication.event.AuthenticationErrorListener;
35-
import org.springframework.vault.authentication.event.AuthenticationListener;
36-
import org.springframework.vault.authentication.event.BeforeLoginTokenRenewedEvent;
37-
import org.springframework.vault.authentication.event.BeforeLoginTokenRevocationEvent;
38-
import org.springframework.vault.authentication.event.LoginFailedEvent;
39-
import org.springframework.vault.authentication.event.LoginTokenExpiredEvent;
40-
import org.springframework.vault.authentication.event.LoginTokenRenewalFailedEvent;
41-
import org.springframework.vault.authentication.event.LoginTokenRevocationFailedEvent;
30+
import org.springframework.vault.authentication.event.*;
4231
import org.springframework.vault.client.VaultHttpHeaders;
4332
import org.springframework.vault.client.VaultResponses;
4433
import org.springframework.vault.support.VaultResponse;
@@ -226,7 +215,8 @@ private Mono<TokenWrapper> doRenewToken(TokenWrapper wrapper) {
226215

227216
VaultTokenRenewalException exception = new VaultTokenRenewalException(format("Cannot renew token", e), e);
228217

229-
if (getLeaseStrategy().shouldDrop(exception)) {
218+
boolean shouldDrop = getLeaseStrategy().shouldDrop(exception);
219+
if (shouldDrop) {
230220
dropCurrentToken();
231221
}
232222

@@ -238,10 +228,8 @@ private Mono<TokenWrapper> doRenewToken(TokenWrapper wrapper) {
238228
}
239229

240230
dispatch(new LoginTokenRenewalFailedEvent(wrapper.getToken(), exception));
241-
return EMPTY;
242-
}
243-
244-
);
231+
return shouldDrop ? EMPTY : Mono.just(wrapper);
232+
});
245233
}
246234

247235
private Mono<TokenWrapper> doRenew(TokenWrapper tokenWrapper) {

spring-vault-core/src/main/java/org/springframework/vault/support/LeaseStrategy.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.vault.support;
1717

18+
import java.io.IOException;
19+
1820
/**
1921
* Strategy interface to control whether to retain or drop a
2022
* {@link org.springframework.vault.core.lease.domain.Lease} after a failure.
@@ -50,4 +52,27 @@ static LeaseStrategy retainOnError() {
5052
return error -> false;
5153
}
5254

55+
/**
56+
* Predefined strategy to retain leases on I/O errors.
57+
* @return the retain on I/O error strategy.
58+
* @since 2.3.3
59+
*/
60+
static LeaseStrategy retainOnIoError() {
61+
return error -> {
62+
63+
Throwable inspect = error;
64+
65+
do {
66+
if (inspect instanceof IOException) {
67+
return false;
68+
}
69+
70+
inspect = inspect.getCause();
71+
}
72+
while (inspect != null);
73+
74+
return true;
75+
};
76+
}
77+
5378
}

spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerSupportUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import org.springframework.vault.authentication.LifecycleAwareSessionManagerSupport.FixedTimeoutRefreshTrigger;
2525

26-
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.*;
2727

2828
/**
2929
* Unit tests for {@link LifecycleAwareSessionManagerSupport} .

spring-vault-core/src/test/java/org/springframework/vault/authentication/LifecycleAwareSessionManagerUnitTests.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.Map;
2222
import java.util.concurrent.atomic.AtomicReference;
2323

24+
import javax.net.ssl.SSLException;
25+
2426
import org.junit.jupiter.api.BeforeEach;
2527
import org.junit.jupiter.api.Test;
2628
import org.junit.jupiter.api.extension.ExtendWith;
@@ -36,18 +38,7 @@
3638
import org.springframework.http.ResponseEntity;
3739
import org.springframework.scheduling.TaskScheduler;
3840
import org.springframework.scheduling.Trigger;
39-
import org.springframework.vault.authentication.event.AfterLoginEvent;
40-
import org.springframework.vault.authentication.event.AfterLoginTokenRenewedEvent;
41-
import org.springframework.vault.authentication.event.AfterLoginTokenRevocationEvent;
42-
import org.springframework.vault.authentication.event.AuthenticationErrorEvent;
43-
import org.springframework.vault.authentication.event.AuthenticationErrorListener;
44-
import org.springframework.vault.authentication.event.AuthenticationEvent;
45-
import org.springframework.vault.authentication.event.AuthenticationListener;
46-
import org.springframework.vault.authentication.event.BeforeLoginTokenRenewedEvent;
47-
import org.springframework.vault.authentication.event.BeforeLoginTokenRevocationEvent;
48-
import org.springframework.vault.authentication.event.LoginFailedEvent;
49-
import org.springframework.vault.authentication.event.LoginTokenExpiredEvent;
50-
import org.springframework.vault.authentication.event.LoginTokenRevocationFailedEvent;
41+
import org.springframework.vault.authentication.event.*;
5142
import org.springframework.vault.client.VaultHttpHeaders;
5243
import org.springframework.vault.support.LeaseStrategy;
5344
import org.springframework.vault.support.VaultResponse;
@@ -57,16 +48,9 @@
5748
import org.springframework.web.client.ResourceAccessException;
5849
import org.springframework.web.client.RestOperations;
5950

60-
import static org.assertj.core.api.Assertions.assertThat;
61-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
62-
import static org.mockito.ArgumentMatchers.any;
63-
import static org.mockito.ArgumentMatchers.anyString;
64-
import static org.mockito.ArgumentMatchers.eq;
65-
import static org.mockito.Mockito.times;
66-
import static org.mockito.Mockito.verify;
67-
import static org.mockito.Mockito.verifyNoMoreInteractions;
68-
import static org.mockito.Mockito.verifyZeroInteractions;
69-
import static org.mockito.Mockito.when;
51+
import static org.assertj.core.api.Assertions.*;
52+
import static org.mockito.ArgumentMatchers.*;
53+
import static org.mockito.Mockito.*;
7054

7155
/**
7256
* Unit tests for {@link LifecycleAwareSessionManager}.
@@ -433,6 +417,22 @@ void renewShouldReportFalseIfTokenRenewalFails() {
433417
verify(this.clientAuthentication, times(1)).login();
434418
}
435419

420+
@Test
421+
void renewShouldRetainTokenOnIoError() {
422+
423+
when(this.clientAuthentication.login())
424+
.thenReturn(LoginToken.renewable("login".toCharArray(), Duration.ofSeconds(5)));
425+
when(this.restOperations.postForObject(anyString(), ArgumentMatchers.any(), ArgumentMatchers.<Class>any()))
426+
.thenThrow(new ResourceAccessException("err", new SSLException("foo")));
427+
428+
this.sessionManager.setLeaseStrategy(LeaseStrategy.retainOnIoError());
429+
this.sessionManager.getSessionToken();
430+
431+
assertThat(this.sessionManager.renewToken()).isFalse();
432+
assertThat(this.sessionManager.getToken()).isNotEmpty();
433+
verify(this.clientAuthentication, times(1)).login();
434+
}
435+
436436
private static VaultResponse fromToken(LoginToken loginToken) {
437437

438438
Map<String, Object> auth = new HashMap<>();

0 commit comments

Comments
 (0)