You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2020/02/29 01:40:32 UTC
[knox] branch master updated: KNOX-2212 - Token permissiveness
(#274)
This is an automated email from the ASF dual-hosted git repository.
more 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 04ec47e KNOX-2212 - Token permissiveness (#274)
04ec47e is described below
commit 04ec47e085d09326d8908d7b2c1ebecc28c9d8a9
Author: Sandeep Moré <mo...@gmail.com>
AuthorDate: Fri Feb 28 20:40:25 2020 -0500
KNOX-2212 - Token permissiveness (#274)
---
.../gateway/config/impl/GatewayConfigImpl.java | 11 ++++
.../token/impl/AliasBasedTokenStateService.java | 13 ++++-
.../token/impl/DefaultTokenStateService.java | 40 +++++++++++++-
.../token/impl/TokenStateServiceMessages.java | 6 ++
.../token/impl/DefaultTokenStateServiceTest.java | 64 +++++++++++++++++++++-
.../apache/knox/gateway/config/GatewayConfig.java | 5 ++
.../org/apache/knox/gateway/GatewayTestConfig.java | 8 +++
7 files changed, 141 insertions(+), 6 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 117165a..5a4031a 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
@@ -249,8 +249,10 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
private static final String KNOX_TOKEN_EVICTION_INTERVAL = GATEWAY_CONFIG_FILE_PREFIX + ".knox.token.eviction.interval";
private static final String KNOX_TOKEN_EVICTION_GRACE_PERIOD = GATEWAY_CONFIG_FILE_PREFIX + ".knox.token.eviction.grace.period";
+ private static final String KNOX_TOKEN_PERMISSIVE_VALIDATION_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".knox.token.permissive.validation";
private static final long KNOX_TOKEN_EVICTION_INTERVAL_DEFAULT = TimeUnit.MINUTES.toSeconds(5);
private static final long KNOX_TOKEN_EVICTION_GRACE_PERIOD_DEFAULT = TimeUnit.MINUTES.toSeconds(5);
+ private static final boolean KNOX_TOKEN_PERMISSIVE_VALIDATION_ENABLED_DEFAULT = false;
private static final String KNOX_HOMEPAGE_HIDDEN_TOPOLOGIES = "knox.homepage.hidden.topologies";
private static final Set<String> KNOX_HOMEPAGE_HIDDEN_TOPOLOGIES_DEFAULT = new HashSet<>(Arrays.asList("admin", "manager", "knoxsso", "metadata"));
@@ -1141,4 +1143,13 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
final Set<String> hiddenTopologies = new HashSet<>(getStringCollection(KNOX_HOMEPAGE_HIDDEN_TOPOLOGIES));
return hiddenTopologies == null || hiddenTopologies.isEmpty() ? KNOX_HOMEPAGE_HIDDEN_TOPOLOGIES_DEFAULT : hiddenTopologies;
}
+
+ /**
+ * @return returns whether know token permissive failure is enabled
+ */
+ @Override
+ public boolean isKnoxTokenPermissiveValidationEnabled() {
+ return getBoolean(KNOX_TOKEN_PERMISSIVE_VALIDATION_ENABLED,
+ KNOX_TOKEN_PERMISSIVE_VALIDATION_ENABLED_DEFAULT);
+ }
}
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 9f6fa1a..ab900e5 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
@@ -92,9 +92,16 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
@Override
public long getTokenExpiration(final String token) throws UnknownTokenException {
long expiration = 0;
-
- validateToken(token);
-
+ try {
+ validateToken(token);
+ } catch (final UnknownTokenException e) {
+ /* if token permissiveness is enabled we check JWT token expiration when the token state is unknown */
+ if (permissiveValidationEnabled && getJWTTokenExpiration(token).isPresent()) {
+ return getJWTTokenExpiration(token).getAsLong();
+ } else {
+ throw e;
+ }
+ }
try {
char[] expStr = aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, token);
if (expStr != null) {
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 13b728c..f0f5ca7 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
@@ -22,12 +22,16 @@ import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.TokenUtils;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import java.text.ParseException;
import java.time.Instant;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.OptionalLong;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -54,6 +58,8 @@ public class DefaultTokenStateService implements TokenStateService {
private long tokenEvictionInterval;
/* grace period (in seconds) after which an expired token should be evicted */
private long tokenEvictionGracePeriod;
+ /* should knox token fail permissively */
+ protected boolean permissiveValidationEnabled;
private final ScheduledExecutorService evictionScheduler = Executors.newScheduledThreadPool(1);
@@ -62,6 +68,7 @@ public class DefaultTokenStateService implements TokenStateService {
public void init(final GatewayConfig config, final Map<String, String> options) throws ServiceLifecycleException {
tokenEvictionInterval = config.getKnoxTokenEvictionInterval();
tokenEvictionGracePeriod = config.getKnoxTokenEvictionGracePeriod();
+ permissiveValidationEnabled = config.isKnoxTokenPermissiveValidationEnabled();
}
@Override
@@ -119,8 +126,17 @@ public class DefaultTokenStateService implements TokenStateService {
public long getTokenExpiration(final String token) throws UnknownTokenException {
long expiration;
- validateToken(token);
+ try {
+ validateToken(token);
+ } catch (final UnknownTokenException e) {
+ /* if token permissiveness is enabled we check JWT token expiration when the token state is unknown */
+ if (permissiveValidationEnabled && getJWTTokenExpiration(token).isPresent()) {
+ return getJWTTokenExpiration(token).getAsLong();
+ } else {
+ throw e;
+ }
+ }
synchronized (tokenExpirations) {
expiration = tokenExpirations.get(token);
}
@@ -310,4 +326,26 @@ public class DefaultTokenStateService implements TokenStateService {
return tokenExpirations.keySet().stream().collect(Collectors.toList());
}
+ /**
+ * A function that returns the JWT token expiration. This is only called when
+ * gateway.knox.token.permissive.validation property is set to true.
+ * @param token token to be verified and saved
+ */
+ protected OptionalLong getJWTTokenExpiration(final String token) {
+ JWT jwt;
+ try {
+ jwt = new JWTToken(token);
+ } catch (final ParseException e) {
+ log.errorParsingToken(e.toString());
+ return OptionalLong.empty();
+ }
+ final Date expires = jwt.getExpiresDate();
+ if (expires == null) {
+ log.jwtTokenExpiry(TokenUtils.getTokenDisplayText(token), "-1");
+ return OptionalLong.of(-1);
+ }
+ log.jwtTokenExpiry(TokenUtils.getTokenDisplayText(token), expires.toString());
+ return OptionalLong.of(expires.getTime());
+ }
+
}
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 c7702d7..318cced 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
@@ -63,4 +63,10 @@ public interface TokenStateServiceMessages {
@Message(level = MessageLevel.ERROR, text = "Error occurred evicting token {0}")
void errorEvictingTokens(@StackTrace(level = MessageLevel.DEBUG) Exception e);
+ @Message(level = MessageLevel.ERROR, text = "Error occurred while parsing JWT token, cause: {0}")
+ void errorParsingToken(String cause);
+
+ @Message(level = MessageLevel.DEBUG, text = "Permissive validation for token is enabled, expiration for token {0} is {1}")
+ void jwtTokenExpiry(String tokenDisplayText, String expiration);
+
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
index 44eaa19..28ac7b8 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
@@ -16,15 +16,22 @@
*/
package org.apache.knox.gateway.services.token.impl;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.token.TokenStateService;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
import org.apache.knox.gateway.services.security.token.TokenUtils;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
import org.apache.knox.gateway.services.security.token.impl.JWTToken;
import org.easymock.EasyMock;
+import org.junit.BeforeClass;
import org.junit.Test;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@@ -38,6 +45,16 @@ import static org.junit.Assert.fail;
public class DefaultTokenStateServiceTest {
private static long EVICTION_INTERVAL = 2L;
+ private static RSAPrivateKey privateKey;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(2048);
+
+ KeyPair kp = kpg.genKeyPair();
+ privateKey = (RSAPrivateKey) kp.getPrivate();
+ }
@Test
public void testGetExpiration() throws Exception {
@@ -185,6 +202,32 @@ public class DefaultTokenStateServiceTest {
}
}
+ @Test
+ public void testTokenPermissiveness() throws UnknownTokenException {
+ final long expiry = System.currentTimeMillis() + 300000;
+ final JWT token = getJWTToken(expiry);
+ TokenStateService tss = new DefaultTokenStateService();
+ try {
+ tss.init(createMockGatewayConfig(true), Collections.emptyMap());
+ } catch (ServiceLifecycleException e) {
+ fail("Error creating TokenStateService: " + e.getMessage());
+ }
+ assertEquals(expiry/1000, tss.getTokenExpiration(token.toString())/1000);
+ }
+
+ @Test
+ public void testTokenPermissivenessNoExpiry() throws UnknownTokenException {
+ final JWT token = getJWTToken(-1);
+ TokenStateService tss = new DefaultTokenStateService();
+ try {
+ tss.init(createMockGatewayConfig(true), Collections.emptyMap());
+ } catch (ServiceLifecycleException e) {
+ fail("Error creating TokenStateService: " + e.getMessage());
+ }
+
+ assertEquals(-1L, tss.getTokenExpiration(token.toString()));
+ }
+
protected static JWTToken createMockToken(final long expiration) {
return createMockToken("abcD1234eFGHIJKLmnoPQRSTUVwXYz", expiration);
}
@@ -197,18 +240,19 @@ public class DefaultTokenStateServiceTest {
return token;
}
- protected static GatewayConfig createMockGatewayConfig() {
+ protected static GatewayConfig createMockGatewayConfig(boolean tokenPermissiveness) {
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
/* configure token eviction time to be 5 secs for test */
EasyMock.expect(config.getKnoxTokenEvictionInterval()).andReturn(EVICTION_INTERVAL).anyTimes();
EasyMock.expect(config.getKnoxTokenEvictionGracePeriod()).andReturn(0L).anyTimes();
+ EasyMock.expect(config.isKnoxTokenPermissiveValidationEnabled()).andReturn(tokenPermissiveness).anyTimes();
EasyMock.replay(config);
return config;
}
protected void initTokenStateService(TokenStateService tss) {
try {
- tss.init(createMockGatewayConfig(), Collections.emptyMap());
+ tss.init(createMockGatewayConfig(false), Collections.emptyMap());
} catch (ServiceLifecycleException e) {
fail("Error creating TokenStateService: " + e.getMessage());
}
@@ -220,4 +264,20 @@ public class DefaultTokenStateServiceTest {
return tss;
}
+ /* create a test JWT token */
+ protected JWT getJWTToken(final long expiry) {
+ String[] claims = new String[4];
+ claims[0] = "KNOXSSO";
+ claims[1] = "john.doe@example.com";
+ claims[2] = "https://login.example.com";
+ if(expiry > 0) {
+ claims[3] = Long.toString(expiry);
+ }
+ JWT token = new JWTToken("RS256", claims);
+ // Sign the token
+ JWSSigner signer = new RSASSASigner(privateKey);
+ token.sign(signer);
+ return token;
+ }
+
}
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 d6e40a0..48330e3 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
@@ -675,4 +675,9 @@ public interface GatewayConfig {
* @return the list of topologies that should be hidden on Knox homepage
*/
Set<String> getHiddenTopologiesOnHomepage();
+
+ /**
+ * @return returns whether know token permissive validation is enabled
+ */
+ boolean isKnoxTokenPermissiveValidationEnabled();
}
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 a7774bc..77206e4 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
@@ -794,4 +794,12 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig {
public Set<String> getHiddenTopologiesOnHomepage() {
return Collections.emptySet();
}
+
+ /**
+ * @return returns whether know token permissive failure is enabled
+ */
+ @Override
+ public boolean isKnoxTokenPermissiveValidationEnabled() {
+ return false;
+ }
}