Repository: ambari Updated Branches: refs/heads/branch-2.5 468589ae7 -> 70d2223a9
AMBARI-18365. Add Ambari configuration options to support Kerberos token authentication (rlevas) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/70d2223a Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/70d2223a Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/70d2223a Branch: refs/heads/branch-2.5 Commit: 70d2223a9bb39da3a9b8d7eaf05bbad3698a92c0 Parents: 468589a Author: Robert Levas <[email protected]> Authored: Wed Sep 14 11:13:50 2016 -0400 Committer: Robert Levas <[email protected]> Committed: Wed Sep 14 11:13:54 2016 -0400 ---------------------------------------------------------------------- .../server/configuration/Configuration.java | 183 ++++++++++++++++++- .../server/configuration/ConfigurationTest.java | 136 +++++++++++++- 2 files changed, 317 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/70d2223a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java index fa0f784..b70c5f4 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java @@ -1,4 +1,4 @@ -/** +/* * 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 @@ -56,7 +56,9 @@ import org.apache.ambari.server.orm.PersistenceType; import org.apache.ambari.server.orm.dao.HostRoleCommandStatusSummaryDTO; import org.apache.ambari.server.orm.entities.StageEntity; import org.apache.ambari.server.security.ClientSecurityType; +import org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosAuthenticationProperties; import org.apache.ambari.server.security.authorization.LdapServerProperties; +import org.apache.ambari.server.security.authorization.UserType; import org.apache.ambari.server.security.authorization.jwt.JwtAuthenticationProperties; import org.apache.ambari.server.security.encryption.CertificateUtils; import org.apache.ambari.server.security.encryption.CredentialProvider; @@ -68,6 +70,7 @@ import org.apache.ambari.server.utils.DateUtils; import org.apache.ambari.server.utils.HostUtils; import org.apache.ambari.server.utils.Parallel; import org.apache.ambari.server.utils.ShellCommandUtil; +import org.apache.ambari.server.utils.StageUtils; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -1312,6 +1315,49 @@ public class Configuration { public static final ConfigurationProperty<String> JWT_ORIGINAL_URL_QUERY_PARAM = new ConfigurationProperty<>( "authentication.jwt.originalUrlParamName", "originalUrl"); + /* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * Kerberos authentication-specific properties + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */ + /** + * Determines whether to use Kerberos (SPNEGO) authentication when connecting Ambari. + */ + @Markdown(description = "Determines whether to use Kerberos (SPNEGO) authentication when connecting Ambari.") + public static final ConfigurationProperty<Boolean> KERBEROS_AUTH_ENABLED = new ConfigurationProperty<>( + "authentication.kerberos.enabled", Boolean.FALSE); + + /** + * The Kerberos principal name to use when verifying user-supplied Kerberos tokens for authentication via SPNEGO. + */ + @Markdown(description = "The Kerberos principal name to use when verifying user-supplied Kerberos tokens for authentication via SPNEGO") + public static final ConfigurationProperty<String> KERBEROS_AUTH_SPNEGO_PRINCIPAL = new ConfigurationProperty<>( + "authentication.kerberos.spnego.principal", "HTTP/_HOST"); + + /** + * The Kerberos identity to use when verifying user-supplied Kerberos tokens for authentication via SPNEGO. + */ + @Markdown(description = "The Kerberos keytab file to use when verifying user-supplied Kerberos tokens for authentication via SPNEGO") + public static final ConfigurationProperty<String> KERBEROS_AUTH_SPNEGO_KEYTAB_FILE = new ConfigurationProperty<>( + "authentication.kerberos.spnego.keytab.file", "/etc/security/keytabs/spnego.service.keytab"); + + /** + * A comma-delimited (ordered) list of preferred user types to use when finding the Ambari user + * account for the user-supplied Kerberos identity during authentication via SPNEGO. + */ + @Markdown(description = "A comma-delimited (ordered) list of preferred user types to use when finding the Ambari user account for the user-supplied Kerberos identity during authentication via SPNEGO") + public static final ConfigurationProperty<String> KERBEROS_AUTH_USER_TYPES = new ConfigurationProperty<>( + "authentication.kerberos.user.types", "LDAP"); + + /** + * The auth-to-local rules set to use when translating a user's principal name to a local user name + * during authentication via SPNEGO. + */ + @Markdown(description = "The auth-to-local rules set to use when translating a user's principal name to a local user name during authentication via SPNEGO.") + public static final ConfigurationProperty<String> KERBEROS_AUTH_AUTH_TO_LOCAL_RULES = new ConfigurationProperty<>( + "authentication.kerberos.auth_to_local.rules", "DEFAULT"); + /* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * Kerberos authentication-specific properties (end) + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */ + /** * The type of connection pool to use with JDBC connections to the database. */ @@ -2306,6 +2352,11 @@ public class Configuration { private Map<String, String> databaseConnectorNames = new HashMap<>(); private Map<String, String> databasePreviousConnectorNames = new HashMap<>(); + /** + * The Kerberos authentication-specific properties container (for convenience) + */ + private final AmbariKerberosAuthenticationProperties kerberosAuthenticationProperties; + static { if (System.getProperty("os.name").contains("Windows")) { DEF_ARCHIVE_EXTENSION = ".zip"; @@ -2511,6 +2562,9 @@ public class Configuration { configsMap.put(CLIENT_API_SSL_CRT_PASS.getKey(), password); } + // Capture the Kerberos authentication-related properties + kerberosAuthenticationProperties = createKerberosAuthenticationProperties(); + loadSSLParams(); } @@ -4437,6 +4491,15 @@ public class Configuration { } /** + * Gets the Kerberos authentication-specific properties container + * + * @return an AmbariKerberosAuthenticationProperties + */ + public AmbariKerberosAuthenticationProperties getKerberosAuthenticationProperties() { + return kerberosAuthenticationProperties; + } + + /** * Ambari server temp dir * @return server temp dir */ @@ -5044,4 +5107,122 @@ public class Configuration { String value(); } + /** + * Creates an AmbariKerberosAuthenticationProperties instance containing the Kerberos authentication-specific + * properties. + * + * The relevant properties are processed to set any default values or translate the propery values + * into usable data for the Kerberos authentication logic. + * + * @return + */ + private AmbariKerberosAuthenticationProperties createKerberosAuthenticationProperties() { + AmbariKerberosAuthenticationProperties kerberosAuthProperties = new AmbariKerberosAuthenticationProperties(); + + kerberosAuthProperties.setKerberosAuthenticationEnabled(Boolean.valueOf(getProperty(KERBEROS_AUTH_ENABLED))); + + // if Kerberos authentication is enabled, continue; else ignore the rest of related properties since + // they will not be used. + if (!kerberosAuthProperties.isKerberosAuthenticationEnabled()) { + return kerberosAuthProperties; + } + + // Get and process the configured user type values to convert the comma-delimited string of + // user types into a ordered (as found in the comma-delimited value) list of UserType values. + String userTypes = getProperty(KERBEROS_AUTH_USER_TYPES); + List<UserType> orderedUserTypes = new ArrayList<UserType>(); + + String[] types = userTypes.split(","); + for (String type : types) { + type = type.trim(); + + if (!type.isEmpty()) { + try { + orderedUserTypes.add(UserType.valueOf(type.toUpperCase())); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(String.format("While processing ordered user types from %s, " + + "%s was found to be an invalid user type.", + KERBEROS_AUTH_USER_TYPES.getKey(), type), e); + } + } + } + + // If no user types have been specified, assume only LDAP users... + if (orderedUserTypes.isEmpty()) { + LOG.info("No (valid) user types were specified in {}. Using the default value of LOCAL.", + KERBEROS_AUTH_USER_TYPES.getKey()); + orderedUserTypes.add(UserType.LDAP); + } + + kerberosAuthProperties.setOrderedUserTypes(orderedUserTypes); + + // Get and process the SPNEGO principal name. If it exists and contains the host replacement + // indicator (_HOST), replace it with the hostname of the current host. + String spnegoPrincipalName = getProperty(KERBEROS_AUTH_SPNEGO_PRINCIPAL); + + if ((spnegoPrincipalName != null) && (spnegoPrincipalName.contains("_HOST"))) { + String hostName = StageUtils.getHostName(); + + if (StringUtils.isEmpty(hostName)) { + LOG.warn("Cannot replace _HOST in the configured SPNEGO principal name with the host name this host since it is not available"); + } else { + LOG.info("Replacing _HOST in the configured SPNEGO principal name with the host name this host: {}", hostName); + spnegoPrincipalName = spnegoPrincipalName.replaceAll("_HOST", hostName); + } + } + + kerberosAuthProperties.setSpnegoPrincipalName(spnegoPrincipalName); + + // Validate the SPNEGO principal name to ensure it was set. + // Log any found issues. + if (StringUtils.isEmpty(kerberosAuthProperties.getSpnegoPrincipalName())) { + throw new IllegalArgumentException(String.format("The SPNEGO principal name specified in %s is empty. " + + "This will cause issues authenticating users using Kerberos.", + KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey())); + } + + // Get the SPNEGO keytab file. There is nothing special to process for this value. + kerberosAuthProperties.setSpnegoKeytabFilePath(getProperty(KERBEROS_AUTH_SPNEGO_KEYTAB_FILE)); + + // Validate the SPNEGO keytab file to ensure it was set, it exists and it is readable by Ambari. + // Log any found issues. + if (StringUtils.isEmpty(kerberosAuthProperties.getSpnegoKeytabFilePath())) { + throw new IllegalArgumentException(String.format("The SPNEGO keytab file path specified in %s is empty. " + + "This will cause issues authenticating users using Kerberos.", + KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey())); + } else { + File keytabFile = new File(kerberosAuthProperties.getSpnegoKeytabFilePath()); + if (!keytabFile.exists()) { + throw new IllegalArgumentException(String.format("The SPNEGO keytab file path (%s) specified in %s does not exist. " + + "This will cause issues authenticating users using Kerberos.", + keytabFile.getAbsolutePath(), KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey())); + } else if (!keytabFile.canRead()) { + throw new IllegalArgumentException(String.format("The SPNEGO keytab file path (%s) specified in %s cannot be read. " + + "This will cause issues authenticating users using Kerberos.", + keytabFile.getAbsolutePath(), KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey())); + } + } + + // Get the auth-to-local rule set. There is nothing special to process for this value. + kerberosAuthProperties.setAuthToLocalRules(getProperty(KERBEROS_AUTH_AUTH_TO_LOCAL_RULES)); + + LOG.info("Kerberos authentication is enabled:\n " + + "\t{}: {}\n" + + "\t{}: {}\n" + + "\t{}: {}\n" + + "\t{}: {}\n" + + "\t{}: {}\n", + KERBEROS_AUTH_ENABLED.getKey(), + kerberosAuthProperties.isKerberosAuthenticationEnabled(), + KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey(), + kerberosAuthProperties.getSpnegoPrincipalName(), + KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), + kerberosAuthProperties.getSpnegoKeytabFilePath(), + KERBEROS_AUTH_USER_TYPES.getKey(), + kerberosAuthProperties.getOrderedUserTypes(), + KERBEROS_AUTH_AUTH_TO_LOCAL_RULES.getKey(), + kerberosAuthProperties.getAuthToLocalRules()); + + return kerberosAuthProperties; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/70d2223a/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java b/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java index f9b76f8..0397288 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java @@ -1,4 +1,4 @@ -/** +/* * 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 @@ -30,6 +30,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; import java.util.Map; import java.util.Properties; @@ -40,8 +42,11 @@ import org.apache.ambari.server.configuration.Configuration.ConfigurationPropert import org.apache.ambari.server.configuration.Configuration.ConnectionPoolType; import org.apache.ambari.server.configuration.Configuration.DatabaseType; import org.apache.ambari.server.controller.metrics.ThreadPoolEnabledPropertyProvider; +import org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosAuthenticationProperties; import org.apache.ambari.server.security.authorization.LdapServerProperties; +import org.apache.ambari.server.security.authorization.UserType; import org.apache.ambari.server.state.services.MetricsRetrievalService; +import org.apache.ambari.server.utils.StageUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; @@ -920,4 +925,133 @@ public class ConfigurationTest { StringUtils.isEmpty(markdown.description())); } } + + /** + * Tests that the Kerberos-authentication properties are read and properly and set in an + * {@link AmbariKerberosAuthenticationProperties} instance when Kerberos authentication is enabled. + */ + @Test + public void testKerberosAuthenticationEnabled() throws IOException { + File keytabFile = temp.newFile("spnego.service.keytab"); + + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "true"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), keytabFile.getAbsolutePath()); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey(), "spnego/principal@REALM"); + properties.put(Configuration.KERBEROS_AUTH_USER_TYPES.getKey(), "LDAP, LOCAL"); + properties.put(Configuration.KERBEROS_AUTH_AUTH_TO_LOCAL_RULES.getKey(), "DEFAULT"); + + Configuration configuration = new Configuration(properties); + + AmbariKerberosAuthenticationProperties kerberosAuthenticationProperties = configuration.getKerberosAuthenticationProperties(); + + Assert.assertTrue(kerberosAuthenticationProperties.isKerberosAuthenticationEnabled()); + Assert.assertEquals(keytabFile.getAbsolutePath(), kerberosAuthenticationProperties.getSpnegoKeytabFilePath()); + Assert.assertEquals("spnego/principal@REALM", kerberosAuthenticationProperties.getSpnegoPrincipalName()); + Assert.assertEquals("DEFAULT", kerberosAuthenticationProperties.getAuthToLocalRules()); + Assert.assertEquals(Arrays.asList(UserType.LDAP, UserType.LOCAL), kerberosAuthenticationProperties.getOrderedUserTypes()); + } + + /** + * Tests that the Kerberos-authentication properties are read and properly and set in an + * {@link AmbariKerberosAuthenticationProperties} instance when Kerberos authentication is enabled + * and default values are expected to be used for unset properties. + */ + @Test + public void testKerberosAuthenticationEnabledUsingDefaults() throws IOException { + File keytabFile = temp.newFile("spnego.service.keytab"); + + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "true"); + // Force a specific path to the SPNEGO keytab file since internal validation expects to exist + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), keytabFile.getAbsolutePath()); + + Configuration configuration = new Configuration(properties); + + AmbariKerberosAuthenticationProperties kerberosAuthenticationProperties = configuration.getKerberosAuthenticationProperties(); + + Assert.assertTrue(kerberosAuthenticationProperties.isKerberosAuthenticationEnabled()); + Assert.assertEquals(keytabFile.getAbsolutePath(), kerberosAuthenticationProperties.getSpnegoKeytabFilePath()); + Assert.assertEquals("HTTP/" + StageUtils.getHostName(), kerberosAuthenticationProperties.getSpnegoPrincipalName()); + Assert.assertEquals("DEFAULT", kerberosAuthenticationProperties.getAuthToLocalRules()); + Assert.assertEquals(Collections.singletonList(UserType.LDAP), kerberosAuthenticationProperties.getOrderedUserTypes()); + } + + /** + * Tests that the Kerberos-authentication properties are read and properly set in an + * {@link AmbariKerberosAuthenticationProperties} instance when Kerberos authentication is disabled. + */ + @Test + public void testKerberosAuthenticationDisabled() { + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "false"); + + Configuration configuration = new Configuration(properties); + + AmbariKerberosAuthenticationProperties kerberosAuthenticationProperties = configuration.getKerberosAuthenticationProperties(); + + Assert.assertFalse(kerberosAuthenticationProperties.isKerberosAuthenticationEnabled()); + Assert.assertNull(kerberosAuthenticationProperties.getSpnegoKeytabFilePath()); + Assert.assertNull(kerberosAuthenticationProperties.getSpnegoPrincipalName()); + Assert.assertNull(kerberosAuthenticationProperties.getAuthToLocalRules()); + Assert.assertEquals(Collections.emptyList(), kerberosAuthenticationProperties.getOrderedUserTypes()); + } + + @Test + public void testKerberosAuthenticationDisabledWithValuesSet() { + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "false"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), "/path/to/spnego/keytab/file"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey(), "spnego/principal@REALM"); + properties.put(Configuration.KERBEROS_AUTH_USER_TYPES.getKey(), "LDAP, LOCAL"); + properties.put(Configuration.KERBEROS_AUTH_AUTH_TO_LOCAL_RULES.getKey(), "DEFAULT"); + + Configuration configuration = new Configuration(properties); + + AmbariKerberosAuthenticationProperties kerberosAuthenticationProperties = configuration.getKerberosAuthenticationProperties(); + + Assert.assertFalse(kerberosAuthenticationProperties.isKerberosAuthenticationEnabled()); + Assert.assertNull(kerberosAuthenticationProperties.getSpnegoKeytabFilePath()); + Assert.assertNull(kerberosAuthenticationProperties.getSpnegoPrincipalName()); + Assert.assertNull(kerberosAuthenticationProperties.getAuthToLocalRules()); + Assert.assertEquals(Collections.emptyList(), kerberosAuthenticationProperties.getOrderedUserTypes()); + } + + @Test(expected = IllegalArgumentException.class) + public void testKerberosAuthenticationEmptySPNEGOPrincipalName() throws IOException { + File keytabFile = temp.newFile("spnego.service.keytab"); + + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "true"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), keytabFile.getAbsolutePath()); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey(), ""); + properties.put(Configuration.KERBEROS_AUTH_USER_TYPES.getKey(), "LDAP, LOCAL"); + properties.put(Configuration.KERBEROS_AUTH_AUTH_TO_LOCAL_RULES.getKey(), "DEFAULT"); + + new Configuration(properties); + } + + @Test(expected = IllegalArgumentException.class) + public void testKerberosAuthenticationEmptySPNEGOKeytabFile() { + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "true"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), ""); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey(), "spnego/principal@REALM"); + properties.put(Configuration.KERBEROS_AUTH_USER_TYPES.getKey(), "LDAP, LOCAL"); + properties.put(Configuration.KERBEROS_AUTH_AUTH_TO_LOCAL_RULES.getKey(), "DEFAULT"); + + new Configuration(properties); + } + + @Test(expected = IllegalArgumentException.class) + public void testKerberosAuthenticationSPNEGOKeytabFileNotFound() { + Properties properties = new Properties(); + properties.put(Configuration.KERBEROS_AUTH_ENABLED.getKey(), "true"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_KEYTAB_FILE.getKey(), "/path/to/missing/spnego/keytab/file"); + properties.put(Configuration.KERBEROS_AUTH_SPNEGO_PRINCIPAL.getKey(), "spnego/principal@REALM"); + properties.put(Configuration.KERBEROS_AUTH_USER_TYPES.getKey(), "LDAP, LOCAL"); + properties.put(Configuration.KERBEROS_AUTH_AUTH_TO_LOCAL_RULES.getKey(), "DEFAULT"); + + new Configuration(properties); + } }
