HADOOP-15996. Improved Kerberos username mapping strategy in Hadoop.
Contributed by Bolke de Bruin
(cherry picked from commit d43af8b3db4743b4b240751b6f29de6c20cfd6e5)
(cherry picked from commit febafd0e4f6e7b0a95045565e149e3ed2bee4b6c)
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java
index 887548b..7a918b2 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java
@@ -88,6 +88,12 @@
*/
public static final String NAME_RULES = TYPE + ".name.rules";
+ /**
+ * Constant for the configuration property that indicates how auth_to_local
+ * rules are evaluated.
+ */
+ public static final String RULE_MECHANISM = TYPE + ".name.rules.mechanism";
+
private String type;
private String keytab;
private GSSManager gssManager;
@@ -163,7 +169,10 @@
if (nameRules != null) {
KerberosName.setRules(nameRules);
}
-
+ String ruleMechanism = config.getProperty(RULE_MECHANISM, null);
+ if (ruleMechanism != null) {
+ KerberosName.setRuleMechanism(ruleMechanism);
+ }
try {
gssManager = Subject.doAs(serverSubject,
new PrivilegedExceptionAction<GSSManager>() {
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java
index 287bb13..684d2c8 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java
@@ -44,6 +44,19 @@
public class KerberosName {
private static final Logger LOG = LoggerFactory.getLogger(KerberosName.class);
+ /**
+ * Constant that defines auth_to_local legacy hadoop evaluation
+ */
+ public static final String MECHANISM_HADOOP = "hadoop";
+
+ /**
+ * Constant that defines auth_to_local MIT evaluation
+ */
+ public static final String MECHANISM_MIT = "mit";
+
+ /** Constant that defines the default behavior of the rule mechanism */
+ public static final String DEFAULT_MECHANISM = MECHANISM_HADOOP;
+
/** The first component of the name */
private final String serviceName;
/** The second component of the name. It may be null. */
@@ -81,6 +94,11 @@
*/
private static List<Rule> rules;
+ /**
+ * How to evaluate auth_to_local rules
+ */
+ private static String ruleMechanism = null;
+
private static String defaultRealm = null;
@VisibleForTesting
@@ -304,10 +322,11 @@
* array.
* @param params first element is the realm, second and later elements are
* are the components of the name "a/b@FOO" -> {"FOO", "a", "b"}
+ * @param ruleMechanism defines the rule evaluation mechanism
* @return the short name if this rule applies or null
* @throws IOException throws if something is wrong with the rules
*/
- String apply(String[] params) throws IOException {
+ String apply(String[] params, String ruleMechanism) throws IOException {
String result = null;
if (isDefault) {
if (getDefaultRealm().equals(params[0])) {
@@ -323,7 +342,9 @@
}
}
}
- if (result != null && nonSimplePattern.matcher(result).find()) {
+ if (result != null
+ && nonSimplePattern.matcher(result).find()
+ && ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) {
throw new NoMatchingRule("Non-simple name " + result +
" after auth_to_local rule " + this);
}
@@ -392,21 +413,22 @@
} else {
params = new String[]{realm, serviceName, hostName};
}
+ String ruleMechanism = this.ruleMechanism;
+ if (ruleMechanism == null && rules != null) {
+ LOG.warn("auth_to_local rule mechanism not set."
+ + "Using default of " + DEFAULT_MECHANISM);
+ ruleMechanism = DEFAULT_MECHANISM;
+ }
for(Rule r: rules) {
- String result = r.apply(params);
+ String result = r.apply(params, ruleMechanism);
if (result != null) {
return result;
}
}
- throw new NoMatchingRule("No rules applied to " + toString());
- }
-
- /**
- * Set the rules.
- * @param ruleString the rules string.
- */
- public static void setRules(String ruleString) {
- rules = (ruleString != null) ? parseRules(ruleString) : null;
+ if (ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) {
+ throw new NoMatchingRule("No rules applied to " + toString());
+ }
+ return toString();
}
/**
@@ -434,6 +456,47 @@
return rules != null;
}
+ /**
+ * Indicates of the rule mechanism has been set
+ *
+ * @return if the rule mechanism has been set.
+ */
+ public static boolean hasRuleMechanismBeenSet() {
+ return ruleMechanism != null;
+ }
+
+ /**
+ * Set the rules.
+ * @param ruleString the rules string.
+ */
+ public static void setRules(String ruleString) {
+ rules = (ruleString != null) ? parseRules(ruleString) : null;
+ }
+
+ /**
+ *
+ * @param ruleMech the evaluation type: hadoop, mit
+ * 'hadoop' indicates '@' or '/' are not allowed the result
+ * evaluation. 'MIT' indicates that auth_to_local
+ * rules follow MIT Kerberos evaluation.
+ */
+ public static void setRuleMechanism(String ruleMech) {
+ if (ruleMech != null
+ && (!ruleMech.equalsIgnoreCase(MECHANISM_HADOOP)
+ && !ruleMech.equalsIgnoreCase(MECHANISM_MIT))) {
+ throw new IllegalArgumentException("Invalid rule mechanism: " + ruleMech);
+ }
+ ruleMechanism = ruleMech;
+ }
+
+ /**
+ * Get the rule evaluation mechanism
+ * @return the rule evaluation mechanism
+ */
+ public static String getRuleMechanism() {
+ return ruleMechanism;
+ }
+
static void printRules() throws IOException {
int i = 0;
for(Rule r: rules) {
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java
index 4aabb34..177bcb4 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java
@@ -65,6 +65,7 @@
props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
+ props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM, "hadoop");
return props;
}
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
index 8b4bc15..6d60eae 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
@@ -65,6 +65,8 @@
KerberosTestUtils.getKeytabFile());
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
+ props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM,
+ KerberosName.MECHANISM_HADOOP);
return props;
}
@@ -89,18 +91,18 @@
}
@Test(timeout=60000)
- public void testNameRules() throws Exception {
+ public void testNameRulesHadoop() throws Exception {
KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
//destroy handler created in setUp()
handler.destroy();
-
- KerberosName.setRules("RULE:[1:$1@$0](.*@FOO)s/@.*//\nDEFAULT");
-
handler = getNewAuthenticationHandler();
+
Properties props = getDefaultProperties();
- props.setProperty(KerberosAuthenticationHandler.NAME_RULES, "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
+ props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
+ "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
+
try {
handler.init(props);
} catch (Exception ex) {
@@ -116,7 +118,55 @@
}
}
- @Test(timeout=60000)
+ @Test
+ public void testNameRulesCompat() throws Exception {
+ KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
+ Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
+
+ //destroy handler created in setUp()
+ handler.destroy();
+ handler = getNewAuthenticationHandler();
+
+ Properties props = getDefaultProperties();
+ props.setProperty(KerberosAuthenticationHandler.NAME_RULES, "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
+ props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM, KerberosName.MECHANISM_MIT);
+
+ try {
+ handler.init(props);
+ } catch (Exception ex) {
+ }
+ kn = new KerberosName("bar@BAR");
+ Assert.assertEquals("bar", kn.getShortName());
+ kn = new KerberosName("bar@FOO");
+ Assert.assertEquals("bar@FOO", kn.getShortName());
+ }
+
+ @Test
+ public void testNullProperties() throws Exception {
+ KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
+ Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
+
+ KerberosName.setRuleMechanism("MIT");
+ KerberosName.setRules("DEFAULT");
+
+ //destroy handler created in setUp()
+ handler.destroy();
+ handler = getNewAuthenticationHandler();
+
+ Properties props = getDefaultProperties();
+ props.remove(KerberosAuthenticationHandler.NAME_RULES);
+ props.remove(KerberosAuthenticationHandler.RULE_MECHANISM);
+
+ try {
+ handler.init(props);
+ } catch (Exception ex) {
+ }
+
+ Assert.assertEquals("MIT", KerberosName.getRuleMechanism());
+ Assert.assertEquals("DEFAULT", KerberosName.getRules());
+ }
+
+ @Test
public void testInit() throws Exception {
Assert.assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab());
Set<KerberosPrincipal> principals = handler.getPrincipals();
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java
index 2db0df4..105fa11 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java
@@ -40,6 +40,7 @@
"RULE:[2:$1;$2](^.*;admin$)s/;admin$//\n" +
"RULE:[2:$2](root)\n" +
"DEFAULT";
+ KerberosName.setRuleMechanism(KerberosName.MECHANISM_HADOOP);
KerberosName.setRules(rules);
KerberosName.printRules();
}
@@ -85,10 +86,16 @@
@Test
public void testAntiPatterns() throws Exception {
+ KerberosName.setRuleMechanism(KerberosName.MECHANISM_HADOOP);
checkBadName("owen/owen/owen@FOO.COM");
checkBadName("owen@foo/bar.com");
+
checkBadTranslation("foo@ACME.COM");
checkBadTranslation("root/joe@FOO.COM");
+
+ KerberosName.setRuleMechanism(KerberosName.MECHANISM_MIT);
+ checkTranslation("foo@ACME.COM", "foo@ACME.COM");
+ checkTranslation("root/joe@FOO.COM", "root/joe@FOO.COM");
}
@Test
@@ -129,6 +136,11 @@
checkTranslation("Joe/guestguest@FOO.COM", "joe");
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidRuleMechanism() throws Exception {
+ KerberosName.setRuleMechanism("INVALID_MECHANISM");
+ }
+
@After
public void clear() {
System.clearProperty("java.security.krb5.realm");
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
index 8523423..d910cdd 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
@@ -607,6 +607,13 @@
* <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
* core-default.xml</a>
*/
+ public static final String HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM =
+ "hadoop.security.auth_to_local.mechanism";
+ /**
+ * @see
+ * <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
+ * core-default.xml</a>
+ */
public static final String HADOOP_SECURITY_DNS_INTERFACE_KEY =
"hadoop.security.dns.interface";
/**
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java
index d2ba469..4b67b63 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java
@@ -1126,7 +1126,6 @@
params.put("kerberos.keytab", httpKeytab);
}
params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
-
defineFilter(webAppContext, SPNEGO_FILTER,
AuthenticationFilter.class.getName(), params, null);
}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java
index 55b2786..df96c50 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java
@@ -19,6 +19,7 @@
package org.apache.hadoop.security;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM;
import java.io.IOException;
@@ -27,6 +28,9 @@
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.hadoop.security.authentication.util.KerberosUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* This class implements parsing and handling of Kerberos principal names. In
* particular, it splits them apart and translates them down into local
@@ -36,6 +40,8 @@
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
@InterfaceStability.Evolving
public class HadoopKerberosName extends KerberosName {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(HadoopKerberosName.class);
/**
* Create a name from the full Kerberos principal name.
@@ -45,8 +51,8 @@
super(name);
}
/**
- * Set the static configuration to get the rules.
- * <p/>
+ * Set the static configuration to get and evaluate the rules.
+ * <p>
* IMPORTANT: This method does a NOP if the rules have been set already.
* If there is a need to reset the rules, the {@link KerberosName#setRules(String)}
* method should be invoked directly.
@@ -73,6 +79,9 @@
}
String ruleString = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL, defaultRule);
setRules(ruleString);
+
+ String ruleMechanism = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, DEFAULT_MECHANISM);
+ setRuleMechanism(ruleMechanism);
}
public static void main(String[] args) throws Exception {
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
index 1711921..2c6de4d 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
@@ -22,6 +22,7 @@
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.TokenIdentifier;
import org.apache.hadoop.util.ExitUtil;
@@ -54,6 +55,7 @@
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.regex.Pattern;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.*;
import static org.apache.hadoop.security.UserGroupInformation.*;
@@ -129,6 +131,12 @@
private boolean nofail = false;
private boolean nologin = false;
private boolean jaas = false;
+ private boolean checkShortName = false;
+
+ /**
+ * A pattern that recognizes simple/non-simple names. Per KerberosName
+ */
+ private static final Pattern nonSimplePattern = Pattern.compile("[/@]");
/**
* Flag set to true if a {@link #verify(boolean, String, String, Object...)}
@@ -157,6 +165,8 @@
public static final String ARG_SECURE = "--secure";
+ public static final String ARG_VERIFYSHORTNAME = "--verifyshortname";
+
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
public KDiag(Configuration conf,
PrintWriter out,
@@ -200,6 +210,7 @@
nofail = popOption(ARG_NOFAIL, args);
jaas = popOption(ARG_JAAS, args);
nologin = popOption(ARG_NOLOGIN, args);
+ checkShortName = popOption(ARG_VERIFYSHORTNAME, args);
// look for list of resources
String resource;
@@ -245,7 +256,9 @@
+ arg(ARG_NOLOGIN, "", "Do not attempt to log in")
+ arg(ARG_OUTPUT, "<file>", "Write output to a file")
+ arg(ARG_RESOURCE, "<resource>", "Load an XML configuration resource")
- + arg(ARG_SECURE, "", "Require the hadoop configuration to be secure");
+ + arg(ARG_SECURE, "", "Require the hadoop configuration to be secure")
+ + arg(ARG_VERIFYSHORTNAME, ARG_PRINCIPAL + " <principal>",
+ "Verify the short name of the specific principal does not contain '@' or '/'");
}
private String arg(String name, String params, String meaning) {
@@ -278,6 +291,7 @@
println("%s = %d", ARG_KEYLEN, minKeyLength);
println("%s = %s", ARG_KEYTAB, keytab);
println("%s = %s", ARG_PRINCIPAL, principal);
+ println("%s = %s", ARG_VERIFYSHORTNAME, checkShortName);
// Fail fast on a JVM without JCE installed.
validateKeyLength();
@@ -376,6 +390,9 @@
validateKinitExecutable();
validateJAAS(jaas);
validateNTPConf();
+ if (checkShortName) {
+ validateShortName();
+ }
if (!nologin) {
title("Logging in");
@@ -431,6 +448,32 @@
}
/**
+ * Verify whether auth_to_local rules transform a principal name
+ * <p>
+ * Having a local user name "bar@foo.com" may be harmless, so it is noted at
+ * info. However if what was intended is a transformation to "bar"
+ * it can be difficult to debug, hence this check.
+ */
+ protected void validateShortName() {
+ failif(principal == null, CAT_KERBEROS, "No principal defined");
+
+ try {
+ KerberosName kn = new KerberosName(principal);
+ String result = kn.getShortName();
+ if (nonSimplePattern.matcher(result).find()) {
+ warn(CAT_KERBEROS, principal + " short name: " + result +
+ " still contains @ or /");
+ }
+ } catch (IOException e) {
+ throw new KerberosDiagsFailure(CAT_KERBEROS, e,
+ "Failed to get short name for " + principal, e);
+ } catch (IllegalArgumentException e) {
+ error(CAT_KERBEROS, "KerberosName(" + principal + ") failed: %s\n%s",
+ e, StringUtils.stringifyException(e));
+ }
+ }
+
+ /**
* Get the default realm.
* <p>
* Not having a default realm may be harmless, so is noted at info.
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index a94ff78..4c24906 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -663,6 +663,15 @@
</property>
<property>
+ <name>hadoop.security.auth_to_local.mechanism</name>
+ <value>hadoop</value>
+ <description>The mechanism by which auth_to_local rules are evaluated.
+ If set to 'hadoop' it will not allow resulting local user names to have
+ either '@' or '/'. If set to 'MIT' it will follow MIT evaluation rules
+ and the restrictions of 'hadoop' do not apply.</description>
+</property>
+
+<property>
<name>hadoop.token.files</name>
<value></value>
<description>List of token cache files that have delegation tokens for hadoop service</description>
diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md b/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md
index 09f1eed..856861f 100644
--- a/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md
+++ b/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md
@@ -133,22 +133,35 @@
### Mapping from Kerberos principals to OS user accounts
-Hadoop maps Kerberos principals to OS user (system) accounts using rules specified by `hadoop.security.auth_to_local`. These rules work in the same way as the `auth_to_local` in [Kerberos configuration file (krb5.conf)](http://web.mit.edu/Kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html). In addition, Hadoop `auth_to_local` mapping supports the **/L** flag that lowercases the returned name.
+Hadoop maps Kerberos principals to OS user (system) accounts using rules specified by `hadoop.security.auth_to_local`. How Hadoop evaluates these rules is determined by the setting of `hadoop.security.auth_to_local.mechanism`.
-The default is to pick the first component of the principal name as the system user name if the realm matches the `default_realm` (usually defined in /etc/krb5.conf). e.g. The default rule maps the principal `host/full.qualified.domain.name@REALM.TLD` to system user `host`. The default rule will *not be appropriate* for most clusters.
+In the default `hadoop` mode a Kerberos principal *must* be matched against a rule that transforms the principal to a simple form, i.e. a user account name without '@' or '/', otherwise a principal will not be authorized and a error will be logged. In case of the `MIT` mode the rules work in the same way as the `auth_to_local` in [Kerberos configuration file (krb5.conf)](http://web.mit.edu/Kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html) and the restrictions of `hadoop` mode do *not* apply. If you use `MIT` mode it is suggested to use the same `auth_to_local` rules that are specified in your /etc/krb5.conf as part of your default realm and keep them in sync. In both `hadoop` and `MIT` mode the rules are being applied (with the exception of `DEFAULT`) to *all* principals regardless of their specified realm. Also, note you should *not* rely on the `auth_to_local` rules as an ACL and use proper (OS) mechanisms.
+Possible values for `auth_to_local` are:
+
+* `RULE:exp` The local name will be formulated from exp. The format for exp is `[n:string](regexp)s/pattern/replacement/g`. The integer n indicates how many components the target principal should have. If this matches, then a string will be formed from string, substituting the realm of the principal for `$0` and the n’th component of the principal for `$n` (e.g., if the principal was johndoe/admin then `[2:$2$1foo]` would result in the string `adminjohndoefoo`). If this string matches regexp, then the `s//[g]` substitution command will be run over the string. The optional g will cause the substitution to be global over the string, instead of replacing only the first match in the string. As an extension to MIT, Hadoop `auth_to_local` mapping supports the **/L** flag that lowercases the returned name.
+
+* `DEFAULT` Picks the first component of the principal name as the system user name if and only if the realm matches the `default_realm` (usually defined in /etc/krb5.conf). e.g. The default rule maps the principal `host/full.qualified.domain.name@MYREALM.TLD` to system user `host` if the default realm is `MYREALM.TLD`.
+
+In case no rules are specified Hadoop defaults to using `DEFAULT`, which is probably *not suitable* to most of the clusters.
+
+Please note that Hadoop does not support multiple default realms (e.g like Heimdal does). Also, Hadoop does not do a verification on mapping whether a local system account exists.
+
+### Example rules
In a typical cluster HDFS and YARN services will be launched as the system `hdfs` and `yarn` users respectively. `hadoop.security.auth_to_local` can be configured as follows:
<property>
<name>hadoop.security.auth_to_local</name>
<value>
- RULE:[2:$1/$2@$0]([ndj]n/.*@REALM.TLD)s/.*/hdfs/
- RULE:[2:$1/$2@$0]([rn]m/.*@REALM.TLD)s/.*/yarn/
- RULE:[2:$1/$2@$0](jhs/.*@REALM.TLD)s/.*/mapred/
+ RULE:[2:$1/$2@$0]([ndj]n/.*@REALM.\TLD)s/.*/hdfs/
+ RULE:[2:$1/$2@$0]([rn]m/.*@REALM\.TLD)s/.*/yarn/
+ RULE:[2:$1/$2@$0](jhs/.*@REALM\.TLD)s/.*/mapred/
DEFAULT
</value>
</property>
+This would map any principal `nn, dn, jn` on any `host` from realm `REALM.TLD` to the local system account `hdfs`. Secondly it would map any principal `rm, nm` on any `host` from `REALM.TLD` to the local system account `yarn`. Thirdly, it would map the principal `jhs` on any `host` from realm `REALM.TLD` to the local system account `mapred`. Finally, any principal on any host from the default realm will be mapped to the user component of that principal.
+
Custom rules can be tested using the `hadoop kerbname` command. This command allows one to specify a principal and apply Hadoop's current `auth_to_local` ruleset.
### Mapping from user to group
@@ -470,6 +483,7 @@
[--out <file>] : Write output to a file.
[--resource <resource>] : Load an XML configuration resource.
[--secure] : Require the hadoop configuration to be secure.
+ [--verifyshortname <principal>]: Verify the short name of the specific principal does not contain '@' or '/'
```
#### `--jaas`: Require a JAAS file to be defined in `java.security.auth.login.config`.
@@ -565,6 +579,11 @@
Needless to say, an application so configured cannot talk to a secure Hadoop cluster.
+#### `--verifyshortname <principal>`: validate the short name of a principal
+
+This verifies that the short name of a principal contains neither the `"@"`
+nor `"/"` characters.
+
### Example
```
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java
index 3895ae1..e395566 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java
@@ -165,6 +165,22 @@
}
@Test
+ public void testKerberosName() throws Throwable {
+ kdiagFailure(ARG_KEYLEN, KEYLEN,
+ ARG_VERIFYSHORTNAME,
+ ARG_PRINCIPAL, "foo/foo/foo@BAR.COM");
+ }
+
+ @Test
+ public void testShortName() throws Throwable {
+ kdiag(ARG_KEYLEN, KEYLEN,
+ ARG_KEYTAB, keytab.getAbsolutePath(),
+ ARG_PRINCIPAL,
+ ARG_VERIFYSHORTNAME,
+ ARG_PRINCIPAL, "foo@EXAMPLE.COM");
+ }
+
+ @Test
public void testFileOutput() throws Throwable {
File f = new File("target/kdiag.txt");
kdiag(ARG_KEYLEN, KEYLEN,
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java
index ce10067..cf2a8b6 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java
@@ -74,6 +74,7 @@
import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM;
import static org.apache.hadoop.test.MetricsAsserts.assertCounter;
import static org.apache.hadoop.test.MetricsAsserts.assertCounterGt;
import static org.apache.hadoop.test.MetricsAsserts.assertGaugeGt;
@@ -329,6 +330,7 @@
// security off, but use rules if explicitly set
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL,
"RULE:[1:$1@$0](.*@OTHER.REALM)s/(.*)@.*/other-$1/");
+ conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
UserGroupInformation.setConfiguration(conf);
testConstructorSuccess("user1", "user1");
testConstructorSuccess("user4@OTHER.REALM", "other-user4");
@@ -336,25 +338,52 @@
testConstructorFailures("user2@DEFAULT.REALM");
testConstructorFailures("user3/cron@DEFAULT.REALM");
testConstructorFailures("user5/cron@OTHER.REALM");
+
+ // with MIT
+ conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "mit");
+ UserGroupInformation.setConfiguration(conf);
+ testConstructorSuccess("user2@DEFAULT.REALM", "user2@DEFAULT.REALM");
+ testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3/cron@DEFAULT.REALM");
+ testConstructorSuccess("user5/cron@OTHER.REALM", "user5/cron@OTHER.REALM");
+
+ // failures
+ testConstructorFailures("user6@example.com@OTHER.REALM");
+ testConstructorFailures("user7@example.com@DEFAULT.REALM");
testConstructorFailures(null);
testConstructorFailures("");
+
+ conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
+
}
/** test constructor */
@Test (timeout = 30000)
public void testConstructorWithKerberos() throws Exception {
// security on, default is remove default realm
+ conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf);
UserGroupInformation.setConfiguration(conf);
testConstructorSuccess("user1", "user1");
testConstructorSuccess("user2@DEFAULT.REALM", "user2");
- testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3");
+ testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3");
+
// failure test
testConstructorFailures("user4@OTHER.REALM");
testConstructorFailures("user5/cron@OTHER.REALM");
+
+ // with MIT
+ conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "mit");
+ UserGroupInformation.setConfiguration(conf);
+ testConstructorSuccess("user4@OTHER.REALM", "user4@OTHER.REALM");
+ testConstructorSuccess("user5/cron@OTHER.REALM", "user5/cron@OTHER.REALM");
+
+ // failures
testConstructorFailures(null);
testConstructorFailures("");
+
+ conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
+
}
/** test constructor */
@@ -393,8 +422,9 @@
} catch (IllegalArgumentException e) {
String expect = (userName == null || userName.isEmpty())
? "Null user" : "Illegal principal name "+userName;
- assertTrue("Did not find "+ expect + " in " + e,
- e.toString().contains(expect));
+ String expect2 = "Malformed Kerberos name: "+userName;
+ assertTrue("Did not find "+ expect + " or " + expect2 + " in " + e,
+ e.toString().contains(expect) || e.toString().contains(expect2));
}
}