You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2020/09/09 05:07:51 UTC
[knox] branch master updated: KNOX-2408 - Improved
AliasBasedTokenState service and house-keeping (#371)
This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new fdcc6d4 KNOX-2408 - Improved AliasBasedTokenState service and house-keeping (#371)
fdcc6d4 is described below
commit fdcc6d4c57c72d5ae6d7600094119b8c6957525e
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Wed Sep 9 07:07:32 2020 +0200
KNOX-2408 - Improved AliasBasedTokenState service and house-keeping (#371)
---
.../gateway/config/impl/GatewayConfigImpl.java | 13 ++
.../security/impl/DefaultAliasService.java | 23 +++-
.../security/impl/DefaultKeystoreService.java | 43 +++---
.../services/security/impl/RemoteAliasService.java | 3 +-
.../security/impl/ZookeeperRemoteAliasService.java | 3 +-
.../token/impl/AliasBasedTokenStateService.java | 145 ++++++++++++++-------
.../token/impl/DefaultTokenStateService.java | 118 +++++++----------
.../token/impl/JournalBasedTokenStateService.java | 2 +-
.../token/impl/TokenStateServiceMessages.java | 18 ++-
.../services/security/CryptoServiceTest.java | 5 +
.../impl/RemoteAliasServiceTestProvider.java | 3 +-
.../impl/AliasBasedTokenStateServiceTest.java | 81 ++++++++----
.../impl/DefaultTokenAuthorityServiceTest.java | 10 ++
.../vault/HashicorpVaultAliasService.java | 3 +-
.../apache/knox/gateway/config/GatewayConfig.java | 10 ++
.../services/security/AbstractAliasService.java | 34 +++++
.../gateway/services/security/AliasService.java | 2 +
.../gateway/services/security/KeystoreService.java | 2 +
.../org/apache/knox/gateway/GatewayTestConfig.java | 11 ++
19 files changed, 360 insertions(+), 169 deletions(-)
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index 5329320..f2c33f3 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -114,6 +114,10 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
private static final String CLIENT_AUTH_NEEDED = GATEWAY_CONFIG_FILE_PREFIX + ".client.auth.needed";
private static final String CLIENT_AUTH_WANTED = GATEWAY_CONFIG_FILE_PREFIX + ".client.auth.wanted";
private static final String KEYSTORE_TYPE = GATEWAY_CONFIG_FILE_PREFIX + ".keystore.type";
+ private static final String KEYSTORE_CACHE_LIMIT = GATEWAY_CONFIG_FILE_PREFIX + ".keystore.cache.size.limit";
+ private static final long DEFAULT_KEYSTORE_CACHE_LIMIT = 1000;
+ private static final String KEYSTORE_CACHE_ENTRY_TTL = GATEWAY_CONFIG_FILE_PREFIX + ".keystore.cache.entry.ttl";
+ private static final long DEFAULT_KEYSTORE_CACHE_ENTRY_TTL = 60;
private static final String XFORWARDED_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".xforwarded.enabled";
private static final String EPHEMERAL_DH_KEY_SIZE = GATEWAY_CONFIG_FILE_PREFIX + ".jdk.tls.ephemeralDHKeySize";
private static final String HTTP_CLIENT_MAX_CONNECTION = GATEWAY_CONFIG_FILE_PREFIX + ".httpclient.maxConnections";
@@ -1180,4 +1184,13 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
return getBoolean(KNOX_HOMEPAGE_LOGOUT_ENABLED, false);
}
+ @Override
+ public long getKeystoreCacheSizeLimit() {
+ return getLong(KEYSTORE_CACHE_LIMIT, DEFAULT_KEYSTORE_CACHE_LIMIT);
+ }
+
+ @Override
+ public long getKeystoreCacheEntryTimeToLiveInMinutes() {
+ return getLong(KEYSTORE_CACHE_ENTRY_TTL, DEFAULT_KEYSTORE_CACHE_ENTRY_TTL);
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
index c0a20ec..0496d12 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
@@ -22,6 +22,7 @@ import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -30,14 +31,14 @@ import org.apache.knox.gateway.GatewayMessages;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.KeystoreService;
import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.knox.gateway.services.security.MasterService;
import org.apache.knox.gateway.util.PasswordUtils;
-public class DefaultAliasService implements AliasService {
+public class DefaultAliasService extends AbstractAliasService {
private static final GatewayMessages LOG = MessagesFactory.get( GatewayMessages.class );
private KeystoreService keystoreService;
@@ -194,6 +195,24 @@ public class DefaultAliasService implements AliasService {
return getPasswordFromAliasForCluster(NO_CLUSTER_NAME, alias);
}
+ //Overriding the default behavior as we want to avoid loading the keystore N-times from the file system
+ @Override
+ public Map<String, char[]> getPasswordsForGateway() throws AliasServiceException {
+ final Map<String, char[]> passwordAliasMap = new HashMap<>();
+ try {
+ final KeyStore gatewayCredentialStore = keystoreService.getCredentialStoreForCluster(NO_CLUSTER_NAME);
+ final Enumeration<String> aliases = gatewayCredentialStore.aliases();
+ String alias;
+ while (aliases.hasMoreElements()) {
+ alias = aliases.nextElement();
+ passwordAliasMap.put(alias, keystoreService.getCredentialForCluster(NO_CLUSTER_NAME, alias, gatewayCredentialStore));
+ }
+ } catch (KeystoreServiceException | KeyStoreException e) {
+ e.printStackTrace();
+ }
+ return passwordAliasMap;
+ }
+
@Override
public void generateAliasForGateway(String alias)
throws AliasServiceException {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
index 559ac10..9487dc4 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
@@ -77,9 +77,7 @@ public class DefaultKeystoreService implements KeystoreService {
private static GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
private static GatewayResources RES = ResourcesFactory.get(GatewayResources.class);
- // Let's configure the cache with hard-coded attributes now; we can introduce new gateway configuration later on if
- // needed visible for testing
- final Cache<CacheKey, String> cache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).maximumSize(1000).build();
+ Cache<CacheKey, String> cache;
private GatewayConfig config;
@@ -105,6 +103,10 @@ public class DefaultKeystoreService implements KeystoreService {
throw new ServiceLifecycleException(RES.failedToCreateKeyStoreDirectory(keyStoreDirPath.toString()));
}
}
+
+ if (this.cache == null) {
+ this.cache = Caffeine.newBuilder().expireAfterAccess(config.getKeystoreCacheEntryTimeToLiveInMinutes(), TimeUnit.MINUTES).maximumSize(config.getKeystoreCacheSizeLimit()).build();
+ }
}
@Override
@@ -330,18 +332,10 @@ public class DefaultKeystoreService implements KeystoreService {
KeyStore ks = getCredentialStoreForCluster(clusterName);
if (ks != null) {
try {
- char[] masterSecret = masterService.getMasterSecret();
- Key credentialKey = ks.getKey(alias, masterSecret);
- if (credentialKey != null) {
- byte[] credentialBytes = credentialKey.getEncoded();
- String credentialString = new String(credentialBytes, StandardCharsets.UTF_8);
- credential = credentialString.toCharArray();
- addToCache(clusterName, alias, credentialString);
- }
- } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
+ credential = getCredentialForCluster(clusterName, alias, ks);
+ } catch (KeystoreServiceException e) {
LOG.failedToGetCredentialForCluster(clusterName, e);
}
-
}
}
}
@@ -350,6 +344,22 @@ public class DefaultKeystoreService implements KeystoreService {
}
@Override
+ public char[] getCredentialForCluster(String clusterName, String alias, KeyStore ks) throws KeystoreServiceException {
+ try {
+ char[] credential = null;
+ final Key credentialKey = ks.getKey(alias, masterService.getMasterSecret());
+ if (credentialKey != null) {
+ final String credentialString = new String(credentialKey.getEncoded(), StandardCharsets.UTF_8);
+ credential = credentialString.toCharArray();
+ addToCache(clusterName, alias, credentialString);
+ }
+ return credential;
+ } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) {
+ throw new KeystoreServiceException(e);
+ }
+ }
+
+ @Override
public void removeCredentialForCluster(String clusterName, String alias) throws KeystoreServiceException {
removeCredentialsForCluster(clusterName, Collections.singleton(alias));
}
@@ -406,13 +416,6 @@ public class DefaultKeystoreService implements KeystoreService {
/**
* Called only from within critical sections of other methods above.
*/
- private void removeFromCache(String clusterName, String alias) {
- cache.invalidate(CacheKey.of(clusterName, alias));
- }
-
- /**
- * Called only from within critical sections of other methods above.
- */
private void removeFromCache(String clusterName, Set<String> aliases) {
Set<CacheKey> keys = new HashSet<>();
for (String alias : aliases) {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
index 5f5b0ed..863956e 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
@@ -22,6 +22,7 @@ import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.security.RemoteAliasServiceProvider;
import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.MasterService;
@@ -50,7 +51,7 @@ import java.util.Set;
*
* @since 1.1.0
*/
-public class RemoteAliasService implements AliasService {
+public class RemoteAliasService extends AbstractAliasService {
public static final String REMOTE_ALIAS_SERVICE_TYPE = "type";
private static final GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java
index afbbdf9..c781695 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java
@@ -26,6 +26,7 @@ import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientService;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient;
import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.EncryptionResult;
@@ -45,7 +46,7 @@ import java.util.Set;
/**
* An {@link AliasService} implementation based on zookeeper remote service registry.
*/
-public class ZookeeperRemoteAliasService implements AliasService {
+public class ZookeeperRemoteAliasService extends AbstractAliasService {
public static final String TYPE = "zookeeper";
public static final String PATH_KNOX = "/knox";
public static final String PATH_KNOX_SECURITY = PATH_KNOX + "/security";
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
index 598bd12..4886cef 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
@@ -16,17 +16,6 @@
*/
package org.apache.knox.gateway.services.token.impl;
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.AliasService;
-import org.apache.knox.gateway.services.security.AliasServiceException;
-import org.apache.knox.gateway.services.security.impl.DefaultKeystoreService;
-import org.apache.knox.gateway.services.security.token.UnknownTokenException;
-import org.apache.knox.gateway.services.token.state.JournalEntry;
-import org.apache.knox.gateway.services.token.state.TokenStateJournal;
-import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
-import org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
-
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -36,11 +25,27 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.impl.DefaultKeystoreService;
+import org.apache.knox.gateway.services.security.token.UnknownTokenException;
+import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
+import org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
+import org.apache.knox.gateway.services.token.state.JournalEntry;
+import org.apache.knox.gateway.services.token.state.TokenStateJournal;
/**
* A TokenStateService implementation based on the AliasService.
@@ -57,6 +62,8 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
private final List<TokenState> unpersistedState = new ArrayList<>();
+ private final AtomicBoolean readyForEviction = new AtomicBoolean(false);
+
private TokenStateJournal journal;
private Path gatewayCredentialsFilePath;
@@ -122,6 +129,51 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
statePersistenceInterval,
TimeUnit.SECONDS);
}
+
+ // Loading ALL entries from __gateway-credentials.jceks could be VERY time-consuming (it took a bit more than 19 minutes to load 12k aliases
+ // during my tests).
+ // Therefore, it's safer to do it in a background thread than just make the service start hang until it's finished
+ final ExecutorService gatewayCredentialsLoader = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder().namingPattern("GatewayCredentialsLoader").build());
+ gatewayCredentialsLoader.execute(this::loadGatewayCredentialsOnStartup);
+ }
+
+ private void loadGatewayCredentialsOnStartup() {
+ try {
+ log.loadingGatewayCredentialsOnStartup();
+ final long start = System.currentTimeMillis();
+ final Map<String, char[]> passwordAliasMap = aliasService.getPasswordsForGateway();
+ String alias, tokenId;
+ long expiration, maxLifeTime;
+ int count = 0;
+ for (Map.Entry<String, char[]> passwordAliasMapEntry : passwordAliasMap.entrySet()) {
+ alias = passwordAliasMapEntry.getKey();
+ if (alias.endsWith(TOKEN_MAX_LIFETIME_POSTFIX)) {
+ // This token state service implementation persists two aliases in __gateway-credentials.jceks (see persistTokenState below):
+ // - an alias which maps a token ID to its expiration time
+ // - another alias with '--max' postfix which maps the maximum lifetime of the token identified by the 1st alias
+ // Given this, we should check aliases ending with '--max' and calculate the token ID from this alias.
+ // If all aliases were blindly processed we would end-up handling aliases that were not persisted via this token state service
+ // implementation -> facing error(s) when trying to parse the expiration/maxLifeTime values and irrelevant data would be loaded in the
+ // in-memory collections in the parent class
+ tokenId = alias.substring(0, alias.indexOf(TOKEN_MAX_LIFETIME_POSTFIX));
+ expiration = convertCharArrayToLong(passwordAliasMap.get(tokenId));
+ maxLifeTime = convertCharArrayToLong(passwordAliasMapEntry.getValue());
+ super.updateExpiration(tokenId, expiration);
+ super.setMaxLifetime(tokenId, maxLifeTime);
+ count++;
+ }
+ }
+ log.loadedGatewayCredentialsOnStartup(count * 2, System.currentTimeMillis() - start); //count is multiplied by two: tokenId + tokenId--max
+ } catch (AliasServiceException e) {
+ log.errorWhileLoadingGatewayCredentialsOnStartup(e.getMessage(), e);
+ } finally {
+ readyForEviction.set(true);
+ }
+ }
+
+ @Override
+ protected boolean readyForEviction() {
+ return readyForEviction.get();
}
@Override
@@ -219,7 +271,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
try {
char[] maxLifetimeStr = getPasswordUsingAliasService(tokenId + TOKEN_MAX_LIFETIME_POSTFIX);
if (maxLifetimeStr != null) {
- result = Long.parseLong(new String(maxLifetimeStr));
+ result = convertCharArrayToLong(maxLifetimeStr);
}
} catch (AliasServiceException e) {
log.errorAccessingTokenState(tokenId, e);
@@ -236,6 +288,10 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
return password;
}
+ private long convertCharArrayToLong(char[] charArray) {
+ return Long.parseLong(new String(charArray));
+ }
+
@Override
public long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException {
// Check the in-memory collection first, to avoid costly keystore access when possible
@@ -301,16 +357,13 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
unpersistedToRemove.add(state);
}
}
- for (TokenState state : unpersistedToRemove) {
- unpersistedState.remove(state);
- }
+ unpersistedState.removeAll(unpersistedToRemove);
}
// Add the max lifetime aliases to the list of aliases to remove
Set<String> aliasesToRemove = new HashSet<>(tokenIds);
for (String tokenId : tokenIds) {
aliasesToRemove.add(tokenId + TOKEN_MAX_LIFETIME_POSTFIX);
- log.removingTokenStateAliases(tokenId);
}
if (!aliasesToRemove.isEmpty()) {
@@ -321,9 +374,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.REMOVE_ALIAS);
tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
}
- for (String tokenId : tokenIds) {
- log.removedTokenStateAliases(tokenId);
- }
+ log.removedTokenStateAliases(String.join(", ", tokenIds));
} catch (AliasServiceException e) {
log.failedToRemoveTokenStateAliases(e);
}
@@ -334,37 +385,17 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
@Override
protected void updateExpiration(final String tokenId, long expiration) {
+ //Update in-memory
super.updateExpiration(tokenId, expiration);
- try {
- aliasService.removeAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
- aliasService.addAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId, String.valueOf(expiration));
- if (tokenStateServiceStatistics != null) {
- tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.REMOVE_ALIAS);
- tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.SAVE_ALIAS);
- tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
- }
- } catch (AliasServiceException e) {
- log.failedToUpdateTokenExpiration(tokenId, e);
- }
- }
-
- @Override
- protected List<String> getTokens() {
- List<String> tokenIds = null;
-
- try {
- List<String> allAliases = aliasService.getAliasesForCluster(AliasService.NO_CLUSTER_NAME);
- if (tokenStateServiceStatistics != null) {
- tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.GET_ALIAS);
+ //Update the in-memory representation of unpersisted states that will be processed by the state persistence thread
+ synchronized (unpersistedState) {
+ final Optional<TokenState> tokenStateToRemove = unpersistedState.stream().filter(tokenState -> tokenState.getTokenId().equals(tokenId)).findFirst();
+ if (tokenStateToRemove.isPresent()) {
+ unpersistedState.remove(tokenStateToRemove.get());
}
- // Filter for the token state aliases (exclude aliases ending with TOKEN_MAX_LIFETIME_POSTFIX)
- tokenIds = allAliases.stream().filter(alias -> !alias.endsWith(TOKEN_MAX_LIFETIME_POSTFIX)).collect(Collectors.toList());
- } catch (AliasServiceException e) {
- log.errorAccessingTokenState(e);
+ unpersistedState.add(new TokenExpiration(tokenId, expiration));
}
-
- return (tokenIds != null ? tokenIds : Collections.emptyList());
}
interface TokenState {
@@ -398,6 +429,16 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
public String getAliasValue() {
return String.valueOf(issueTime + maxLifetime);
}
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
}
private static final class TokenExpiration implements TokenState {
@@ -423,6 +464,16 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
public String getAliasValue() {
return String.valueOf(expiration);
}
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
index 850ac07..f58a7f1 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
@@ -19,11 +19,11 @@ package org.apache.knox.gateway.services.token.impl;
import java.lang.management.ManagementFactory;
import java.time.Instant;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -58,9 +58,9 @@ public class DefaultTokenStateService implements TokenStateService {
protected static final TokenStateServiceMessages log = MessagesFactory.get(TokenStateServiceMessages.class);
- private final Map<String, Long> tokenExpirations = new HashMap<>();
+ private final Map<String, Long> tokenExpirations = new ConcurrentHashMap<>();
- private final Map<String, Long> maxTokenLifetimes = new HashMap<>();
+ private final Map<String, Long> maxTokenLifetimes = new ConcurrentHashMap<>();
// Token eviction interval (in seconds)
private long tokenEvictionInterval;
@@ -134,12 +134,8 @@ public class DefaultTokenStateService implements TokenStateService {
long issueTime,
long expiration,
long maxLifetimeDuration) {
- if (!isValidIdentifier(tokenId)) {
- throw new IllegalArgumentException("Token identifier cannot be null.");
- }
- synchronized (tokenExpirations) {
- tokenExpirations.put(tokenId, expiration);
- }
+ validateTokenIdentifier(tokenId);
+ tokenExpirations.put(tokenId, expiration);
setMaxLifetime(tokenId, issueTime, maxLifetimeDuration);
log.addedToken(tokenId, getTimestampDisplay(expiration));
if (tokenStateServiceStatistics != null) {
@@ -181,15 +177,11 @@ public class DefaultTokenStateService implements TokenStateService {
validateToken(tokenId);
}
- long expiration = -1;
- synchronized (tokenExpirations) {
- expiration = tokenExpirations.getOrDefault(tokenId, -1L);
- }
- if (expiration == -1) {
+ final Long expiration = tokenExpirations.get(tokenId);
+ if (expiration == null) {
throw new UnknownTokenException(tokenId);
}
-
- return expiration;
+ return expiration.longValue();
}
@Override
@@ -253,10 +245,12 @@ public class DefaultTokenStateService implements TokenStateService {
return getTokenExpiration(token) <= System.currentTimeMillis();
}
+ protected void setMaxLifetime(final String token, long parsedMaxLifeTime) {
+ maxTokenLifetimes.put(token, parsedMaxLifeTime);
+ }
+
protected void setMaxLifetime(final String token, long issueTime, long maxLifetimeDuration) {
- synchronized (maxTokenLifetimes) {
- maxTokenLifetimes.put(token, issueTime + maxLifetimeDuration);
- }
+ maxTokenLifetimes.put(token, issueTime + maxLifetimeDuration);
}
/**
@@ -264,19 +258,11 @@ public class DefaultTokenStateService implements TokenStateService {
* @return false, if the service has previously stored the specified token; Otherwise, true.
*/
protected boolean isUnknown(final String token) {
- boolean isUnknown;
-
- synchronized (tokenExpirations) {
- isUnknown = !(tokenExpirations.containsKey(token));
- }
-
- return isUnknown;
+ return !tokenExpirations.containsKey(token);
}
protected void updateExpiration(final String tokenId, long expiration) {
- synchronized (tokenExpirations) {
- tokenExpirations.put(tokenId, expiration);
- }
+ tokenExpirations.put(tokenId, expiration);
}
protected void removeToken(final String tokenId) throws UnknownTokenException {
@@ -296,15 +282,9 @@ public class DefaultTokenStateService implements TokenStateService {
}
private void removeTokenState(final Set<String> tokenIds) {
- synchronized (tokenExpirations) {
- tokenExpirations.keySet().removeAll(tokenIds);
- }
- synchronized (maxTokenLifetimes) {
- maxTokenLifetimes.keySet().removeAll(tokenIds);
- }
- for (String tokenId : tokenIds) {
- log.removedTokenState(tokenId);
- }
+ tokenExpirations.keySet().removeAll(tokenIds);
+ maxTokenLifetimes.keySet().removeAll(tokenIds);
+ log.removedTokenState(String.join(", ", tokenIds));
}
protected boolean hasRemainingRenewals(final String tokenId, long renewInterval) {
@@ -313,15 +293,13 @@ public class DefaultTokenStateService implements TokenStateService {
}
protected long getMaxLifetime(final String tokenId) {
- long result;
- synchronized (maxTokenLifetimes) {
- result = maxTokenLifetimes.getOrDefault(tokenId, 0L);
- }
- return result;
+ return maxTokenLifetimes.getOrDefault(tokenId, 0L);
}
- protected boolean isValidIdentifier(final String tokenId) {
- return tokenId != null && !tokenId.isEmpty();
+ private void validateTokenIdentifier(final String tokenId) {
+ if (tokenId == null || tokenId.isEmpty()) {
+ throw new IllegalArgumentException("Token identifier cannot be null or empty.");
+ }
}
/**
@@ -333,9 +311,7 @@ public class DefaultTokenStateService implements TokenStateService {
* @throws UnknownTokenException if the specified token in valid, but not known to the service.
*/
protected void validateToken(final String tokenId) throws IllegalArgumentException, UnknownTokenException {
- if (!isValidIdentifier(tokenId)) {
- throw new IllegalArgumentException("Token identifier cannot be null.");
- }
+ validateTokenIdentifier(tokenId);
// First, make sure the token is one we know about
if (isUnknown(tokenId)) {
@@ -344,7 +320,7 @@ public class DefaultTokenStateService implements TokenStateService {
}
}
- protected String getTimestampDisplay(long timestamp) {
+ private String getTimestampDisplay(long timestamp) {
return Instant.ofEpochMilli(timestamp).toString();
}
@@ -352,28 +328,36 @@ public class DefaultTokenStateService implements TokenStateService {
* Method that deletes expired tokens based on the token timestamp.
*/
protected void evictExpiredTokens() {
- Set<String> tokensToEvict = new HashSet<>();
-
- for (final String tokenId : getTokens()) {
- try {
- if (needsEviction(tokenId)) {
- log.evictToken(tokenId);
- tokensToEvict.add(tokenId); // Add the token to the set of tokens to evict
+ if (readyForEviction()) {
+ final Set<String> tokensToEvict = new HashSet<>();
+
+ for (final String tokenId : getTokenIds()) {
+ try {
+ if (needsEviction(tokenId)) {
+ log.evictToken(tokenId);
+ tokensToEvict.add(tokenId); // Add the token to the set of tokens to evict
+ }
+ } catch (final Exception e) {
+ log.failedExpiredTokenEviction(tokenId, e);
}
- } catch (final Exception e) {
- log.failedExpiredTokenEviction(tokenId, e);
}
- }
- if (!tokensToEvict.isEmpty()) {
- try {
- removeTokens(tokensToEvict);
- } catch (UnknownTokenException e) {
- log.failedExpiredTokenEviction(e);
+ if (!tokensToEvict.isEmpty()) {
+ try {
+ removeTokens(tokensToEvict);
+ } catch (UnknownTokenException e) {
+ log.failedExpiredTokenEviction(e);
+ }
}
+ } else {
+ log.skipEviction();
}
}
+ protected boolean readyForEviction() {
+ return true;
+ }
+
/**
* Method that checks if a token's state is a candidate for eviction.
*
@@ -393,10 +377,8 @@ public class DefaultTokenStateService implements TokenStateService {
*
* @return
*/
- protected List<String> getTokens() {
- synchronized (tokenExpirations) {
- return tokenExpirations.keySet().stream().collect(Collectors.toList());
- }
+ private List<String> getTokenIds() {
+ return tokenExpirations.keySet().stream().collect(Collectors.toList());
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java
index 39feb7e..949a29a 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java
@@ -42,7 +42,7 @@ public class JournalBasedTokenStateService extends DefaultTokenStateService {
// Initialize the token state journal
journal = TokenStateJournalFactory.create(config);
- // Load any persisted journal entries, and add them to the unpersisted state collection
+ // Load any persisted journal entries, and add them to the in-memory collection
List<JournalEntry> entries = journal.get();
for (JournalEntry entry : entries) {
String id = entry.getTokenId();
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
index 686d4bd..a83f638 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
@@ -33,8 +33,8 @@ public interface TokenStateServiceMessages {
@Message(level = MessageLevel.DEBUG, text = "Revoked token {0}")
void revokedToken(String tokenId);
- @Message(level = MessageLevel.DEBUG, text = "Removed state for token {0}")
- void removedTokenState(String tokenId);
+ @Message(level = MessageLevel.DEBUG, text = "Removed state for tokens {0}")
+ void removedTokenState(String tokenIds);
@Message(level = MessageLevel.ERROR, text = "Unknown token {0}")
void unknownToken(String tokenId);
@@ -67,6 +67,9 @@ public interface TokenStateServiceMessages {
@Message(level = MessageLevel.ERROR, text = "Failed to remove token state aliases : {0}")
void failedToRemoveTokenStateAliases(@StackTrace(level = MessageLevel.DEBUG) Exception e);
+ @Message(level = MessageLevel.DEBUG, text = "Skipping expired token eviction")
+ void skipEviction();
+
@Message(level = MessageLevel.ERROR, text = "Failed to evict expired token {0} : {1}")
void failedExpiredTokenEviction(String tokenId, @StackTrace(level = MessageLevel.DEBUG) Exception e);
@@ -95,7 +98,7 @@ public interface TokenStateServiceMessages {
void removingTokenStateAliases(String tokenId);
@Message(level = MessageLevel.INFO, text = "Removed token state aliases for {0}")
- void removedTokenStateAliases(String tokenId);
+ void removedTokenStateAliases(String tokenIds);
@Message(level = MessageLevel.DEBUG, text = "Loading peristed token state journal entries")
void loadingPersistedJournalEntries();
@@ -136,4 +139,13 @@ public interface TokenStateServiceMessages {
@Message(level = MessageLevel.ERROR, text = "Failed to remove the token state journal entries : {0}")
void failedToRemoveJournalEntries(@StackTrace(level = MessageLevel.DEBUG) Exception e);
+ @Message(level = MessageLevel.INFO, text = "Loading Gateway credentials on startup...")
+ void loadingGatewayCredentialsOnStartup();
+
+ @Message(level = MessageLevel.INFO, text = "Loaded {0} Gateway credentials in {1} milliseonds")
+ void loadedGatewayCredentialsOnStartup(int count, long duration);
+
+ @Message(level = MessageLevel.ERROR, text = "Error while loading Gateway credentials on startup: {0}")
+ void errorWhileLoadingGatewayCredentialsOnStartup(String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/CryptoServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/CryptoServiceTest.java
index dfcab8b..a87263b 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/CryptoServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/CryptoServiceTest.java
@@ -92,6 +92,11 @@ public class CryptoServiceTest {
}
@Override
+ public Map<String, char[]> getPasswordsForGateway() throws AliasServiceException {
+ return null;
+ }
+
+ @Override
public void generateAliasForGateway(String alias) {
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java
index 23560df..e99aff3 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java
@@ -20,6 +20,7 @@ package org.apache.knox.gateway.services.security.impl;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.security.RemoteAliasServiceProvider;
import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.MasterService;
@@ -49,7 +50,7 @@ public class RemoteAliasServiceTestProvider implements RemoteAliasServiceProvide
return new TestAliasService();
}
- private class TestAliasService implements AliasService {
+ private class TestAliasService extends AbstractAliasService {
private final Map<String, Map<String, String>> aliases = new HashMap<>();
private GatewayConfig config;
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
index 2b589fb..2c0fb6c 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
@@ -18,6 +18,7 @@ package org.apache.knox.gateway.services.token.impl;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.token.TokenStateService;
@@ -26,6 +27,7 @@ import org.apache.knox.gateway.services.token.state.JournalEntry;
import org.apache.knox.gateway.services.token.state.TokenStateJournal;
import org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
import org.easymock.EasyMock;
+import org.junit.Ignore;
import org.junit.Test;
import java.lang.reflect.Field;
@@ -92,6 +94,9 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
aliasService.removeAliasesForCluster(anyString(), anyObject());
EasyMock.expectLastCall().andVoid().once();
+ //expecting this call when loading credentials from the keystore on startup
+ EasyMock.expect(aliasService.getPasswordsForGateway()).andReturn(Collections.emptyMap()).anyTimes();
+
EasyMock.replay(aliasService);
AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
@@ -145,14 +150,17 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
aliasService.removeAliasesForCluster(anyString(), anyObject());
EasyMock.expectLastCall().andVoid().once();
+ //expecting this call when loading credentials from the keystore on startup
+ EasyMock.expect(aliasService.getPasswordsForGateway()).andReturn(Collections.emptyMap()).anyTimes();
+
EasyMock.replay(aliasService);
AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
tss.setAliasService(aliasService);
initTokenStateService(tss);
- Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
- Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
+ Map<String, Long> tokenExpirations = getTokenExpirationsField(tss, false);
+ Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss, false);
final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
final long maxTokenLifetime = evictionInterval * 3;
@@ -195,7 +203,8 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
* Verify that the token state reaper includes token state which has not been cached, so it's not left in the keystore
* forever.
*/
- @Test
+ @Ignore("I'm not sure if this is a valid use case since we have everything in the cache when eviction takes place")
+ @Test()
public void testTokenEvictionIncludesUncachedAliases() throws Exception {
final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
final long maxTokenLifetime = evictionInterval * 3;
@@ -234,10 +243,12 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
EasyMock.expectLastCall().andVoid().once();
aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, uncachedTokenId);
EasyMock.expectLastCall().andReturn(String.valueOf(uncachedTokenExpiration).toCharArray()).once();
+ //expecting this call when loading credentials from the keystore on startup
+ EasyMock.expect(aliasService.getPasswordsForGateway()).andReturn(Collections.emptyMap()).anyTimes();
EasyMock.replay(aliasService);
- AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
+ AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
initTokenStateService(tss);
@@ -283,9 +294,12 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
aliasService.addAliasesForCluster(anyString(), anyObject());
EasyMock.expectLastCall().once(); // Expecting this during shutdown
+ //expecting this call when loading credentials from the keystore on startup
+ EasyMock.expect(aliasService.getPasswordsForGateway()).andReturn(Collections.emptyMap()).anyTimes();
+
EasyMock.replay(aliasService);
- AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
+ AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
initTokenStateService(tss);
@@ -337,17 +351,17 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
@Test
public void testUpdateExpirationUsesCache() throws Exception {
- AliasService aliasService = EasyMock.createMock(AliasService.class);
- aliasService.addAliasForCluster(anyString(), anyString(), anyString());
- EasyMock.expectLastCall().andVoid().atLeastOnce();
- aliasService.removeAliasForCluster(anyString(), anyObject());
- EasyMock.expectLastCall().andVoid().atLeastOnce();
+ final AliasService aliasService = EasyMock.createMock(AliasService.class);
+ // Neither addAliasForCluster nor removeAliasForCluster should be called because updating expiration should happen in memory and let the
+ // background persistence job done its job
aliasService.addAliasesForCluster(anyString(), anyObject());
EasyMock.expectLastCall().andVoid().once(); // Expecting this during shutdown
+ //expecting this call when loading credentials from the keystore on startup
+ EasyMock.expect(aliasService.getPasswordsForGateway()).andReturn(Collections.emptyMap()).anyTimes();
EasyMock.replay(aliasService);
- AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
+ AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
initTokenStateService(tss);
@@ -385,9 +399,7 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
// Invoking with true/false validation flags as it should not affect if values are coming from the cache
int count = 0;
for (String tokenId : tokenExpirations.keySet()) {
- assertEquals("Expected the cached expiration to have been updated.",
- updatedExpiration,
- tss.getTokenExpiration(tokenId, count++ % 2 == 0));
+ assertEquals("Expected the cached expiration to have been updated.", updatedExpiration, tss.getTokenExpiration(tokenId, count++ % 2 == 0));
}
} finally {
@@ -405,17 +417,19 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
EasyMock.expectLastCall().andReturn(Collections.emptyList()).anyTimes();
aliasService.addAliasesForCluster(anyString(), anyObject());
EasyMock.expectLastCall().once();
+
+ //expecting this call when loading credentials from the keystore on startup
+ EasyMock.expect(aliasService.getPasswordsForGateway()).andReturn(Collections.emptyMap()).anyTimes();
+
EasyMock.replay(aliasService);
tokenStatePersistenceInterval = 1L; // Override the persistence interval for this test
- AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
+ AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
initTokenStateService(tss);
- Field maxTokenLifetimesField = tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
- maxTokenLifetimesField.setAccessible(true);
- Map<String, Long> maxTokenLifetimes = (Map<String, Long>) maxTokenLifetimesField.get(tss);
+ Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
Path journalDir = Paths.get(getGatewaySecurityDir(), "token-state");
@@ -500,7 +514,7 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24));
}
- AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
+ AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
// Initialize the service, and presumably load the previously-persisted journal entries
@@ -576,7 +590,7 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
String.valueOf(System.currentTimeMillis()),
"invalidLongValue"));
- AliasBasedTokenStateService tss = new AliasBasedTokenStateService();
+ AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
// Initialize the service, and presumably load the previously-persisted journal entries
@@ -614,7 +628,7 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
/**
* A dumbed-down AliasService implementation for testing purposes only.
*/
- private static final class TestAliasService implements AliasService {
+ private static final class TestAliasService extends AbstractAliasService {
private final Map<String, Map<String, String>> clusterAliases= new HashMap<>();
@@ -759,20 +773,29 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
}
private static Map<String, Long> getTokenExpirationsField(TokenStateService tss) throws Exception {
- Field tokenExpirationsField = tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
+ return getTokenExpirationsField(tss, true);
+ }
+ private static Map<String, Long> getTokenExpirationsField(TokenStateService tss, boolean fromGrandParent) throws Exception {
+ final Class<TokenStateService> clazz = (Class<TokenStateService>) (fromGrandParent ? tss.getClass().getSuperclass().getSuperclass() : tss.getClass().getSuperclass());
+ final Field tokenExpirationsField = clazz.getDeclaredField("tokenExpirations");
tokenExpirationsField.setAccessible(true);
return (Map<String, Long>) tokenExpirationsField.get(tss);
}
private static Map<String, Long> getMaxTokenLifetimesField(TokenStateService tss) throws Exception {
- Field maxTokenLifetimesField = tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
+ return getMaxTokenLifetimesField(tss, true);
+ }
+
+ private static Map<String, Long> getMaxTokenLifetimesField(TokenStateService tss, boolean fromGrandParent) throws Exception {
+ final Class<TokenStateService> clazz = (Class<TokenStateService>) (fromGrandParent ? tss.getClass().getSuperclass().getSuperclass() : tss.getClass().getSuperclass());
+ Field maxTokenLifetimesField = clazz.getDeclaredField("maxTokenLifetimes");
maxTokenLifetimesField.setAccessible(true);
return (Map<String, Long>) maxTokenLifetimesField.get(tss);
}
private static List<AliasBasedTokenStateService.TokenState> getUnpersistedStateField(TokenStateService tss)
throws Exception {
- Field unpersistedStateField = tss.getClass().getDeclaredField("unpersistedState");
+ Field unpersistedStateField = tss.getClass().getSuperclass().getDeclaredField("unpersistedState");
unpersistedStateField.setAccessible(true);
return (List<AliasBasedTokenStateService.TokenState>) unpersistedStateField.get(tss);
@@ -817,4 +840,14 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
return tokenId + "," + issueTime + "," + expiration + "," + maxLifetime;
}
}
+
+ private static class NoEvictionAliasBasedTokenStateService extends AliasBasedTokenStateService {
+
+ @Override
+ protected boolean readyForEviction() {
+ return false;
+ }
+
+ }
+
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
index 84817aa..83a663e 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
@@ -353,6 +353,8 @@ public class DefaultTokenAuthorityServiceTest {
EasyMock.expect(config.getSigningKeystoreType()).andReturn("jks").atLeastOnce();
EasyMock.expect(config.getSigningKeystorePasswordAlias()).andReturn(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS).anyTimes();
EasyMock.expect(config.getSigningKeyAlias()).andReturn("server").anyTimes();
+ EasyMock.expect(config.getKeystoreCacheEntryTimeToLiveInMinutes()).andReturn(0L).anyTimes();
+ EasyMock.expect(config.getKeystoreCacheSizeLimit()).andReturn(0L).anyTimes();
MasterService ms = EasyMock.createMock(MasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("horton".toCharArray()).atLeastOnce();
@@ -392,6 +394,8 @@ public class DefaultTokenAuthorityServiceTest {
EasyMock.expect(config.getSigningKeystorePath()).andReturn(basedir + "/target/test-classes/keystores/missing-server-keystore.jks").atLeastOnce();
EasyMock.expect(config.getSigningKeystoreType()).andReturn("jks").atLeastOnce();
EasyMock.expect(config.getSigningKeystorePasswordAlias()).andReturn(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS).anyTimes();
+ EasyMock.expect(config.getKeystoreCacheEntryTimeToLiveInMinutes()).andReturn(0L).anyTimes();
+ EasyMock.expect(config.getKeystoreCacheSizeLimit()).andReturn(0L).anyTimes();
MasterService ms = EasyMock.createMock(MasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("horton".toCharArray()).atLeastOnce();
@@ -436,6 +440,8 @@ public class DefaultTokenAuthorityServiceTest {
EasyMock.expect(config.getSigningKeystoreType()).andReturn("jks").atLeastOnce();
EasyMock.expect(config.getSigningKeystorePasswordAlias()).andReturn(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS).anyTimes();
EasyMock.expect(config.getSigningKeyAlias()).andReturn("server").anyTimes();
+ EasyMock.expect(config.getKeystoreCacheEntryTimeToLiveInMinutes()).andReturn(0L).anyTimes();
+ EasyMock.expect(config.getKeystoreCacheSizeLimit()).andReturn(0L).anyTimes();
MasterService ms = EasyMock.createMock(MasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("invalid_password".toCharArray()).atLeastOnce();
@@ -480,6 +486,8 @@ public class DefaultTokenAuthorityServiceTest {
EasyMock.expect(config.getSigningKeystoreType()).andReturn("jks").atLeastOnce();
EasyMock.expect(config.getSigningKeystorePasswordAlias()).andReturn(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS).anyTimes();
EasyMock.expect(config.getSigningKeyAlias()).andReturn("invalid_key").anyTimes();
+ EasyMock.expect(config.getKeystoreCacheEntryTimeToLiveInMinutes()).andReturn(0L).anyTimes();
+ EasyMock.expect(config.getKeystoreCacheSizeLimit()).andReturn(0L).anyTimes();
MasterService ms = EasyMock.createMock(MasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("horton".toCharArray()).atLeastOnce();
@@ -524,6 +532,8 @@ public class DefaultTokenAuthorityServiceTest {
EasyMock.expect(config.getSigningKeystoreType()).andReturn("jks").atLeastOnce();
EasyMock.expect(config.getSigningKeystorePasswordAlias()).andReturn(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS).anyTimes();
EasyMock.expect(config.getSigningKeyAlias()).andReturn("server").anyTimes();
+ EasyMock.expect(config.getKeystoreCacheEntryTimeToLiveInMinutes()).andReturn(0L).anyTimes();
+ EasyMock.expect(config.getKeystoreCacheSizeLimit()).andReturn(0L).anyTimes();
MasterService ms = EasyMock.createMock(MasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("horton".toCharArray()).atLeastOnce();
diff --git a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java
index 05bc3dd..905d8d3 100644
--- a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java
+++ b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java
@@ -20,6 +20,7 @@ package org.apache.knox.gateway.backend.hashicorp.vault;
import org.apache.knox.gateway.backend.hashicorp.vault.authentication.HashicorpVaultClientAuthenticationProvider;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.util.PasswordUtils;
@@ -40,7 +41,7 @@ import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
-public class HashicorpVaultAliasService implements AliasService {
+public class HashicorpVaultAliasService extends AbstractAliasService {
public static final String TYPE = "hashicorp.vault";
public static final String VAULT_CONFIG_PREFIX = "hashicorp.vault.";
public static final String VAULT_ADDRESS_KEY = VAULT_CONFIG_PREFIX + "address";
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index 91585e4..5955624 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -704,4 +704,14 @@ public interface GatewayConfig {
* @return the whether logout from the knox home page is enabled or not
*/
boolean homePageLogoutEnabled();
+
+ /**
+ * @return the maximum number of cache entries where keystore entries are stored
+ */
+ long getKeystoreCacheSizeLimit();
+
+ /**
+ * @return the time - in minutes - an entry should be live (i.e. must not expire) in keystore cache
+ */
+ long getKeystoreCacheEntryTimeToLiveInMinutes();
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AbstractAliasService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AbstractAliasService.java
new file mode 100644
index 0000000..2637420
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AbstractAliasService.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.knox.gateway.services.security;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractAliasService implements AliasService {
+
+ @Override
+ public Map<String, char[]> getPasswordsForGateway() throws AliasServiceException {
+ final Map<String, char[]> passwordAliasMap = new HashMap<>();
+ for (String alias : getAliasesForCluster(NO_CLUSTER_NAME)) {
+ passwordAliasMap.put(alias, getPasswordFromAliasForGateway(alias));
+ }
+ return passwordAliasMap;
+ }
+
+}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java
index 730d932..39e3028 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java
@@ -54,6 +54,8 @@ public interface AliasService extends Service {
char[] getPasswordFromAliasForGateway(String alias)
throws AliasServiceException;
+ Map<String, char[]> getPasswordsForGateway() throws AliasServiceException;
+
char[] getGatewayIdentityPassphrase() throws AliasServiceException;
char[] getGatewayIdentityKeystorePassword() throws AliasServiceException;
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/KeystoreService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/KeystoreService.java
index 68fb637..b2cb717 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/KeystoreService.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/KeystoreService.java
@@ -76,5 +76,7 @@ public interface KeystoreService extends Service {
char[] getCredentialForCluster(String clusterName, String alias) throws KeystoreServiceException;
+ char[] getCredentialForCluster(String clusterName, String alias, KeyStore ks) throws KeystoreServiceException;
+
String getKeystorePath();
}
diff --git a/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java b/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index 558bb29..6b5b466 100644
--- a/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++ b/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -822,4 +822,15 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig {
public boolean homePageLogoutEnabled() {
return false;
}
+
+ @Override
+ public long getKeystoreCacheSizeLimit() {
+ return 0;
+ }
+
+ @Override
+ public long getKeystoreCacheEntryTimeToLiveInMinutes() {
+ return 0;
+ }
+
}