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 8a504c7 KNOX-1832 - KnoxSession handling of JAAS config for kerberos
auth is not deterministic
8a504c7 is described below
commit 8a504c7c7f8e6a07ded65c671b64ccd702f47668
Author: pzampino <[email protected]>
AuthorDate: Wed Mar 20 12:03:39 2019 -0400
KNOX-1832 - KnoxSession handling of JAAS config for kerberos auth is not
deterministic
---
.../apache/knox/gateway/shell/ClientContext.java | 2 +-
.../org/apache/knox/gateway/shell/KnoxSession.java | 140 ++++++++++++++++++---
.../knox/gateway/shell/KnoxShellMessages.java | 30 ++++-
gateway-shell/src/main/resources/jaas.conf | 7 +-
.../apache/knox/gateway/shell/KnoxSessionTest.java | 68 ++++++++++
5 files changed, 225 insertions(+), 22 deletions(-)
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
index 3bb1339..dde0ac7 100644
---
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
+++
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
@@ -240,7 +240,7 @@ public class ClientContext {
}
public boolean debug() {
- return configuration.getBoolean("debug");
+ return configuration.getBoolean("debug", false);
}
}
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
index 0033e02..3179642 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
@@ -57,6 +57,8 @@ import
org.apache.knox.gateway.shell.util.ClientTrustStoreHelper;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.security.auth.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
@@ -66,10 +68,12 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
+import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -100,6 +104,8 @@ public class KnoxSession implements Closeable {
private static final KnoxShellMessages LOG =
MessagesFactory.get(KnoxShellMessages.class);
private boolean isKerberos;
+ private URL jaasConfigURL;
+
String base;
HttpHost host;
CloseableHttpClient client;
@@ -118,7 +124,7 @@ public class KnoxSession implements Closeable {
protected KnoxSession() throws KnoxShellException, URISyntaxException {
}
- public KnoxSession( final ClientContext clientContext) throws
KnoxShellException, URISyntaxException {
+ public KnoxSession(final ClientContext clientContext) throws
KnoxShellException, URISyntaxException {
this.executor = Executors.newCachedThreadPool();
this.base = clientContext.url();
@@ -199,7 +205,22 @@ public class KnoxSession implements Closeable {
*/
public static KnoxSession kerberosLogin(final String url)
throws URISyntaxException {
- return kerberosLogin(url, "", "", false);
+ return kerberosLogin(url, false);
+ }
+
+ /**
+ * Support kerberos authentication.
+ * This method assumed kinit has already been called
+ * and the token is persisted on disk.
+ * @param url Gateway url
+ * @param debug enable debug messages
+ * @return KnoxSession
+ * @throws URISyntaxException exception in case of malformed url
+ * @since 1.3.0
+ */
+ public static KnoxSession kerberosLogin(final String url, boolean debug)
+ throws URISyntaxException {
+ return kerberosLogin(url, "", "", debug);
}
public static KnoxSession loginInsecure(String url, String username, String
password) throws URISyntaxException {
@@ -264,12 +285,24 @@ public class KnoxSession implements Closeable {
}
if (!StringUtils.isBlank(clientContext.kerberos().jaasConf())) {
- System.setProperty("java.security.auth.login.config",
- clientContext.kerberos().jaasConf());
- } else {
- final URL url = getClass().getResource(DEFAULT_JAAS_FILE);
- System.setProperty("java.security.auth.login.config",
- url.toExternalForm());
+ File f = new File(clientContext.kerberos().jaasConf());
+ if (f.exists()) {
+ try {
+ jaasConfigURL = f.getCanonicalFile().toURI().toURL();
+ LOG.jaasConfigurationLocation(jaasConfigURL.toExternalForm());
+ } catch (IOException e) {
+ LOG.failedToLocateJAASConfiguration(e.getMessage());
+ }
+ } else {
+ LOG.jaasConfigurationDoesNotExist(f.getAbsolutePath());
+ }
+ }
+
+ // Fall back to the default JAAS config
+ if (jaasConfigURL == null) {
+ LOG.usingDefaultJAASConfiguration();
+ jaasConfigURL = getClass().getResource(DEFAULT_JAAS_FILE);
+ LOG.jaasConfigurationLocation(jaasConfigURL.toExternalForm());
}
if (clientContext.kerberos().debug()) {
@@ -426,7 +459,18 @@ public class KnoxSession implements Closeable {
if (isKerberos) {
LoginContext lc;
try {
- lc = new LoginContext(JGSS_LOGIN_MOUDLE, new TextCallbackHandler());
+ Configuration jaasConf;
+ try {
+ jaasConf = new JAASClientConfig(jaasConfigURL);
+ } catch (Exception e) {
+ LOG.failedToLoadJAASConfiguration(jaasConfigURL.toExternalForm());
+ throw new KnoxShellException(e.toString(), e);
+ }
+
+ lc = new LoginContext(JGSS_LOGIN_MOUDLE,
+
Subject.getSubject(AccessController.getContext()),
+ new TextCallbackHandler(),
+ jaasConf);
lc.login();
return Subject.doAs(lc.getSubject(),
(PrivilegedAction<CloseableHttpResponse>) () -> {
@@ -447,9 +491,7 @@ public class KnoxSession implements Closeable {
} catch (final LoginException e) {
throw new KnoxShellException(e.toString(), e);
}
-
} else {
-
CloseableHttpResponse response = client.execute(host, request, context);
if (response.getStatusLine().getStatusCode() < 400) {
return response;
@@ -458,7 +500,6 @@ public class KnoxSession implements Closeable {
response);
}
}
-
}
public <T> Future<T> executeLater( Callable<T> callable ) {
@@ -519,8 +560,79 @@ public class KnoxSession implements Closeable {
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder(
- "KnoxSession{base='").append(base).append("\'}");
+ final StringBuilder sb = new StringBuilder("KnoxSession{base='");
+ sb.append(base).append("\'}");
return sb.toString();
}
+
+
+ private static final class JAASClientConfig extends Configuration {
+
+ private static final Configuration baseConfig =
Configuration.getConfiguration();
+
+ private Configuration configFile;
+
+ JAASClientConfig(URL configFileURL) throws Exception {
+ if (configFileURL != null) {
+ this.configFile = ConfigurationFactory.create(configFileURL.toURI());
+ }
+ }
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ AppConfigurationEntry[] result = null;
+
+ // Try the config file if it exists
+ if (configFile != null) {
+ result = configFile.getAppConfigurationEntry(name);
+ }
+
+ // If the entry isn't there, delegate to the base configuration
+ if (result == null) {
+ result = baseConfig.getAppConfigurationEntry(name);
+ }
+
+ return result;
+ }
+ }
+
+ @SuppressWarnings("PMD.AvoidAccessibilityAlteration")
+ private static class ConfigurationFactory {
+
+ private static final Class implClazz;
+ static {
+ // Oracle and OpenJDK use the Sun implementation
+ String implName = System.getProperty("java.vendor").contains("IBM") ?
+ "com.ibm.security.auth.login.ConfigFile" :
"com.sun.security.auth.login.ConfigFile";
+
+ LOG.usingJAASConfigurationFileImplementation(implName);
+ Class clazz = null;
+ try {
+ clazz = Class.forName(implName, false,
Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ LOG.failedToLoadJAASConfigurationFileImplementation(implName,
e.getLocalizedMessage());
+ }
+
+ implClazz = clazz;
+ }
+
+ static Configuration create(URI uri) {
+ Configuration config = null;
+
+ if (implClazz != null) {
+ try {
+ Constructor ctor = implClazz.getDeclaredConstructor(URI.class);
+ config = (Configuration) ctor.newInstance(uri);
+ } catch (Exception e) {
+
LOG.failedToInstantiateJAASConfigurationFileImplementation(implClazz.getCanonicalName(),
+
e.getLocalizedMessage());
+ }
+ } else {
+ LOG.noJAASConfigurationFileImplementation();
+ }
+
+ return config;
+ }
+ }
+
}
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
index f3671b2..07f5fa6 100644
---
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
+++
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
@@ -26,6 +26,34 @@ import org.apache.knox.gateway.i18n.messages.StackTrace;
@Messages(logger="org.apache.knox.gateway.shell")
public interface KnoxShellMessages {
- @Message( level = MessageLevel.WARN, text = "Unable to load provided PEM
encoded trusted cert - falling through for other truststores: {0}" )
+ @Message( level = MessageLevel.WARN, text = "Unable to create provided PEM
encoded trusted cert - falling through for other truststores: {0}" )
void unableToLoadProvidedPEMEncodedTrustedCert(@StackTrace( level =
MessageLevel.DEBUG ) IOException e);
+
+ @Message( level = MessageLevel.DEBUG, text = "Using JAAS configuration file
implementation: {0}" )
+ void usingJAASConfigurationFileImplementation(String implName);
+
+ @Message( level = MessageLevel.ERROR, text = "Failed to create JAAS
configuration file implementation {0}: {1}" )
+ void failedToLoadJAASConfigurationFileImplementation(String implName, String
error);
+
+ @Message( level = MessageLevel.ERROR, text = "Failed to instantiate JAAS
configuration file implementation {0}: {1}" )
+ void failedToInstantiateJAASConfigurationFileImplementation(String implName,
String error);
+
+ @Message( level = MessageLevel.ERROR, text = "No JAAS configuration file
implementation is available" )
+ void noJAASConfigurationFileImplementation();
+
+ @Message( level = MessageLevel.ERROR, text = "Failed to create the JAAS
configuration: {0}" )
+ void failedToLoadJAASConfiguration(String configFileName);
+
+ @Message( level = MessageLevel.ERROR, text = "Failed to locate the specified
JAAS configuration: {0}" )
+ void failedToLocateJAASConfiguration(String message);
+
+ @Message( level = MessageLevel.ERROR, text = "The specified JAAS
configuration does not exist: {0}" )
+ void jaasConfigurationDoesNotExist(String jaasConf);
+
+ @Message( level = MessageLevel.INFO, text = "Using default JAAS
configuration" )
+ void usingDefaultJAASConfiguration();
+
+ @Message( level = MessageLevel.DEBUG, text = "JAAS configuration: {0}" )
+ void jaasConfigurationLocation(String location);
+
}
diff --git a/gateway-shell/src/main/resources/jaas.conf
b/gateway-shell/src/main/resources/jaas.conf
index 330ae7a..7ff583b 100644
--- a/gateway-shell/src/main/resources/jaas.conf
+++ b/gateway-shell/src/main/resources/jaas.conf
@@ -19,10 +19,5 @@ com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
useTicketCache=true
doNotPrompt=true
- renewTGT=true
- debug=true;
- //ticketCache="/tmp/krb5cc_502"
- //useKeyTab=true
- //principal="guest/[email protected]"
- //keyTab="/Users/username/spnego.service.keytab";
+ renewTGT=true;
};
\ No newline at end of file
diff --git
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
index 56e5712..0c275b4 100644
---
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
+++
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
@@ -17,11 +17,21 @@
*/
package org.apache.knox.gateway.shell;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
public class KnoxSessionTest {
public static final String PEM =
"MIICOjCCAaOgAwIBAgIJAN5kp1oW3Up8MA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNVBAYTAlVTMQ0w\n"
+
"CwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRvb3AxDTALBgNVBAsTBFRl\n"
@@ -72,4 +82,62 @@ public class KnoxSessionTest {
fail("Should have been able to parse cert with BEGIN and END Certificate
delimiters.");
}
}
+
+ /**
+ * Validate that the jaasConf option is applied when specified for a
kerberos KnoxSession login.
+ */
+ @Test
+ public void testJAASConfigOption() {
+ final String testJaasConf = "/etc/knoxsessiontest-jaas.conf";
+
+ final Logger logger = Logger.getLogger("org.apache.knox.gateway.shell");
+ final Level originalLevel = logger.getLevel();
+ logger.setLevel(Level.FINEST);
+ LogHandler logCapture = new LogHandler();
+ logger.addHandler(logCapture);
+
+ try {
+ ClientContext context =
ClientContext.with("https://localhost:8443/gateway/dt")
+ .kerberos()
+ .enable(true)
+ .jaasConf(testJaasConf)
+ .end();
+ assertNotNull(context);
+ assertEquals(context.kerberos().jaasConf(), testJaasConf);
+
+ try {
+ KnoxSession.login(context).executeNow(null);
+ } catch (Exception e) {
+ // Expected because the HTTP request is null, which is irrelevant for
this test
+ }
+
+ assertFalse(logCapture.logMessages.isEmpty());
+ assertEquals("The specified JAAS configuration does not exist: " +
testJaasConf, logCapture.logMessages.get(0));
+ assertEquals("Using default JAAS configuration",
logCapture.logMessages.get(1));
+ assertTrue(logCapture.logMessages.get(2).startsWith("JAAS configuration:
"));
+ assertTrue(logCapture.logMessages.get(2).endsWith("jaas.conf"));
+ } finally {
+ logger.removeHandler(logCapture);
+ logger.setLevel(originalLevel);
+ }
+ }
+
+ private static class LogHandler extends Handler {
+
+ List<String> logMessages = new ArrayList<>();
+
+ @Override
+ public void publish(LogRecord record) {
+ logMessages.add(record.getMessage());
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ }
+ }
+
}