HADOOP-15996. Improved Kerberos username mapping strategy in Hadoop.
Contributed by Bolke de Bruin
This commit is contained in:
parent
6e35f7130f
commit
d43af8b3db
@ -88,6 +88,12 @@ public class KerberosAuthenticationHandler implements AuthenticationHandler {
|
|||||||
*/
|
*/
|
||||||
public static final String NAME_RULES = TYPE + ".name.rules";
|
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 type;
|
||||||
private String keytab;
|
private String keytab;
|
||||||
private GSSManager gssManager;
|
private GSSManager gssManager;
|
||||||
@ -163,7 +169,10 @@ public void init(Properties config) throws ServletException {
|
|||||||
if (nameRules != null) {
|
if (nameRules != null) {
|
||||||
KerberosName.setRules(nameRules);
|
KerberosName.setRules(nameRules);
|
||||||
}
|
}
|
||||||
|
String ruleMechanism = config.getProperty(RULE_MECHANISM, null);
|
||||||
|
if (ruleMechanism != null) {
|
||||||
|
KerberosName.setRuleMechanism(ruleMechanism);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
gssManager = Subject.doAs(serverSubject,
|
gssManager = Subject.doAs(serverSubject,
|
||||||
new PrivilegedExceptionAction<GSSManager>() {
|
new PrivilegedExceptionAction<GSSManager>() {
|
||||||
|
@ -44,6 +44,19 @@
|
|||||||
public class KerberosName {
|
public class KerberosName {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(KerberosName.class);
|
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 */
|
/** The first component of the name */
|
||||||
private final String serviceName;
|
private final String serviceName;
|
||||||
/** The second component of the name. It may be null. */
|
/** The second component of the name. It may be null. */
|
||||||
@ -81,6 +94,11 @@ public class KerberosName {
|
|||||||
*/
|
*/
|
||||||
private static List<Rule> rules;
|
private static List<Rule> rules;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How to evaluate auth_to_local rules
|
||||||
|
*/
|
||||||
|
private static String ruleMechanism = null;
|
||||||
|
|
||||||
private static String defaultRealm = null;
|
private static String defaultRealm = null;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@ -304,10 +322,11 @@ static String replaceSubstitution(String base, Pattern from, String to,
|
|||||||
* array.
|
* array.
|
||||||
* @param params first element is the realm, second and later elements are
|
* @param params first element is the realm, second and later elements are
|
||||||
* are the components of the name "a/b@FOO" -> {"FOO", "a", "b"}
|
* 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
|
* @return the short name if this rule applies or null
|
||||||
* @throws IOException throws if something is wrong with the rules
|
* @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;
|
String result = null;
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
if (getDefaultRealm().equals(params[0])) {
|
if (getDefaultRealm().equals(params[0])) {
|
||||||
@ -323,7 +342,9 @@ String apply(String[] params) throws IOException {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 +
|
throw new NoMatchingRule("Non-simple name " + result +
|
||||||
" after auth_to_local rule " + this);
|
" after auth_to_local rule " + this);
|
||||||
}
|
}
|
||||||
@ -392,21 +413,22 @@ public String getShortName() throws IOException {
|
|||||||
} else {
|
} else {
|
||||||
params = new String[]{realm, serviceName, hostName};
|
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) {
|
for(Rule r: rules) {
|
||||||
String result = r.apply(params);
|
String result = r.apply(params, ruleMechanism);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new NoMatchingRule("No rules applied to " + toString());
|
if (ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) {
|
||||||
}
|
throw new NoMatchingRule("No rules applied to " + toString());
|
||||||
|
}
|
||||||
/**
|
return toString();
|
||||||
* Set the rules.
|
|
||||||
* @param ruleString the rules string.
|
|
||||||
*/
|
|
||||||
public static void setRules(String ruleString) {
|
|
||||||
rules = (ruleString != null) ? parseRules(ruleString) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -434,6 +456,47 @@ public static boolean hasRulesBeenSet() {
|
|||||||
return rules != null;
|
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 {
|
static void printRules() throws IOException {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(Rule r: rules) {
|
for(Rule r: rules) {
|
||||||
|
@ -65,6 +65,7 @@ private Properties getAuthenticationHandlerConfiguration() {
|
|||||||
props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
|
props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
|
||||||
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
|
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
|
||||||
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
|
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
|
||||||
|
props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM, "hadoop");
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +70,8 @@ protected Properties getDefaultProperties() {
|
|||||||
KerberosTestUtils.getKeytabFile());
|
KerberosTestUtils.getKeytabFile());
|
||||||
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
|
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
|
||||||
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
|
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
|
||||||
|
props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM,
|
||||||
|
KerberosName.MECHANISM_HADOOP);
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,19 +98,18 @@ public void setup() throws Exception {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNameRules() throws Exception {
|
public void testNameRulesHadoop() throws Exception {
|
||||||
KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
|
KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
|
||||||
Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
|
Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
|
||||||
|
|
||||||
//destroy handler created in setUp()
|
//destroy handler created in setUp()
|
||||||
handler.destroy();
|
handler.destroy();
|
||||||
|
|
||||||
KerberosName.setRules("RULE:[1:$1@$0](.*@FOO)s/@.*//\nDEFAULT");
|
|
||||||
|
|
||||||
handler = getNewAuthenticationHandler();
|
handler = getNewAuthenticationHandler();
|
||||||
|
|
||||||
Properties props = getDefaultProperties();
|
Properties props = getDefaultProperties();
|
||||||
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
|
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
|
||||||
"RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
|
"RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
handler.init(props);
|
handler.init(props);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -124,7 +125,55 @@ public void testNameRules() throws Exception {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInit() {
|
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());
|
Assert.assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab());
|
||||||
Set<KerberosPrincipal> principals = handler.getPrincipals();
|
Set<KerberosPrincipal> principals = handler.getPrincipals();
|
||||||
Principal expectedPrincipal =
|
Principal expectedPrincipal =
|
||||||
|
@ -40,6 +40,7 @@ public void setUp() throws Exception {
|
|||||||
"RULE:[2:$1;$2](^.*;admin$)s/;admin$//\n" +
|
"RULE:[2:$1;$2](^.*;admin$)s/;admin$//\n" +
|
||||||
"RULE:[2:$2](root)\n" +
|
"RULE:[2:$2](root)\n" +
|
||||||
"DEFAULT";
|
"DEFAULT";
|
||||||
|
KerberosName.setRuleMechanism(KerberosName.MECHANISM_HADOOP);
|
||||||
KerberosName.setRules(rules);
|
KerberosName.setRules(rules);
|
||||||
KerberosName.printRules();
|
KerberosName.printRules();
|
||||||
}
|
}
|
||||||
@ -85,10 +86,16 @@ private void checkBadTranslation(String from) {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAntiPatterns() throws Exception {
|
public void testAntiPatterns() throws Exception {
|
||||||
|
KerberosName.setRuleMechanism(KerberosName.MECHANISM_HADOOP);
|
||||||
checkBadName("owen/owen/owen@FOO.COM");
|
checkBadName("owen/owen/owen@FOO.COM");
|
||||||
checkBadName("owen@foo/bar.com");
|
checkBadName("owen@foo/bar.com");
|
||||||
|
|
||||||
checkBadTranslation("foo@ACME.COM");
|
checkBadTranslation("foo@ACME.COM");
|
||||||
checkBadTranslation("root/joe@FOO.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
|
@Test
|
||||||
@ -129,6 +136,11 @@ public void testToLowerCase() throws Exception {
|
|||||||
checkTranslation("Joe/guestguest@FOO.COM", "joe");
|
checkTranslation("Joe/guestguest@FOO.COM", "joe");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testInvalidRuleMechanism() throws Exception {
|
||||||
|
KerberosName.setRuleMechanism("INVALID_MECHANISM");
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void clear() {
|
public void clear() {
|
||||||
System.clearProperty("java.security.krb5.realm");
|
System.clearProperty("java.security.krb5.realm");
|
||||||
|
@ -607,6 +607,13 @@ public class CommonConfigurationKeysPublic {
|
|||||||
* <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
|
* <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
|
||||||
* core-default.xml</a>
|
* 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 =
|
public static final String HADOOP_SECURITY_DNS_INTERFACE_KEY =
|
||||||
"hadoop.security.dns.interface";
|
"hadoop.security.dns.interface";
|
||||||
/**
|
/**
|
||||||
|
@ -1129,7 +1129,6 @@ private void initSpnego(Configuration conf, String hostName,
|
|||||||
params.put("kerberos.keytab", httpKeytab);
|
params.put("kerberos.keytab", httpKeytab);
|
||||||
}
|
}
|
||||||
params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
|
params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
|
||||||
|
|
||||||
defineFilter(webAppContext, SPNEGO_FILTER,
|
defineFilter(webAppContext, SPNEGO_FILTER,
|
||||||
AuthenticationFilter.class.getName(), params, null);
|
AuthenticationFilter.class.getName(), params, null);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package org.apache.hadoop.security;
|
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;
|
||||||
|
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -27,6 +28,9 @@
|
|||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.security.authentication.util.KerberosName;
|
import org.apache.hadoop.security.authentication.util.KerberosName;
|
||||||
import org.apache.hadoop.security.authentication.util.KerberosUtil;
|
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
|
* This class implements parsing and handling of Kerberos principal names. In
|
||||||
* particular, it splits them apart and translates them down into local
|
* particular, it splits them apart and translates them down into local
|
||||||
@ -36,6 +40,8 @@
|
|||||||
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
|
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
|
||||||
@InterfaceStability.Evolving
|
@InterfaceStability.Evolving
|
||||||
public class HadoopKerberosName extends KerberosName {
|
public class HadoopKerberosName extends KerberosName {
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(HadoopKerberosName.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a name from the full Kerberos principal name.
|
* Create a name from the full Kerberos principal name.
|
||||||
@ -45,7 +51,7 @@ public HadoopKerberosName(String name) {
|
|||||||
super(name);
|
super(name);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Set the static configuration to get the rules.
|
* Set the static configuration to get and evaluate the rules.
|
||||||
* <p>
|
* <p>
|
||||||
* IMPORTANT: This method does a NOP if the rules have been set already.
|
* 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)}
|
* If there is a need to reset the rules, the {@link KerberosName#setRules(String)}
|
||||||
@ -73,6 +79,9 @@ public static void setConfiguration(Configuration conf) throws IOException {
|
|||||||
}
|
}
|
||||||
String ruleString = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL, defaultRule);
|
String ruleString = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL, defaultRule);
|
||||||
setRules(ruleString);
|
setRules(ruleString);
|
||||||
|
|
||||||
|
String ruleMechanism = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, DEFAULT_MECHANISM);
|
||||||
|
setRuleMechanism(ruleMechanism);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.conf.Configured;
|
import org.apache.hadoop.conf.Configured;
|
||||||
import org.apache.hadoop.io.Text;
|
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.Token;
|
||||||
import org.apache.hadoop.security.token.TokenIdentifier;
|
import org.apache.hadoop.security.token.TokenIdentifier;
|
||||||
import org.apache.hadoop.util.ExitUtil;
|
import org.apache.hadoop.util.ExitUtil;
|
||||||
@ -54,6 +55,7 @@
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.*;
|
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.*;
|
||||||
import static org.apache.hadoop.security.UserGroupInformation.*;
|
import static org.apache.hadoop.security.UserGroupInformation.*;
|
||||||
@ -129,6 +131,12 @@ public class KDiag extends Configured implements Tool, Closeable {
|
|||||||
private boolean nofail = false;
|
private boolean nofail = false;
|
||||||
private boolean nologin = false;
|
private boolean nologin = false;
|
||||||
private boolean jaas = 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...)}
|
* Flag set to true if a {@link #verify(boolean, String, String, Object...)}
|
||||||
@ -157,6 +165,8 @@ public class KDiag extends Configured implements Tool, Closeable {
|
|||||||
|
|
||||||
public static final String ARG_SECURE = "--secure";
|
public static final String ARG_SECURE = "--secure";
|
||||||
|
|
||||||
|
public static final String ARG_VERIFYSHORTNAME = "--verifyshortname";
|
||||||
|
|
||||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||||
public KDiag(Configuration conf,
|
public KDiag(Configuration conf,
|
||||||
PrintWriter out,
|
PrintWriter out,
|
||||||
@ -200,6 +210,7 @@ public int run(String[] argv) throws Exception {
|
|||||||
nofail = popOption(ARG_NOFAIL, args);
|
nofail = popOption(ARG_NOFAIL, args);
|
||||||
jaas = popOption(ARG_JAAS, args);
|
jaas = popOption(ARG_JAAS, args);
|
||||||
nologin = popOption(ARG_NOLOGIN, args);
|
nologin = popOption(ARG_NOLOGIN, args);
|
||||||
|
checkShortName = popOption(ARG_VERIFYSHORTNAME, args);
|
||||||
|
|
||||||
// look for list of resources
|
// look for list of resources
|
||||||
String resource;
|
String resource;
|
||||||
@ -245,7 +256,9 @@ private String usage() {
|
|||||||
+ arg(ARG_NOLOGIN, "", "Do not attempt to log in")
|
+ arg(ARG_NOLOGIN, "", "Do not attempt to log in")
|
||||||
+ arg(ARG_OUTPUT, "<file>", "Write output to a file")
|
+ arg(ARG_OUTPUT, "<file>", "Write output to a file")
|
||||||
+ arg(ARG_RESOURCE, "<resource>", "Load an XML configuration resource")
|
+ 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) {
|
private String arg(String name, String params, String meaning) {
|
||||||
@ -278,6 +291,7 @@ public boolean execute() throws Exception {
|
|||||||
println("%s = %d", ARG_KEYLEN, minKeyLength);
|
println("%s = %d", ARG_KEYLEN, minKeyLength);
|
||||||
println("%s = %s", ARG_KEYTAB, keytab);
|
println("%s = %s", ARG_KEYTAB, keytab);
|
||||||
println("%s = %s", ARG_PRINCIPAL, principal);
|
println("%s = %s", ARG_PRINCIPAL, principal);
|
||||||
|
println("%s = %s", ARG_VERIFYSHORTNAME, checkShortName);
|
||||||
|
|
||||||
// Fail fast on a JVM without JCE installed.
|
// Fail fast on a JVM without JCE installed.
|
||||||
validateKeyLength();
|
validateKeyLength();
|
||||||
@ -376,6 +390,9 @@ public boolean execute() throws Exception {
|
|||||||
validateKinitExecutable();
|
validateKinitExecutable();
|
||||||
validateJAAS(jaas);
|
validateJAAS(jaas);
|
||||||
validateNTPConf();
|
validateNTPConf();
|
||||||
|
if (checkShortName) {
|
||||||
|
validateShortName();
|
||||||
|
}
|
||||||
|
|
||||||
if (!nologin) {
|
if (!nologin) {
|
||||||
title("Logging in");
|
title("Logging in");
|
||||||
@ -430,6 +447,32 @@ protected void validateKeyLength() throws NoSuchAlgorithmException {
|
|||||||
aesLen, minKeyLength);
|
aesLen, minKeyLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Get the default realm.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -704,6 +704,15 @@
|
|||||||
<description>Maps kerberos principals to local user names</description>
|
<description>Maps kerberos principals to local user names</description>
|
||||||
</property>
|
</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>
|
<property>
|
||||||
<name>hadoop.token.files</name>
|
<name>hadoop.token.files</name>
|
||||||
<value></value>
|
<value></value>
|
||||||
|
@ -133,22 +133,35 @@ The MapReduce JobHistory Server keytab file, on that host, should look like the
|
|||||||
|
|
||||||
### Mapping from Kerberos principals to OS user accounts
|
### 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:
|
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>
|
<property>
|
||||||
<name>hadoop.security.auth_to_local</name>
|
<name>hadoop.security.auth_to_local</name>
|
||||||
<value>
|
<value>
|
||||||
RULE:[2:$1/$2@$0]([ndj]n/.*@REALM.TLD)s/.*/hdfs/
|
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]([rn]m/.*@REALM\.TLD)s/.*/yarn/
|
||||||
RULE:[2:$1/$2@$0](jhs/.*@REALM.TLD)s/.*/mapred/
|
RULE:[2:$1/$2@$0](jhs/.*@REALM\.TLD)s/.*/mapred/
|
||||||
DEFAULT
|
DEFAULT
|
||||||
</value>
|
</value>
|
||||||
</property>
|
</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.
|
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
|
### Mapping from user to group
|
||||||
@ -470,6 +483,7 @@ KDiag: Diagnose Kerberos Problems
|
|||||||
[--out <file>] : Write output to a file.
|
[--out <file>] : Write output to a file.
|
||||||
[--resource <resource>] : Load an XML configuration resource.
|
[--resource <resource>] : Load an XML configuration resource.
|
||||||
[--secure] : Require the hadoop configuration to be secure.
|
[--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`.
|
#### `--jaas`: Require a JAAS file to be defined in `java.security.auth.login.config`.
|
||||||
@ -565,6 +579,11 @@ or implicitly set to "simple":
|
|||||||
|
|
||||||
Needless to say, an application so configured cannot talk to a secure Hadoop cluster.
|
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
|
### Example
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -164,6 +164,22 @@ public void testKeytabAndPrincipal() throws Throwable {
|
|||||||
ARG_PRINCIPAL, "foo@EXAMPLE.COM");
|
ARG_PRINCIPAL, "foo@EXAMPLE.COM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void testFileOutput() throws Throwable {
|
public void testFileOutput() throws Throwable {
|
||||||
File f = new File("target/kdiag.txt");
|
File f = new File("target/kdiag.txt");
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
|
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_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;
|
||||||
|
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.assertCounter;
|
||||||
import static org.apache.hadoop.test.MetricsAsserts.assertCounterGt;
|
import static org.apache.hadoop.test.MetricsAsserts.assertCounterGt;
|
||||||
import static org.apache.hadoop.test.MetricsAsserts.assertGaugeGt;
|
import static org.apache.hadoop.test.MetricsAsserts.assertGaugeGt;
|
||||||
@ -329,6 +330,7 @@ public void testConstructorWithRules() throws Exception {
|
|||||||
// security off, but use rules if explicitly set
|
// security off, but use rules if explicitly set
|
||||||
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL,
|
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL,
|
||||||
"RULE:[1:$1@$0](.*@OTHER.REALM)s/(.*)@.*/other-$1/");
|
"RULE:[1:$1@$0](.*@OTHER.REALM)s/(.*)@.*/other-$1/");
|
||||||
|
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
|
||||||
UserGroupInformation.setConfiguration(conf);
|
UserGroupInformation.setConfiguration(conf);
|
||||||
testConstructorSuccess("user1", "user1");
|
testConstructorSuccess("user1", "user1");
|
||||||
testConstructorSuccess("user4@OTHER.REALM", "other-user4");
|
testConstructorSuccess("user4@OTHER.REALM", "other-user4");
|
||||||
@ -336,25 +338,52 @@ public void testConstructorWithRules() throws Exception {
|
|||||||
testConstructorFailures("user2@DEFAULT.REALM");
|
testConstructorFailures("user2@DEFAULT.REALM");
|
||||||
testConstructorFailures("user3/cron@DEFAULT.REALM");
|
testConstructorFailures("user3/cron@DEFAULT.REALM");
|
||||||
testConstructorFailures("user5/cron@OTHER.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(null);
|
||||||
testConstructorFailures("");
|
testConstructorFailures("");
|
||||||
|
|
||||||
|
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** test constructor */
|
/** test constructor */
|
||||||
@Test (timeout = 30000)
|
@Test (timeout = 30000)
|
||||||
public void testConstructorWithKerberos() throws Exception {
|
public void testConstructorWithKerberos() throws Exception {
|
||||||
// security on, default is remove default realm
|
// security on, default is remove default realm
|
||||||
|
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
|
||||||
SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf);
|
SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf);
|
||||||
UserGroupInformation.setConfiguration(conf);
|
UserGroupInformation.setConfiguration(conf);
|
||||||
|
|
||||||
testConstructorSuccess("user1", "user1");
|
testConstructorSuccess("user1", "user1");
|
||||||
testConstructorSuccess("user2@DEFAULT.REALM", "user2");
|
testConstructorSuccess("user2@DEFAULT.REALM", "user2");
|
||||||
testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3");
|
testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3");
|
||||||
|
|
||||||
// failure test
|
// failure test
|
||||||
testConstructorFailures("user4@OTHER.REALM");
|
testConstructorFailures("user4@OTHER.REALM");
|
||||||
testConstructorFailures("user5/cron@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(null);
|
||||||
testConstructorFailures("");
|
testConstructorFailures("");
|
||||||
|
|
||||||
|
conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** test constructor */
|
/** test constructor */
|
||||||
@ -393,8 +422,9 @@ private void testConstructorFailures(String userName) {
|
|||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
String expect = (userName == null || userName.isEmpty())
|
String expect = (userName == null || userName.isEmpty())
|
||||||
? "Null user" : "Illegal principal name "+userName;
|
? "Null user" : "Illegal principal name "+userName;
|
||||||
assertTrue("Did not find "+ expect + " in " + e,
|
String expect2 = "Malformed Kerberos name: "+userName;
|
||||||
e.toString().contains(expect));
|
assertTrue("Did not find "+ expect + " or " + expect2 + " in " + e,
|
||||||
|
e.toString().contains(expect) || e.toString().contains(expect2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user