Преглед изворни кода

AMBARI-14791 improve logging for when ambari does not have permissions to read the ambari server keytab (dsen)

Dmytro Sen пре 9 година
родитељ
комит
8e15bec8e9

+ 13 - 0
ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java

@@ -312,6 +312,8 @@ public class Configuration {
   public static final String KDC_CONNECTION_CHECK_TIMEOUT_DEFAULT = "10000";
   public static final String KERBEROS_KEYTAB_CACHE_DIR_KEY = "kerberos.keytab.cache.dir";
   public static final String KERBEROS_KEYTAB_CACHE_DIR_DEFAULT = "/var/lib/ambari-server/data/cache";
+  public static final String KERBEROS_CHECK_JAAS_CONFIGURATION_KEY = "kerberos.check.jaas.configuration";
+  public static final String KERBEROS_CHECK_JAAS_CONFIGURATION_DEFAULT = "false";
 
   /**
    * Recovery related configuration
@@ -2076,6 +2078,17 @@ public class Configuration {
     return new File(fileName);
   }
 
+  /**
+   * Determine whether or not ambari server credentials validation is enabled.
+   *
+   * @return true if ambari server credentials check is enabled
+   */
+  public boolean isKerberosJaasConfigurationCheckEnabled() {
+    return Boolean.parseBoolean(properties.getProperty(
+      KERBEROS_CHECK_JAAS_CONFIGURATION_KEY,
+      KERBEROS_CHECK_JAAS_CONFIGURATION_DEFAULT));
+  }
+
   /**
    * Gets the type of database by examining the {@link #getDatabaseUrl()} JDBC
    * URL.

+ 2 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java

@@ -66,6 +66,7 @@ import org.apache.ambari.server.controller.internal.UserPrivilegeResourceProvide
 import org.apache.ambari.server.controller.internal.ViewPermissionResourceProvider;
 import org.apache.ambari.server.controller.metrics.ThreadPoolEnabledPropertyProvider;
 import org.apache.ambari.server.controller.utilities.DatabaseChecker;
+import org.apache.ambari.server.controller.utilities.KerberosChecker;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.PersistenceType;
 import org.apache.ambari.server.orm.dao.BlueprintDAO;
@@ -890,6 +891,7 @@ public class AmbariServer {
       server = injector.getInstance(AmbariServer.class);
       CertificateManager certMan = injector.getInstance(CertificateManager.class);
       certMan.initRootCert();
+      KerberosChecker.checkJaasConfiguration();
       ViewRegistry.initInstance(server.viewRegistry);
       ComponentSSLConfiguration.instance().init(server.configs);
       server.run();

+ 2 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java

@@ -59,6 +59,7 @@ import org.apache.ambari.server.controller.metrics.timeline.cache.TimelineMetric
 import org.apache.ambari.server.controller.metrics.timeline.cache.TimelineMetricCacheProvider;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.utilities.DatabaseChecker;
+import org.apache.ambari.server.controller.utilities.KerberosChecker;
 import org.apache.ambari.server.notifications.DispatchFactory;
 import org.apache.ambari.server.notifications.NotificationDispatcher;
 import org.apache.ambari.server.orm.DBAccessor;
@@ -353,6 +354,7 @@ public class ControllerModule extends AbstractModule {
 
     requestStaticInjection(ExecutionCommandWrapper.class);
     requestStaticInjection(DatabaseChecker.class);
+    requestStaticInjection(KerberosChecker.class);
 
     bindByAnnotation(null);
     bindNotificationDispatchers();

+ 127 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosChecker.java

@@ -0,0 +1,127 @@
+/**
+ * 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.ambari.server.controller.utilities;
+
+import com.google.inject.Inject;
+import com.sun.security.auth.callback.TextCallbackHandler;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import java.io.File;
+import java.util.Map;
+
+public class KerberosChecker {
+
+  private static final String HTTP_SPNEGO_STANDARD_ENTRY =
+    "com.sun.security.jgss.krb5.initiate";
+  private static final String KRB5_LOGIN_MODULE =
+    "com.sun.security.auth.module.Krb5LoginModule";
+  public static final String JAVA_SECURITY_AUTH_LOGIN_CONFIG =
+    "java.security.auth.login.config";
+
+  static Logger LOG = LoggerFactory.getLogger(KerberosChecker.class);
+
+  @Inject
+  static Configuration config;
+
+  /**
+   * Checks Ambari Server with a Kerberos principal and keytab to allow views
+   * to authenticate via SPNEGO against cluster components.
+   *
+   * @throws AmbariException
+   */
+  public static void checkJaasConfiguration() throws AmbariException {
+
+    if (config.isKerberosJaasConfigurationCheckEnabled()) {
+      LOG.info("Checking Ambari Server Kerberos credentials.");
+
+      String jaasConfPath = System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG);
+
+      javax.security.auth.login.Configuration jaasConf =
+        javax.security.auth.login.Configuration.getConfiguration();
+
+      AppConfigurationEntry[] jaasConfEntries =
+        jaasConf.getAppConfigurationEntry(HTTP_SPNEGO_STANDARD_ENTRY);
+
+      if (jaasConfEntries == null) {
+        LOG.warn("Can't find " + HTTP_SPNEGO_STANDARD_ENTRY + " entry in " +
+        jaasConfPath);
+      } else {
+        boolean krb5LoginModulePresent = false;
+        for (AppConfigurationEntry ace : jaasConfEntries) {
+          if (KRB5_LOGIN_MODULE.equals(ace.getLoginModuleName())) {
+            krb5LoginModulePresent = true;
+            Map<String, ?> options = ace.getOptions();
+            if ((options != null)) {
+              if (options.containsKey("keyTab")) {
+                String keytabPath = (String) options.get("keyTab");
+                File keytabFile = new File(keytabPath);
+                if (!keytabFile.exists()) {
+                  LOG.warn(keytabPath + " doesn't exist.");
+                } else if (!keytabFile.canRead()) {
+                  LOG.warn("Unable to read " + keytabPath +
+                    " Please check the file access permissions for user " +
+                    System.getProperty("user.name"));
+                }
+              } else {
+                LOG.warn("Can't find keyTab option in " + KRB5_LOGIN_MODULE +
+                  " module of " + HTTP_SPNEGO_STANDARD_ENTRY + " entry in " +
+                  jaasConfPath);              }
+
+              if (!options.containsKey("principal")) {
+                LOG.warn("Can't find principal option in " + KRB5_LOGIN_MODULE +
+                  " module of " + HTTP_SPNEGO_STANDARD_ENTRY + " entry in " +
+                  jaasConfPath);
+              }
+            }
+          }
+        }
+        if (!krb5LoginModulePresent) {
+          LOG.warn("Can't find " + KRB5_LOGIN_MODULE + " module in " +
+          HTTP_SPNEGO_STANDARD_ENTRY + " entry in " + jaasConfPath);
+        }
+      }
+
+      TextCallbackHandler textHandler = new TextCallbackHandler();
+      try {
+        LoginContext loginContext = new LoginContext(HTTP_SPNEGO_STANDARD_ENTRY,
+          textHandler);
+
+        loginContext.login();
+        loginContext.logout();
+      }
+      catch (LoginException le) {
+        LOG.error(le.getMessage());
+        throw new AmbariException(
+          "Ambari Server Kerberos credentials check failed. \n" +
+          "Check KDC availability and JAAS configuration in " + jaasConfPath);
+      }
+
+      LOG.info("Ambari Server Kerberos credentials check passed.");
+    } else {
+      LOG.info("Skipping Ambari Server Kerberos credentials check.");
+    }
+  }
+
+}

