You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by kr...@apache.org on 2019/03/05 15:36:40 UTC

[knox] branch master updated: KNOX-474 - Improve Kerberos config validation and diagnostics at startup (#62)

This is an automated email from the ASF dual-hosted git repository.

krisden 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 fe15d44  KNOX-474 - Improve Kerberos config validation and diagnostics at startup (#62)
fe15d44 is described below

commit fe15d443d1df4020c3cba94bb88cd5d907113f14
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Tue Mar 5 16:36:34 2019 +0100

    KNOX-474 - Improve Kerberos config validation and diagnostics at startup (#62)
---
 .../org/apache/knox/gateway/GatewayServer.java     | 31 +++++++++++
 .../zk/RemoteConfigurationRegistryJAASConfig.java  | 21 ++++++++
 .../RemoteConfigurationRegistryJAASConfigTest.java | 62 +++++++++++++++-------
 .../config/GatewayConfigurationException.java      | 37 +++++++++++++
 4 files changed, 133 insertions(+), 18 deletions(-)

diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
index 6539c68..e4a9e76 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
@@ -30,6 +30,7 @@ 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.config.GatewayConfigurationException;
 import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
 import org.apache.knox.gateway.deploy.DeploymentException;
 import org.apache.knox.gateway.deploy.DeploymentFactory;
@@ -96,6 +97,7 @@ import java.net.URL;
 import java.net.URLClassLoader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
@@ -104,12 +106,14 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 import java.util.ServiceLoader;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 
@@ -152,6 +156,7 @@ public class GatewayServer {
         }
         GatewayConfig config = new GatewayConfigImpl();
         if (config.isHadoopKerberosSecured()) {
+          validateKerberosConfig(config);
           configureKerberosSecurity( config );
         }
         Map<String,String> options = new HashMap<>();
@@ -244,6 +249,32 @@ public class GatewayServer {
     setSystemProperty(GatewayConfig.KRB5_USE_SUBJECT_CREDS_ONLY,  "false");
   }
 
+  private static void validateKerberosConfig(GatewayConfig config) throws GatewayConfigurationException {
+    final Set<String> errors = new HashSet<>();
+    if (config.isHadoopKerberosSecured()) {
+      if (config.getKerberosConfig() != null) {
+        final File krb5ConfFile = Paths.get(config.getKerberosConfig()).toFile();
+        checkIfFileExistsAndCanBeRead(krb5ConfFile, GatewayConfig.KRB5_CONFIG, errors);
+      }
+
+      if (config.getKerberosLoginConfig() != null) {
+        final File loginConfigFile = Paths.get(config.getKerberosLoginConfig()).toFile();
+        checkIfFileExistsAndCanBeRead(loginConfigFile, GatewayConfig.KRB5_LOGIN_CONFIG, errors);
+      }
+    }
+    if (!errors.isEmpty()) {
+      throw new GatewayConfigurationException(errors);
+    }
+  }
+
+  private static void checkIfFileExistsAndCanBeRead(File fileToBeChecked, String propertyName, Set<String> errors) {
+    if (!fileToBeChecked.exists()) {
+      errors.add(propertyName + " is set to a non-existing file: " + fileToBeChecked);
+    } else if (!fileToBeChecked.canRead()) {
+      errors.add(propertyName + " is set to a non-readable file: " + fileToBeChecked);
+    }
+  }
+
   private static void setSystemProperty(String name, String value) {
     System.setProperty(name, value);
     log.logSysProp(name, System.getProperty(name));
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java b/gateway-service-remoteconfig/src/main/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java
index d702d17..9e138a9 100644
--- a/gateway-service-remoteconfig/src/main/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java
@@ -26,6 +26,9 @@ import org.apache.knox.gateway.services.security.AliasServiceException;
 
 import javax.security.auth.login.AppConfigurationEntry;
 import javax.security.auth.login.Configuration;
+
+import java.io.File;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -37,6 +40,7 @@ import java.util.Map;
 class RemoteConfigurationRegistryJAASConfig extends Configuration {
 
    static final String JAAS_CONFIG_ERRROR_PREFIX = "Error while getting secure configuration. This error usually indicates an issue within the supplied JAAS configuration";
+   static final String JGSS_LOGIN_MODUDLE = "com.sun.security.jgss.initiate";
 
     // Underlying SASL mechanisms supported
     enum SASLMechanism {
@@ -72,6 +76,8 @@ class RemoteConfigurationRegistryJAASConfig extends Configuration {
           throw new ConfigurationException(message, e);
         }
 
+        validateKeytabFile();
+
         this.aliasService = aliasService;
 
         // Populate context entries
@@ -89,6 +95,21 @@ class RemoteConfigurationRegistryJAASConfig extends Configuration {
         }
     }
 
+    private void validateKeytabFile() {
+      final AppConfigurationEntry[] appConfigurationEntries = delegate.getAppConfigurationEntry(JGSS_LOGIN_MODUDLE);
+      if (appConfigurationEntries != null) {
+        for (AppConfigurationEntry appConfigurationEntry : appConfigurationEntries) {
+          String keytabFileName = (String) appConfigurationEntry.getOptions().get("keyTab");
+          if (keytabFileName != null && keytabFileName.length() > 0) {
+            final File keytabFile = Paths.get(keytabFileName).toFile();
+            if (!keytabFile.exists() || !keytabFile.canRead()) {
+              throw new ConfigurationException("The specified keytab file " + keytabFile.getAbsolutePath() + " is either non-existing or cannot be read!");
+            }
+          }
+        }
+      }
+    }
+
     @Override
     public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
         AppConfigurationEntry[] result;
diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java
index e80c823..8685808 100644
--- a/gateway-service-remoteconfig/src/test/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java
+++ b/gateway-service-remoteconfig/src/test/java/org/apache/knox/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java
@@ -30,6 +30,7 @@ import org.junit.rules.TemporaryFolder;
 import javax.security.auth.login.AppConfigurationEntry;
 import javax.security.auth.login.Configuration;
 
+import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.startsWith;
 
 import java.io.File;
@@ -91,8 +92,8 @@ public class RemoteConfigurationRegistryJAASConfigTest {
         final String ENTRY_NAME = "my_kerberos_context";
         final String PRINCIPAL  = "myIdentity";
 
-        File dummyKeyTab = File.createTempFile("my_context", "keytab");
-        registryConfigs.add(createKerberosConfig(ENTRY_NAME, PRINCIPAL, dummyKeyTab.getAbsolutePath()));
+        final String dummyKeyTab = createTempKeytabFile("dummyKeytab1");
+        registryConfigs.add(createKerberosConfig(ENTRY_NAME, PRINCIPAL, dummyKeyTab));
 
         try {
             RemoteConfigurationRegistryJAASConfig jaasConfig =
@@ -105,7 +106,7 @@ public class RemoteConfigurationRegistryJAASConfigTest {
             validateKerberosContext(jaasConfig,
                                     ENTRY_NAME,
                                     PRINCIPAL,
-                                    dummyKeyTab.getAbsolutePath(),
+                                    dummyKeyTab,
                                     true,
                                     false);
 
@@ -128,8 +129,8 @@ public class RemoteConfigurationRegistryJAASConfigTest {
         EasyMock.expect(aliasService.getPasswordFromAliasForGateway(DIGEST_PWD_ALIAS)).andReturn(DIGEST_PWD.toCharArray()).anyTimes();
         EasyMock.replay(aliasService);
 
-        File dummyKeyTab = File.createTempFile("my_context", "keytab");
-        registryConfigs.add(createKerberosConfig(KERBEROS_ENTRY_NAME, KERBEROS_PRINCIPAL, dummyKeyTab.getAbsolutePath()));
+        final String dummyKeyTab = createTempKeytabFile("dummyKeytab2");
+        registryConfigs.add(createKerberosConfig(KERBEROS_ENTRY_NAME, KERBEROS_PRINCIPAL, dummyKeyTab));
         registryConfigs.add(createDigestConfig(DIGEST_ENTRY_NAME, DIGEST_PRINCIPAL, DIGEST_PWD_ALIAS));
 
         try {
@@ -143,7 +144,7 @@ public class RemoteConfigurationRegistryJAASConfigTest {
             validateKerberosContext(jaasConfig,
                                     KERBEROS_ENTRY_NAME,
                                     KERBEROS_PRINCIPAL,
-                                    dummyKeyTab.getAbsolutePath(),
+                                    dummyKeyTab,
                                     true,
                                     false);
 
@@ -195,13 +196,14 @@ public class RemoteConfigurationRegistryJAASConfigTest {
         RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, null);
       } finally {
         System.clearProperty(GatewayConfig.KRB5_LOGIN_CONFIG);
+        Configuration.setConfiguration(null);
       }
     }
 
     @Test
     public void shouldRaiseAnErrorWithMeaningfulErrorMessageIfAuthLoginConfigCannotBeParsed() throws Exception {
       final List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>();
-      final String jaasConfigFilePath = writeInvalidJaasConf();
+      final String jaasConfigFilePath = writeInvalidJaasConf(false, "jaasConfWithInvalidKeytab", createTempKeytabFile("invalidKeytab"));
       System.setProperty(GatewayConfig.KRB5_LOGIN_CONFIG, jaasConfigFilePath);
 
       expectedException.expect(ConfigurationException.class);
@@ -211,20 +213,44 @@ public class RemoteConfigurationRegistryJAASConfigTest {
         RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, null);
       } finally {
         System.clearProperty(GatewayConfig.KRB5_LOGIN_CONFIG);
+        Configuration.setConfiguration(null);
       }
     }
 
-    private String writeInvalidJaasConf() throws IOException {
-      final File jaasConfigFile = testFolder.newFile("jaas.conf");
-      final String jaasConfig = "com.sun.security.jgss.initiate {" +
-        "com.sun.security.auth.module.Krb5LoginModule required" +
-        "renewTGT=false" +
-        "doNotPrompt=true" +
-        "useKeyTab=true" +
-        "keyTab=/etc/security/keytabs/knox.service.keytab" + //note the missing quotes; it should be keyTab="/etc/security/keytabs/knox.service.keytab"
-        "principal=\"knox/myHost@myRealm\"" +
-        "storeKey=true" +
-        "useTicketCache=false; " +
+    @Test
+    public void shouldRaiseAnErrorWithMeaningfulErrorMessageIfReferencedKeytabFileDoesNotExists() throws Exception {
+      final String jaasConfigFilePath = writeInvalidJaasConf(true, "jaasConfWithMissingKeytab", "nonExistingKeytabFile");
+      System.setProperty(GatewayConfig.KRB5_LOGIN_CONFIG, jaasConfigFilePath);
+
+      expectedException.expect(ConfigurationException.class);
+      expectedException.expectMessage(startsWith("The specified keytab file"));
+      expectedException.expectMessage(endsWith("is either non-existing or cannot be read!"));
+
+      try {
+        RemoteConfigurationRegistryJAASConfig.configure(new ArrayList<>(), null);
+      } finally {
+        System.clearProperty(GatewayConfig.KRB5_LOGIN_CONFIG);
+        Configuration.setConfiguration(null);
+      }
+    }
+
+    private String createTempKeytabFile(String keytabFileName) throws IOException {
+      final File keytabFile = testFolder.newFile(keytabFileName);
+      FileUtils.writeStringToFile(keytabFile, "dummyBinaryContent", StandardCharsets.UTF_8);
+      return keytabFile.getAbsolutePath();
+    }
+
+    private String writeInvalidJaasConf(boolean valid, String jaasConfFileName, String keytabFileName) throws IOException {
+      final File jaasConfigFile = testFolder.newFile(jaasConfFileName);
+      final String jaasConfig = "com.sun.security.jgss.initiate {\n" +
+        "com.sun.security.auth.module.Krb5LoginModule required\n" +
+        "renewTGT=false\n" +
+        "doNotPrompt=true\n" +
+        "useKeyTab=true\n" +
+        "keyTab=" + (valid ? "\"" : "" ) + keytabFileName + (valid ? "\"" : "" ) + "\n" + //note the missing quotes in case valid=false; it should be keyTab="/etc/security/keytabs/knox.service.keytab"
+        "principal=\"knox/myHost@myRealm\"\n" +
+        "storeKey=true\n" +
+        "useTicketCache=false;\n" +
         "};";
 
       FileUtils.writeStringToFile(jaasConfigFile, jaasConfig, StandardCharsets.UTF_8);
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfigurationException.java b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfigurationException.java
new file mode 100644
index 0000000..f444b8c
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfigurationException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.config;
+
+import java.util.Set;
+
+@SuppressWarnings("serial")
+public class GatewayConfigurationException extends Exception {
+
+  public GatewayConfigurationException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public GatewayConfigurationException(String message) {
+    super(message);
+  }
+
+  public GatewayConfigurationException(Set<String> configurationErrors) {
+    this("Found configurations errors:" + System.lineSeparator() + String.join(System.lineSeparator(), configurationErrors));
+  }
+
+}