You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by pz...@apache.org on 2020/02/10 15:20:51 UTC
[knox] branch master updated: KNOX-2210 - Gateway-level
configuration for server-managed Knox token state (#259)
This is an automated email from the ASF dual-hosted git repository.
pzampino 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 90a623b KNOX-2210 - Gateway-level configuration for server-managed Knox token state (#259)
90a623b is described below
commit 90a623b1678773bc0dc5ac4491f9fab2afd73312
Author: Phil Zampino <pz...@apache.org>
AuthorDate: Mon Feb 10 10:20:44 2020 -0500
KNOX-2210 - Gateway-level configuration for server-managed Knox token state (#259)
---
.../federation/jwt/filter/AbstractJWTFilter.java | 25 +++-
.../provider/federation/CommonJWTFilterTest.java | 130 +++++++++++++++++++++
.../gateway/config/impl/GatewayConfigImpl.java | 8 ++
.../gateway/service/knoxtoken/TokenResource.java | 24 +++-
.../knoxtoken/TokenServiceResourceTest.java | 107 +++++++++++++++--
.../apache/knox/gateway/config/GatewayConfig.java | 6 +
.../org/apache/knox/gateway/GatewayTestConfig.java | 5 +
7 files changed, 290 insertions(+), 15 deletions(-)
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 190af6d..25813f4 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -49,6 +49,7 @@ import org.apache.knox.gateway.audit.api.AuditServiceFactory;
import org.apache.knox.gateway.audit.api.Auditor;
import org.apache.knox.gateway.audit.api.ResourceType;
import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
@@ -111,13 +112,35 @@ public abstract class AbstractJWTFilter implements Filter {
GatewayServices services = (GatewayServices) context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
if (services != null) {
authority = services.getService(ServiceType.TOKEN_SERVICE);
- if (Boolean.valueOf(filterConfig.getInitParameter(TokenStateService.CONFIG_SERVER_MANAGED))) {
+ if (isServerManagedTokenStateEnabled(filterConfig)) {
tokenStateService = services.getService(ServiceType.TOKEN_STATE_SERVICE);
}
}
}
}
+ protected boolean isServerManagedTokenStateEnabled(FilterConfig filterConfig) {
+ boolean isServerManaged = false;
+
+ // First, check for explicit provider-level configuration
+ String providerParamValue = filterConfig.getInitParameter(TokenStateService.CONFIG_SERVER_MANAGED);
+
+ // If there is no provider-level configuration
+ if (providerParamValue == null || providerParamValue.isEmpty()) {
+ // Fall back to the gateway-level default
+ ServletContext context = filterConfig.getServletContext();
+ if (context != null) {
+ GatewayConfig config = (GatewayConfig) context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+ isServerManaged = (config != null) && config.isServerManagedTokenStateEnabled();
+ }
+ } else {
+ // Otherwise, apply the provider-level configuration
+ isServerManaged = Boolean.valueOf(providerParamValue);
+ }
+
+ return isServerManaged;
+ }
+
protected void configureExpectedParameters(FilterConfig filterConfig) {
expectedIssuer = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER);
if (expectedIssuer == null) {
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/CommonJWTFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/CommonJWTFilterTest.java
new file mode 100644
index 0000000..93bd51b
--- /dev/null
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/CommonJWTFilterTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.provider.federation;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
+import org.apache.knox.gateway.services.security.token.TokenStateService;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CommonJWTFilterTest {
+
+ private AbstractJWTFilter handler;
+
+ @Before
+ public void setUp() {
+ handler = new TestHandler();
+ }
+
+ @After
+ public void tearDown() {
+ handler = null;
+ }
+
+ @Test
+ public void testServerManagedTokenStateEnabledByProviderOverride() throws Exception {
+ // Provider param should override gateway config
+ assertTrue(doTestServerManagedTokenState(false, "true"));
+ }
+
+ @Test
+ public void testServerManagedTokenStateDisabledByProviderOverride() throws Exception {
+ // Provider param should override gateway config
+ assertFalse(doTestServerManagedTokenState(true, "false"));
+ }
+
+ @Test
+ public void testServerManagedTokenStateDisabledWithoutProviderOverride() throws Exception {
+ // Missing provider param override should apply gateway config
+ assertFalse(doTestServerManagedTokenState(false, null));
+ }
+
+ @Test
+ public void testServerManagedTokenStateEnabledWithoutProviderOverride() throws Exception {
+ // Missing provider param override should apply gateway config
+ assertTrue(doTestServerManagedTokenState(true, null));
+ }
+
+ @Test
+ public void testServerManagedTokenStateDisabledWitEmptyProviderOverride() throws Exception {
+ // Empty provider param override should apply gateway config
+ assertFalse(doTestServerManagedTokenState(false, ""));
+ }
+
+ @Test
+ public void testServerManagedTokenStateEnabledWithEmptyProviderOverride() throws Exception {
+ // Empty provider param override should apply gateway config
+ assertTrue(doTestServerManagedTokenState(true, ""));
+ }
+
+ private boolean doTestServerManagedTokenState(final Boolean isEnabledAtGateway, final String providerParamValue)
+ throws Exception {
+
+ GatewayConfig gwConf = EasyMock.createNiceMock(GatewayConfig.class);
+ EasyMock.expect(gwConf.isServerManagedTokenStateEnabled()).andReturn(isEnabledAtGateway).anyTimes();
+ EasyMock.replay(gwConf);
+
+ ServletContext sc = EasyMock.createNiceMock(ServletContext.class);
+ EasyMock.expect(sc.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gwConf).anyTimes();
+ EasyMock.replay(sc);
+
+ FilterConfig fc = EasyMock.createNiceMock(FilterConfig.class);
+ EasyMock.expect(fc.getInitParameter(TokenStateService.CONFIG_SERVER_MANAGED)).andReturn(providerParamValue).anyTimes();
+ EasyMock.expect(fc.getServletContext()).andReturn(sc).anyTimes();
+ EasyMock.replay(fc);
+
+ Method m = AbstractJWTFilter.class.getDeclaredMethod("isServerManagedTokenStateEnabled", FilterConfig.class);
+ m.setAccessible(true);
+ return (Boolean) m.invoke(handler, fc);
+ }
+
+
+ static final class TestHandler extends AbstractJWTFilter {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+
+ }
+
+ @Override
+ protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status, String error) throws IOException {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ }
+
+}
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 36ba1ee..1a0954a 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
@@ -240,6 +240,8 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
/* property that specifies list of services for which we need to append service name to the X-Forward-Context header */
public static final String X_FORWARD_CONTEXT_HEADER_APPEND_SERVICES = GATEWAY_CONFIG_FILE_PREFIX + ".xforwarded.header.context.append.servicename";
+ private static final String TOKEN_STATE_SERVER_MANAGED = GATEWAY_CONFIG_FILE_PREFIX + ".knox.token.exp.server-managed";
+
private static final String CLOUDERA_MANAGER_DESCRIPTORS_MONITOR_INTERVAL = GATEWAY_CONFIG_FILE_PREFIX + ".cloudera.manager.descriptors.monitor.interval";
private static final long DEFAULT_CLOUDERA_MANAGER_DESCRIPTORS_MONITOR_INTERVAL = 30000L;
private static final String CLOUDERA_MANAGER_ADVANCED_SERVICE_DISCOVERY_CONF_MONITOR_INTERVAL = GATEWAY_CONFIG_FILE_PREFIX + ".cloudera.manager.advanced.service.discovery.config.monitor.interval";
@@ -1109,4 +1111,10 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
public long getClouderaManagerAdvancedServiceDiscoveryConfigurationMonitoringInterval() {
return getLong(CLOUDERA_MANAGER_ADVANCED_SERVICE_DISCOVERY_CONF_MONITOR_INTERVAL, DEFAULT_CLOUDERA_MANAGER_ADVANCED_SERVICE_DISCOVERY_CONF_MONITOR_INTERVAL);
}
+
+ @Override
+ public boolean isServerManagedTokenStateEnabled() {
+ return getBoolean(TOKEN_STATE_SERVER_MANAGED, false);
+ }
+
}
diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 10c62e0..70cd59d 100644
--- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -40,6 +40,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.apache.commons.codec.binary.Base64;
+import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.security.SubjectUtils;
import org.apache.knox.gateway.services.ServiceType;
@@ -158,8 +159,8 @@ public class TokenResource {
endpointPublicCert = targetEndpointPublicCert;
}
- // If server-managed token expiration is configured, set the token store service
- if (Boolean.valueOf(context.getInitParameter(TokenStateService.CONFIG_SERVER_MANAGED))) {
+ // If server-managed token expiration is configured, set the token state service
+ if (isServerManagedTokenStateEnabled()) {
String topologyName = getTopologyName();
log.serverManagedTokenStateEnabled(topologyName);
@@ -196,6 +197,25 @@ public class TokenResource {
}
}
+ private boolean isServerManagedTokenStateEnabled() {
+ boolean isServerManaged;
+
+ // First, check for explicit service-level configuration
+ String serviceParamValue = context.getInitParameter(TokenStateService.CONFIG_SERVER_MANAGED);
+
+ // If there is no service-level configuration
+ if (serviceParamValue == null || serviceParamValue.isEmpty()) {
+ // Fall back to the gateway-level default
+ GatewayConfig config = (GatewayConfig) context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+ isServerManaged = (config != null) && config.isServerManagedTokenStateEnabled();
+ } else {
+ // Otherwise, apply the service-level configuration
+ isServerManaged = Boolean.valueOf(serviceParamValue);
+ }
+
+ return isServerManaged;
+ }
+
@GET
@Produces({APPLICATION_JSON, APPLICATION_XML})
public Response doGet() {
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 9ccee4d..074dfeb 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -625,9 +625,31 @@ public class TokenServiceResourceTest {
assertTrue((expiresDate.getTime() - now.getTime()) < 30000L);
}
+
+ @Test
+ public void testTokenRenewal_ServerManagedStateConfiguredAtGatewayOnly() throws Exception {
+ final String caller = "yarn";
+ Response renewalResponse = doTestTokenRenewal(null, true, caller, null, createTestSubject(caller)).getValue();
+ validateSuccessfulRenewalResponse(renewalResponse);
+ }
+
+ @Test
+ public void testTokenRenewal_ServerManagedStateDisabledAtGatewayWithServiceOverride() throws Exception {
+ final String caller = "yarn";
+ Response renewalResponse = doTestTokenRenewal(true, false, caller, null, createTestSubject(caller)).getValue();
+ validateSuccessfulRenewalResponse(renewalResponse);
+ }
+
+ @Test
+ public void testTokenRenewal_ServerManagedStateEnabledAtGatewayWithServiceOverride() throws Exception {
+ final String caller = "yarn";
+ Response renewalResponse = doTestTokenRenewal(false, true, caller, null, createTestSubject(caller)).getValue();
+ validateRenewalResponse(renewalResponse, 400, false, "Token renewal support is not configured");
+ }
+
@Test
- public void testTokenRenewal_ServerManagedStateNotConfigured() throws Exception {
- Response renewalResponse = doTestTokenRenewal(null, null, null);
+ public void testTokenRenewal_ServerManagedStateNotConfiguredAtAll() throws Exception {
+ Response renewalResponse = doTestTokenRenewal(null, null, null, null, null).getValue();
validateRenewalResponse(renewalResponse, 400, false, "Token renewal support is not configured");
}
@@ -810,13 +832,39 @@ public class TokenServiceResourceTest {
final String renewers,
final Long maxTokenLifetime,
final Subject caller) throws Exception {
+ return doTestTokenRenewal(isTokenStateServerManaged,
+ null,
+ renewers,
+ maxTokenLifetime,
+ caller);
+ }
+
+ /**
+ *
+ * @param serviceLevelConfig true, if server-side token state management should be enabled; Otherwise, false or null.
+ * @param gatewayLevelConfig true, if server-side token state management should be enabled; Otherwise, false or null.
+ * @param renewers A comma-delimited list of permitted renewer user names
+ * @param maxTokenLifetime The maximum duration (milliseconds) for a token's lifetime
+ * @param caller The user name making the request
+ *
+ * @return The Response from the token renewal request
+ *
+ * @throws Exception
+ */
+ private Map.Entry<TestTokenStateService, Response> doTestTokenRenewal(final Boolean serviceLevelConfig,
+ final Boolean gatewayLevelConfig,
+ final String renewers,
+ final Long maxTokenLifetime,
+ final Subject caller) throws Exception {
return doTestTokenLifecyle(TokenLifecycleOperation.Renew,
- isTokenStateServerManaged,
+ serviceLevelConfig,
+ gatewayLevelConfig,
renewers,
maxTokenLifetime,
caller);
}
+
/**
*
* @param isTokenStateServerManaged true, if server-side token state management should be enabled; Otherwise, false or null.
@@ -851,36 +899,71 @@ public class TokenServiceResourceTest {
}
/**
- * @param operation A TokenLifecycleOperation
- * @param isTokenStateServerManaged true, if server-side token state management should be enabled; Otherwise, false or null.
- * @param renewers A comma-delimited list of permitted renewer user names
- * @param maxTokenLifetime The maximum lifetime duration for a token.
- * @param caller The user name making the request
+ * @param operation A TokenLifecycleOperation
+ * @param serviceLevelConfig true, if server-side token state management should be enabled at the service level;
+ * Otherwise, false or null.
+ * @param renewers A comma-delimited list of permitted renewer user names
+ * @param maxTokenLifetime The maximum lifetime duration for a token.
+ * @param caller The user name making the request
*
* @return The Response from the token revocation request
*
* @throws Exception
*/
private Map.Entry<TestTokenStateService, Response> doTestTokenLifecyle(final TokenLifecycleOperation operation,
- final Boolean isTokenStateServerManaged,
+ final Boolean serviceLevelConfig,
final String renewers,
final Long maxTokenLifetime,
final Subject caller) throws Exception {
+ return doTestTokenLifecyle(operation, serviceLevelConfig, null, renewers, maxTokenLifetime, caller);
+ }
+
+ /**
+ * @param operation A TokenLifecycleOperation
+ * @param serviceLevelConfig true, if server-side token state management should be enabled at the service level;
+ * Otherwise, false or null.
+ * @param gatewayLevelConfig true, if server-side token state management should be enabled at the gateway level;
+ * Otherwise, false or null.
+ * @param renewers A comma-delimited list of permitted renewer user names
+ * @param maxTokenLifetime The maximum lifetime duration for a token.
+ * @param caller The user name making the request
+ *
+ * @return The Response from the token revocation request
+ *
+ * @throws Exception
+ */
+ private Map.Entry<TestTokenStateService, Response> doTestTokenLifecyle(final TokenLifecycleOperation operation,
+ final Boolean serviceLevelConfig,
+ final Boolean gatewayLevelConfig,
+ final String renewers,
+ final Long maxTokenLifetime,
+ final Subject caller) throws Exception {
+
ServletContext context = EasyMock.createNiceMock(ServletContext.class);
EasyMock.expect(context.getInitParameter("knox.token.audiences")).andReturn("recipient1,recipient2");
EasyMock.expect(context.getInitParameter("knox.token.ttl")).andReturn(String.valueOf(Long.MAX_VALUE));
EasyMock.expect(context.getInitParameter("knox.token.target.url")).andReturn(null);
EasyMock.expect(context.getInitParameter("knox.token.client.data")).andReturn(null);
- if (isTokenStateServerManaged != null) {
+ // Configure the service-level params
+ if (serviceLevelConfig != null) {
EasyMock.expect(context.getInitParameter("knox.token.exp.server-managed"))
- .andReturn(String.valueOf(isTokenStateServerManaged));
+ .andReturn(String.valueOf(serviceLevelConfig));
if (maxTokenLifetime != null) {
- EasyMock.expect(context.getInitParameter("knox.token.exp.renew-interval")).andReturn(String.valueOf(maxTokenLifetime / 2));
+ EasyMock.expect(context.getInitParameter("knox.token.exp.renew-interval"))
+ .andReturn(String.valueOf(maxTokenLifetime / 2));
EasyMock.expect(context.getInitParameter("knox.token.exp.max-lifetime")).andReturn(maxTokenLifetime.toString());
}
}
EasyMock.expect(context.getInitParameter("knox.token.renewer.whitelist")).andReturn(renewers);
+ // Configure the gateway-level properties
+ GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+ if (gatewayLevelConfig != null) {
+ EasyMock.expect(gatewayConfig.isServerManagedTokenStateEnabled()).andReturn(gatewayLevelConfig).anyTimes();
+ }
+ EasyMock.replay(gatewayConfig);
+ EasyMock.expect(context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
+
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
Principal principal = EasyMock.createNiceMock(Principal.class);
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 4aa7192..968b0fc 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
@@ -652,4 +652,10 @@ public interface GatewayConfig {
* @return the monitoring interval (in milliseconds) of Cloudera Manager advanced service discovery configuration
*/
long getClouderaManagerAdvancedServiceDiscoveryConfigurationMonitoringInterval();
+
+ /**
+ * @return true, if state for tokens issued by the Knox Token service should be managed by Knox.
+ */
+ boolean isServerManagedTokenStateEnabled();
+
}
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 f7df9a3..6424d61 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
@@ -774,4 +774,9 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig {
public long getClouderaManagerAdvancedServiceDiscoveryConfigurationMonitoringInterval() {
return 0;
}
+
+ @Override
+ public boolean isServerManagedTokenStateEnabled() {
+ return false;
+ }
}