+ 4 - 1
ambari-server/src/main/python/ambari_server/serverConfiguration.py

@@ -163,6 +163,9 @@ SSL_API = 'api.ssl'
 SSL_API_PORT = 'client.api.ssl.port'
 DEFAULT_SSL_API_PORT = 8443
 
+# Kerberos
+CHECK_AMBARI_KRB_JAAS_CONFIGURATION_PROPERTY = "kerberos.check.jaas.configuration"
+
 # JDK
 JDK_RELEASES="java.releases"
 
@@ -1325,4 +1328,4 @@ def get_missing_properties(properties):
     if not value:
       missing_propertiers.append(property)
 
-  return missing_propertiers
+  return missing_propertiers

+ 3 - 2
ambari-server/src/main/python/ambari_server/setupSecurity.py

@@ -38,7 +38,7 @@ from ambari_commons.os_utils import is_root, set_file_permissions, \
   run_os_command, search_file, is_valid_filepath, change_owner, get_ambari_repo_file_full_name, get_file_owner
 from ambari_server.serverConfiguration import configDefaults, \
   encrypt_password, find_jdk, find_properties_file, get_alias_string, get_ambari_properties, get_conf_dir, \
-  get_credential_store_location, get_is_persisted, get_is_secure, get_master_key_location, \
+  get_credential_store_location, get_is_persisted, get_is_secure, get_master_key_location, write_property, \
   get_original_master_key, get_value_from_properties, get_java_exe_path, is_alias_string, read_ambari_user, \
   read_passwd_for_alias, remove_password_file, save_passwd_for_alias, store_password_file, update_properties_2, \
   BLIND_PASSWORD, BOOTSTRAP_DIR_PROPERTY, IS_LDAP_CONFIGURED, JDBC_PASSWORD_FILENAME, JDBC_PASSWORD_PROPERTY, \
