Skip to content

Commit

Permalink
Do not revoke batch tokens.
Browse files Browse the repository at this point in the history
We now track the token type and no longer attempt revoking batch tokens.
Also, refactor code duplicates to use LoginTokenUtil.

Closes gh-764
  • Loading branch information
mp911de committed Mar 7, 2023
1 parent 2d9463e commit 0b376d5
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@
* <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. Tokens are only revoked only if the associated
* {@link ClientAuthentication} returns a {@link LoginToken}.
* token refresh. Tokens are only revoked if the associated {@link ClientAuthentication}
* returns a {@link LoginToken#isServiceToken() service token}.
* <p>
* If Token renewal runs into a client-side error, it assumes the token was
* revoked/expired. It discards the token state so the next attempt will lead to another
* login attempt.
* <p>
* By default, {@link VaultToken} are looked up in Vault to determine renewability and the
* remaining TTL, see {@link #setTokenSelfLookupEnabled(boolean)}.
* By default, {@link VaultToken} are looked up in Vault to determine renewability,
* remaining TTL, accessor and type, see {@link #setTokenSelfLookupEnabled(boolean)}.
* <p>
* The session manager dispatches authentication events to {@link AuthenticationListener}
* and {@link AuthenticationErrorListener}. Event notifications are dispatched either on
Expand Down Expand Up @@ -390,7 +390,12 @@ public VaultToken getToken() {
}

public boolean isRevocable() {
return this.revocable;

if (token instanceof LoginToken login && login.isServiceToken()) {
return this.revocable;
}

return false;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package org.springframework.vault.authentication;

import java.time.Duration;
import java.util.Arrays;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.vault.support.VaultToken;

Expand All @@ -34,12 +36,29 @@ public class LoginToken extends VaultToken {
*/
private final Duration leaseDuration;

private LoginToken(char[] token, Duration duration, boolean renewable) {
@Nullable
private final String accessor;

@Nullable
private final String type;

private LoginToken(char[] token, Duration duration, boolean renewable, @Nullable String accessor,
@Nullable String type) {

super(token);

this.leaseDuration = duration;
this.renewable = renewable;
this.accessor = accessor;
this.type = type;
}

/**
* @return a new {@link LoginTokenBuilder}.
* @since 3.0.2
*/
public static LoginTokenBuilder builder() {
return new LoginTokenBuilder();
}

/**
Expand Down Expand Up @@ -79,7 +98,7 @@ public static LoginToken of(char[] token, Duration leaseDuration) {
Assert.notNull(leaseDuration, "Lease duration must not be null");
Assert.isTrue(!leaseDuration.isNegative(), "Lease duration must not be negative");

return new LoginToken(token, leaseDuration, false);
return new LoginToken(token, leaseDuration, false, null, null);
}

/**
Expand Down Expand Up @@ -110,7 +129,7 @@ public static LoginToken renewable(char[] token, Duration leaseDuration) {
Assert.notNull(leaseDuration, "Lease duration must not be null");
Assert.isTrue(!leaseDuration.isNegative(), "Lease duration must not be negative");

return new LoginToken(token, leaseDuration, true);
return new LoginToken(token, leaseDuration, true, null, null);
}

/**
Expand All @@ -127,14 +146,169 @@ public boolean isRenewable() {
return this.renewable;
}

/**
* @return the token accessor.
* @since 3.0.2
*/
@Nullable
public String getAccessor() {
return accessor;
}

/**
* @return the token type.
* @since 3.0.2
* @see #isBatchToken()
* @see #isServiceToken())
*/
@Nullable
public String getType() {
return type;
}

/**
* @return {@literal true} if the token is a batch token.
* @since 3.0.2
*/
public boolean isBatchToken() {
return "batch".equals(this.type);
}

/**
* @return {@literal true} if the token is a service token.
* @since 3.0.2
*/
public boolean isServiceToken() {
return this.type == null || "service".equals(this.type);
}

@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append(" [renewable=").append(this.renewable);
sb.append(", leaseDuration=").append(this.leaseDuration);
sb.append(", type=").append(this.type);
sb.append(']');
return sb.toString();
}

/**
* Builder for {@link LoginToken}.
*
* @since 3.0.2
*/
public static class LoginTokenBuilder {

@Nullable
private char[] token;

private boolean renewable;

/**
* Duration in seconds.
*/
private Duration leaseDuration = Duration.ZERO;

@Nullable
private String accessor;

@Nullable
private String type;

private LoginTokenBuilder() {
}

/**
* Configure the token value. This is a required builder property. Without this
* property, you cannot {@link #build()} a {@link LoginToken}.
* @param token must not be empty or {@literal null}.
* @return {@code this} {@link LoginTokenBuilder}.
*/
public LoginTokenBuilder token(String token) {

Assert.hasText(token, "Token must not be empty");

return token(token.toCharArray());
}

/**
* Configure the token value. This is a required builder property. Without this
* property, you cannot {@link #build()} a {@link LoginToken}.
* @param token must not be empty or {@literal null}.
* @return {@code this} {@link LoginTokenBuilder}.
*/
public LoginTokenBuilder token(char[] token) {

Assert.notNull(token, "Token must not be null");
Assert.isTrue(token.length > 0, "Token must not be empty");

this.token = token;
return this;
}

/**
* Configure whether the token is renewable.
* @param renewable
* @return {@code this} {@link LoginTokenBuilder}.
*/
public LoginTokenBuilder renewable(boolean renewable) {

this.renewable = renewable;
return this;
}

/**
* Configure the lease duration.
* @param leaseDuration must not be {@literal null}.
* @return {@code this} {@link LoginTokenBuilder}.
*/
public LoginTokenBuilder leaseDuration(Duration leaseDuration) {

Assert.notNull(leaseDuration, "Lease duration must not be empty");

this.leaseDuration = leaseDuration;
return this;
}

/**
* Configure the token accessor.
* @param accessor must not be empty or {@literal null}.
* @return {@code this} {@link LoginTokenBuilder}.
*/
public LoginTokenBuilder accessor(String accessor) {

Assert.hasText(accessor, "Token accessor must not be empty");

this.accessor = accessor;
return this;
}

/**
* Configure the token type.
* @param type must not be empty or {@literal null}.
* @return {@code this} {@link LoginTokenBuilder}.
*/
public LoginTokenBuilder type(String type) {

Assert.hasText(type, "Token type must not be empty");

this.type = type;
return this;
}

/**
* Build a new {@link LoginToken} instance. {@link #token} must be configured.
* @return a new {@link LoginToken} instance.
*/
public LoginToken build() {

Assert.notNull(token, "Token must not be null");

return new LoginToken(Arrays.copyOf(this.token, this.token.length), this.leaseDuration, this.renewable,
this.accessor, this.type);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@
*/
package org.springframework.vault.authentication;

import java.time.Duration;
import java.util.Map;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.client.VaultHttpHeaders;
Expand All @@ -35,7 +33,7 @@
/**
* Adapts tokens created by a {@link ClientAuthentication} to a {@link LoginToken}. Allows
* decoration of a {@link ClientAuthentication} object to perform a self-lookup after
* token retrieval to obtain the remaining TTL and renewability.
* token retrieval to obtain the remaining TTL, renewability, accessor and token type.
* <p>
* Using this adapter decrements the usage counter for the created token.
*
Expand Down Expand Up @@ -77,14 +75,7 @@ static LoginToken augmentWithSelfLookup(RestOperations restOperations, VaultToke

Map<String, Object> data = lookupSelf(restOperations, token);

Boolean renewable = (Boolean) data.get("renewable");
Number ttl = (Number) data.get("ttl");

if (renewable != null && renewable) {
return LoginToken.renewable(token.toCharArray(), getLeaseDuration(ttl));
}

return LoginToken.of(token.toCharArray(), getLeaseDuration(ttl));
return LoginTokenUtil.from(token.toCharArray(), data);
}

private static Map<String, Object> lookupSelf(RestOperations restOperations, VaultToken token) {
Expand All @@ -106,8 +97,4 @@ private static Map<String, Object> lookupSelf(RestOperations restOperations, Vau
}
}

static Duration getLeaseDuration(@Nullable Number ttl) {
return ttl == null ? Duration.ZERO : Duration.ofSeconds(ttl.longValue());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Map;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
* Utility class for {@link LoginToken}.
Expand Down Expand Up @@ -51,26 +52,39 @@ static LoginToken from(Map<String, Object> auth) {
* @return the {@link LoginToken}
* @since 2.0
*/
static LoginToken from(char[] token, Map<String, Object> auth) {
static LoginToken from(char[] token, Map<String, ?> auth) {

Assert.notNull(auth, "Authentication must not be null");

Boolean renewable = (Boolean) auth.get("renewable");
Number leaseDuration = (Number) auth.get("lease_duration");
String accessor = (String) auth.get("accessor");
String type = (String) auth.get("type");

if (leaseDuration == null) {
leaseDuration = (Number) auth.get("ttl");
}

if (renewable != null && renewable) {
return LoginToken.renewable(token, Duration.ofSeconds(leaseDuration.longValue()));
LoginToken.LoginTokenBuilder builder = LoginToken.builder();
builder.token(token);

if (StringUtils.hasText(accessor)) {
builder.accessor(accessor);
}

if (leaseDuration != null) {
return LoginToken.of(token, Duration.ofSeconds(leaseDuration.longValue()));
builder.leaseDuration(Duration.ofSeconds(leaseDuration.longValue()));
}

if (renewable != null) {
builder.renewable(renewable);
}

if (StringUtils.hasText(type)) {
builder.type(type);
}

return LoginToken.of(token);
return builder.build();
}

}
Loading

0 comments on commit 0b376d5

Please sign in to comment.