@@ -48,7 +48,7 @@ from ambari_server.serverConfiguration import configDefaults, \
   SECURITY_PROVIDER_KEY_CMD, SECURITY_MASTER_KEY_FILENAME, SSL_TRUSTSTORE_PASSWORD_ALIAS, \
   SSL_TRUSTSTORE_PASSWORD_PROPERTY, SSL_TRUSTSTORE_PATH_PROPERTY, SSL_TRUSTSTORE_TYPE_PROPERTY, \
   SSL_API, SSL_API_PORT, DEFAULT_SSL_API_PORT, CLIENT_API_PORT, JDK_NAME_PROPERTY, JCE_NAME_PROPERTY, JAVA_HOME_PROPERTY, \
-  get_resources_location, SECURITY_MASTER_KEY_LOCATION, SETUP_OR_UPGRADE_MSG
+  get_resources_location, SECURITY_MASTER_KEY_LOCATION, SETUP_OR_UPGRADE_MSG, CHECK_AMBARI_KRB_JAAS_CONFIGURATION_PROPERTY
 from ambari_server.serverUtils import is_server_runing, get_ambari_server_api_base
 from ambari_server.setupActions import SETUP_ACTION, LDAP_SETUP_ACTION
 from ambari_server.userInput import get_validated_string_input, get_prompt_default, read_password, get_YN_input
@@ -545,6 +545,7 @@ def setup_ambari_krb5_jaas():
       line = re.sub('principal=.*$', 'principal="' + principal + '"', line)
       print line,
 
+    write_property(CHECK_AMBARI_KRB_JAAS_CONFIGURATION_PROPERTY, "true")
   else:
     raise NonFatalException('No jaas config file found at location: ' +
                             jaas_conf_file)

+ 88 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosCheckerTest.java

@@ -0,0 +1,88 @@
+/**
+ * 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.ambari.server.controller.utilities;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.Configuration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+import static org.powermock.api.easymock.PowerMock.expectNew;
+import static org.powermock.api.easymock.PowerMock.replay;
+import static org.powermock.api.easymock.PowerMock.verifyAll;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({LoginContext.class, KerberosChecker.class})
+public class KerberosCheckerTest {
+
+  @Test
+  public void testCheckPassed() throws Exception {
+    Configuration config =  createMock(Configuration.class);
+    LoginContext lc =  createMock(LoginContext.class);
+
+    expect(config.isKerberosJaasConfigurationCheckEnabled()).andReturn(true).once();
+
+    expectNew(LoginContext.class, new Class<?>[] { String.class, CallbackHandler.class },
+      isA(String.class), isA(CallbackHandler.class) )
+      .andReturn(lc);
+    lc.login();
+    expectLastCall().once();
+    lc.logout();
+    expectLastCall().once();
+
+    replay(config, LoginContext.class, lc);
+
+    KerberosChecker.config = config;
+    KerberosChecker.checkJaasConfiguration();
+
+    verifyAll();
+  }
+
+  @Test(expected = AmbariException.class)
+  public void testCheckFailed() throws Exception {
+    Configuration config =  createMock(Configuration.class);
+    LoginContext lc =  createMock(LoginContext.class);
+
+    expect(config.isKerberosJaasConfigurationCheckEnabled()).andReturn(true).once();
+
+    expectNew(LoginContext.class, new Class<?>[] { String.class, CallbackHandler.class },
+      isA(String.class), isA(CallbackHandler.class) )
+      .andReturn(lc);
+    lc.login();
+    expectLastCall().andThrow(new LoginException()).once();
+
+    replay(config, LoginContext.class, lc);
+
+    KerberosChecker.config = config;
+    KerberosChecker.checkJaasConfiguration();
+
+    verifyAll();
+  }
+
+}