You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2015/01/22 23:16:43 UTC
[03/14] ambari git commit: AMBARI-9209. Add the ability to append a
random value to values in LDAP attributes when generating principals in
Active Directory (rlevas)
AMBARI-9209. Add the ability to append a random value to values in LDAP attributes when generating principals in Active Directory (rlevas)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/9f291484
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/9f291484
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/9f291484
Branch: refs/heads/2.0-preview
Commit: 9f291484ae3764975e3f3f2c616288454bca41c5
Parents: ae82067
Author: Robert Levas <rl...@hortonworks.com>
Authored: Wed Jan 21 11:20:30 2015 -0500
Committer: Yusaku Sako <yu...@hortonworks.com>
Committed: Wed Jan 21 12:21:26 2015 -0800
----------------------------------------------------------------------
.../kerberos/ADKerberosOperationHandler.java | 161 +++++-----
.../kerberos/DeconstructedPrincipal.java | 201 +++++++++++++
.../kerberos/KerberosOperationHandler.java | 10 +-
.../1.10.3-10/configuration/kerberos-env.xml | 16 +-
.../ADKerberosOperationHandlerTest.java | 301 +++++++++++--------
.../kerberos/DeconstructedPrincipalTest.java | 113 +++++++
6 files changed, 582 insertions(+), 220 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
index 20f7e60..b5de64f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
@@ -21,7 +21,7 @@ package org.apache.ambari.server.serveraction.kerberos;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
-import org.apache.ambari.server.utils.StageUtils;
+import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
@@ -42,8 +42,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Implementation of <code>KerberosOperationHandler</code> to created principal in Active Directory
@@ -52,15 +50,6 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
private static Log LOG = LogFactory.getLog(ADKerberosOperationHandler.class);
- /**
- * Regular expression to parse the different principal formats:
- * primary/instance@REALM
- * primary@REALM
- * primary/instance
- * primary
- */
- private static Pattern PATTERN_PRINCIPAL = Pattern.compile("^(([^ /@]+)(?:/([^ /@]+))?)(?:@(.+)?)?$");
-
private static final String LDAP_CONTEXT_FACTORY_CLASS = "com.sun.jndi.ldap.LdapCtxFactory";
public final static String KERBEROS_ENV_LDAP_URL = "ldap_url";
@@ -213,27 +202,14 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
if (principal == null) {
throw new KerberosOperationException("principal is null");
}
- NamingEnumeration<SearchResult> searchResultEnum = null;
+
+ DeconstructedPrincipal deconstructPrincipal = deconstructPrincipal(principal);
+
try {
- searchResultEnum = ldapContext.search(
- principalContainerDn,
- "(userPrincipalName=" + principal + ")",
- searchControls);
- if (searchResultEnum.hasMore()) {
- return true;
- }
+ return (findPrincipalDN(deconstructPrincipal.getNormalizedPrincipal()) != null);
} catch (NamingException ne) {
throw new KerberosOperationException("can not check if principal exists: " + principal, ne);
- } finally {
- try {
- if (searchResultEnum != null) {
- searchResultEnum.close();
- }
- } catch (NamingException ne) {
- // ignore, we can not do anything about it
- }
}
- return false;
}
/**
@@ -262,31 +238,24 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
}
// TODO: (rlevas) pass components and realm in separately (AMBARI-9122)
- String realm = null;
- String principal_primary = null;
- String principal_instance = null;
-
- Matcher matcher = PATTERN_PRINCIPAL.matcher(principal);
- if (matcher.matches()) {
- principal = matcher.group(1);
- principal_primary = matcher.group(2);
- principal_instance = matcher.group(3);
- realm = matcher.group(4);
- }
+ DeconstructedPrincipal deconstructedPrincipal = deconstructPrincipal(principal);
- if ((realm == null) || realm.isEmpty()) {
- realm = getDefaultRealm();
+ String realm = deconstructedPrincipal.getRealm();
+ if (realm == null) {
+ realm = "";
}
Map<String, Object> context = new HashMap<String, Object>();
- context.put("principal", principal);
- context.put("principal_primary", principal_primary);
- context.put("principal_instance", principal_instance);
+ context.put("normalized_principal", deconstructedPrincipal.getNormalizedPrincipal());
+ context.put("principal_name", deconstructedPrincipal.getPrincipalName());
+ context.put("principal_primary", deconstructedPrincipal.getPrimary());
+ context.put("principal_instance", deconstructedPrincipal.getInstance());
context.put("realm", realm);
- context.put("realm_lowercase", (realm == null) ? null : realm.toLowerCase());
+ context.put("realm_lowercase", realm.toLowerCase());
context.put("password", password);
context.put("is_service", service);
context.put("container_dn", this.principalContainerDn);
+ context.put("principal_digest", DigestUtils.sha1Hex(deconstructedPrincipal.getNormalizedPrincipal()));
Map<String, Object> data = processCreateTemplate(context);
@@ -300,13 +269,11 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
if ("unicodePwd".equals(key)) {
if (value instanceof String) {
- Attribute passwordAttr = new BasicAttribute("unicodePwd"); // password
try {
- passwordAttr.add(((String) value).getBytes("UTF-16LE"));
+ attributes.put(new BasicAttribute("unicodePwd", String.format("\"%s\"", password).getBytes("UTF-16LE")));
} catch (UnsupportedEncodingException ue) {
throw new KerberosOperationException("Can not encode password with UTF-16LE", ue);
}
- attributes.put(passwordAttr);
}
} else {
Attribute attribute = new BasicAttribute(key);
@@ -327,7 +294,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
}
if (cn == null) {
- cn = String.format("%s@%s", principal, realm);
+ cn = deconstructedPrincipal.getNormalizedPrincipal();
}
try {
Name name = new CompositeName().add(String.format("cn=%s,%s", cn, principalContainerDn));
@@ -359,26 +326,27 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
if (password == null) {
throw new KerberosOperationException("principal password is null");
}
+
+ DeconstructedPrincipal deconstructPrincipal = deconstructPrincipal(principal);
+
try {
- if (!principalExists(principal)) {
- throw new KerberosOperationException("principal not found : " + principal);
+ String dn = findPrincipalDN(deconstructPrincipal.getNormalizedPrincipal());
+
+ if (dn != null) {
+ ldapContext.modifyAttributes(dn,
+ new ModificationItem[]{
+ new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", String.format("\"%s\"", password).getBytes("UTF-16LE")))
+ }
+ );
+ } else {
+ throw new KerberosOperationException(String.format("Can not set password for principal %s: Not Found", principal));
}
- } catch (KerberosOperationException e) {
- e.printStackTrace();
- }
- try {
- ModificationItem[] mods = new ModificationItem[1];
- String quotedPasswordVal = "\"" + password + "\"";
- mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
- new BasicAttribute("UnicodePwd", quotedPasswordVal.getBytes("UTF-16LE")));
- ldapContext.modifyAttributes(
- new CompositeName().add("cn=" + principal + "," + principalContainerDn),
- mods);
- } catch (NamingException ne) {
- throw new KerberosOperationException("Can not set password for principal : " + principal, ne);
- } catch (UnsupportedEncodingException ue) {
- throw new KerberosOperationException("Unsupported encoding UTF-16LE", ue);
+ } catch (NamingException e) {
+ throw new KerberosOperationException(String.format("Can not set password for principal %s: %s", principal, e.getMessage()), e);
+ } catch (UnsupportedEncodingException e) {
+ throw new KerberosOperationException("Unsupported encoding UTF-16LE", e);
}
+
return 0;
}
@@ -399,18 +367,17 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
if (principal == null) {
throw new KerberosOperationException("principal is null");
}
+
+ DeconstructedPrincipal deconstructPrincipal = deconstructPrincipal(principal);
+
try {
- if (!principalExists(principal)) {
- return false;
+ String dn = findPrincipalDN(deconstructPrincipal.getNormalizedPrincipal());
+
+ if (dn != null) {
+ ldapContext.destroySubcontext(dn);
}
- } catch (KerberosOperationException e) {
- e.printStackTrace();
- }
- try {
- Name name = new CompositeName().add("cn=" + principal + "," + principalContainerDn);
- ldapContext.destroySubcontext(name);
- } catch (NamingException ne) {
- throw new KerberosOperationException("Can not remove principal: " + principal);
+ } catch (NamingException e) {
+ throw new KerberosOperationException(String.format("Can not remove principal %s: %s", principal, e.getMessage()), e);
}
return true;
@@ -531,14 +498,14 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
if ((createTemplate == null) || createTemplate.isEmpty()) {
template = "{" +
"\"objectClass\": [\"top\", \"person\", \"organizationalPerson\", \"user\"]," +
- "\"cn\": \"$principal\"," +
+ "\"cn\": \"$principal_name\"," +
"#if( $is_service )" +
- " \"servicePrincipalName\": \"$principal\"," +
+ " \"servicePrincipalName\": \"$principal_name\"," +
"#end" +
- "\"userPrincipalName\": \"$principal@$realm.toLowerCase()\"," +
- "\"unicodePwd\": \"\\\"$password\\\"\"," +
+ "\"userPrincipalName\": \"$normalized_principal.toLowerCase()\"," +
+ "\"unicodePwd\": \"$password\"," +
"\"accountExpires\": \"0\"," +
- "\"userAccountControl\": \"512\"" +
+ "\"userAccountControl\": \"66048\"" +
"}";
} else {
template = createTemplate;
@@ -566,4 +533,34 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
return data;
}
+ private String findPrincipalDN(String normalizedPrincipal) throws NamingException, KerberosOperationException {
+ String dn = null;
+
+ if (normalizedPrincipal != null) {
+ NamingEnumeration<SearchResult> results = null;
+
+ try {
+ results = ldapContext.search(
+ principalContainerDn,
+ String.format("(userPrincipalName=%s)", normalizedPrincipal),
+ searchControls
+ );
+
+ if ((results != null) && results.hasMore()) {
+ SearchResult result = results.next();
+ dn = result.getNameInNamespace();
+ }
+ } finally {
+ try {
+ if (results != null) {
+ results.close();
+ }
+ } catch (NamingException ne) {
+ // ignore, we can not do anything about it
+ }
+ }
+ }
+
+ return dn;
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java
new file mode 100644
index 0000000..f5d8156
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.serveraction.kerberos;
+
+import javax.annotation.Nullable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * DeconstructedPrincipal manages the different parts of a principal and can be used to get a
+ * normalized principal value
+ * <p/>
+ * A "normalized" principal has the following forms:
+ * <ul>
+ * <li>primary/instance@realm</li>
+ * <li>primary@realm</li>
+ * </ul>
+ * <p/>
+ * This class will create a DeconstructedPrincipal from a String containing a principal using
+ * {@link DeconstructedPrincipal#valueOf(String, String)}
+ */
+class DeconstructedPrincipal {
+ /**
+ * Regular expression to parse the different principal formats:
+ * <ul>
+ * <li>primary/instance@REALM</li>
+ * <li>primary@REALM</li>
+ * <li>primary/instance</li>
+ * <li>primary</li>
+ * </ul>
+ */
+ private static Pattern PATTERN_PRINCIPAL = Pattern.compile("^([^ /@]+)(?:/([^ /@]+))?(?:@(.+)?)?$");
+
+ /**
+ * A String containing the "primary" component of a principal
+ */
+ private final String primary;
+
+ /**
+ * A String containing the "instance" component of a principal
+ */
+ private final String instance;
+
+ /**
+ * A String containing the "realm" component of a principal
+ */
+ private final String realm;
+
+ /**
+ * A String containing the principal name portion of the principal.
+ * The principal name is the combination of the primary and instance components.
+ * This value is generated using the primary, instance, and realm components.
+ */
+ private final String principalName;
+
+ /**
+ * A String containing the complete normalized principal
+ * The normalized principal is the combination of the primary, instance, and realm components.
+ * This value is generated using the primary, instance, and realm components.
+ */
+ private final String normalizedPrincipal;
+
+ /**
+ * Given a principal and a default realm, creates a new DeconstructedPrincipal
+ * <p/>
+ * If the supplied principal does not have a realm component, the default realm (supplied) will be
+ * used.
+ *
+ * @param principal a String containing the principal to deconstruct
+ * @param defaultRealm a String containing the default realm
+ * @return a new DeconstructedPrincipal
+ */
+ public static DeconstructedPrincipal valueOf(String principal, @Nullable String defaultRealm) {
+ if (principal == null) {
+ throw new IllegalArgumentException("The principal may not be null");
+ }
+
+ Matcher matcher = PATTERN_PRINCIPAL.matcher(principal);
+
+ if (matcher.matches()) {
+ String primary = matcher.group(1);
+ String instance = matcher.group(2);
+ String realm = matcher.group(3);
+
+ if ((realm == null) || realm.isEmpty()) {
+ realm = defaultRealm;
+ }
+
+ return new DeconstructedPrincipal(primary, instance, realm);
+ } else {
+ throw new IllegalArgumentException(String.format("Invalid principal value: %s", principal));
+ }
+ }
+
+
+ /**
+ * Constructs a new DeconstructedPrincipal
+ *
+ * @param primary a String containing the "primary" component of the principal
+ * @param instance a String containing the "instance" component of the principal
+ * @param realm a String containing the "realm" component of the principal
+ */
+ protected DeconstructedPrincipal(String primary, String instance, String realm) {
+ this.primary = primary;
+ this.instance = instance;
+ this.realm = realm;
+
+ StringBuilder builder = new StringBuilder();
+
+ if (this.primary != null) {
+ builder.append(primary);
+ }
+
+ if (this.instance != null) {
+ builder.append('/');
+ builder.append(this.instance);
+ }
+
+ this.principalName = builder.toString();
+
+ if (this.realm != null) {
+ builder.append('@');
+ builder.append(this.realm);
+ }
+
+ this.normalizedPrincipal = builder.toString();
+ }
+
+ /**
+ * Gets the primary component of this DeconstructedPrincipal
+ *
+ * @return a String containing the "primary" component of this DeconstructedPrincipal
+ */
+ public String getPrimary() {
+ return primary;
+ }
+
+ /**
+ * Gets the instance component of this DeconstructedPrincipal
+ *
+ * @return a String containing the "instance" component of this DeconstructedPrincipal
+ */
+ public String getInstance() {
+ return instance;
+ }
+
+ /**
+ * Gets the realm component of this DeconstructedPrincipal
+ *
+ * @return a String containing the "realm" component of this DeconstructedPrincipal
+ */
+ public String getRealm() {
+ return realm;
+ }
+
+ /**
+ * Gets the constructed principal name for this DeconstructedPrincipal
+ * <p/>
+ * The principal name is the combination of the primary and instance components:
+ * <ul>
+ * <li>primary/instance</li>
+ * <li>primary</li>
+ * </ul>
+ *
+ * @return a String containing the "realm" component of this DeconstructedPrincipal
+ */
+ public String getPrincipalName() {
+ return principalName;
+ }
+
+ /**
+ * Gets the constructed normalized principal for this DeconstructedPrincipal
+ * <p/>
+ * The normalized principal is the combination of the primary, instance, and realm components:
+ * <ul>
+ * <li>primary/instance@realm</li>
+ * <li>primary@realm</li>
+ * </ul>
+ *
+ * @return a String containing the "realm" component of this DeconstructedPrincipal
+ */
+ public String getNormalizedPrincipal() {
+ return normalizedPrincipal;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
index 7a9233b..a23aa81 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
@@ -63,7 +63,6 @@ public abstract class KerberosOperationHandler {
private final static char[] SECURE_PASSWORD_CHARS =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?.!$%^*()-_+=~".toCharArray();
-
/**
* The default set of ciphers to use for creating keytab entries
*/
@@ -432,4 +431,13 @@ public abstract class KerberosOperationHandler {
}
}
}
+
+ protected DeconstructedPrincipal deconstructPrincipal(String principal) throws KerberosOperationException {
+ try {
+ return DeconstructedPrincipal.valueOf(principal, getDefaultRealm());
+ } catch (IllegalArgumentException e) {
+ throw new KerberosOperationException(e.getMessage(), e);
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
index 85ae018..d37e736 100644
--- a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
+++ b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
@@ -40,19 +40,23 @@
<property require-input="true">
<name>create_attributes_template</name>
<description>
- Customizable JSON document representing the LDAP attributes needed to create a new Kerberos entity in the KDC (Velocity template engine).
+ A Velocity template to use to generate a JSON-formatted document containing the set of
+ attribute names and values needed to create a new Kerberos identity in the relevant KDC.
+ Variables include:
+ principal_name, principal_primary, principal_instance, realm, realm_lowercase,
+ normalized_principal, principal digest, password, is_service, container_dn
</description>
<value>
{
"objectClass": ["top", "person", "organizationalPerson", "user"],
- "cn": "$principal",
+ "cn": "$principal_name",
#if( $is_service )
- "servicePrincipalName": "$principal",
+ "servicePrincipalName": "$principal_name",
#end
- "userPrincipalName": "$principal@$realm.toLowerCase()",
- "unicodePwd": "\"$password\"",
+ "userPrincipalName": "$normalized_principal.toLowerCase()",
+ "unicodePwd": "$password",
"accountExpires": "0",
- "userAccountControl": "512"
+ "userAccountControl": "66048"
}
</value>
</property>
http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
index 6a89dbb..8d2a3c4 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
@@ -19,6 +19,8 @@
package org.apache.ambari.server.serveraction.kerberos;
import junit.framework.Assert;
+import org.easymock.Capture;
+import org.easymock.CaptureType;
import org.easymock.EasyMockSupport;
import org.easymock.IAnswer;
import org.junit.Ignore;
@@ -26,21 +28,22 @@ import org.junit.Test;
import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
+import javax.naming.Name;
import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.nio.charset.Charset;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.*;
public class ADKerberosOperationHandlerTest extends EasyMockSupport {
private static final String DEFAULT_ADMIN_PRINCIPAL = "cluser_admin@HDP01.LOCAL";
@@ -235,32 +238,30 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
}
};
+ Capture<Name> capturedName = new Capture<Name>(CaptureType.ALL);
+ Capture<Attributes> capturedAttributes = new Capture<Attributes>(CaptureType.ALL);
+
ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
.addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
.addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
.createNiceMock();
+ NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
+ expect(searchResult.hasMore()).andReturn(false).once();
+
+ LdapContext ldapContext = createNiceMock(LdapContext.class);
+ expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
+ .andReturn(searchResult)
+ .once();
+
+ expect(ldapContext.createSubcontext(capture(capturedName), capture(capturedAttributes)))
+ .andReturn(createNiceMock(DirContext.class))
+ .anyTimes();
+
expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class)))
- .andAnswer(new IAnswer<LdapContext>() {
- @Override
- public LdapContext answer() throws Throwable {
- LdapContext ldapContext = createNiceMock(LdapContext.class);
- expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
- .andAnswer(new IAnswer<NamingEnumeration<SearchResult>>() {
- @Override
- public NamingEnumeration<SearchResult> answer() throws Throwable {
- NamingEnumeration<SearchResult> result = createNiceMock(NamingEnumeration.class);
- expect(result.hasMore()).andReturn(false).once();
- replay(result);
- return result;
- }
- })
- .once();
- replay(ldapContext);
- return ldapContext;
- }
- })
+ .andReturn(ldapContext)
.once();
+
expect(handler.createSearchControls()).andAnswer(new IAnswer<SearchControls>() {
@Override
public SearchControls answer() throws Throwable {
@@ -273,45 +274,67 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
replayAll();
handler.open(kc, DEFAULT_REALM, kerberosEnvMap);
+ handler.createPrincipal("nn/c6501.ambari.apache.org", "secret", true);
+ handler.createPrincipal("hdfs@" + DEFAULT_REALM, "secret", false);
+ handler.close();
- Map<String, Object> context = new HashMap<String, Object>();
- context.put("principal", "nn/c6501.ambari.apache.org");
- context.put("principal_primary", "nn");
- context.put("principal_instance", "c6501.ambari.apache.org");
- context.put("realm", "EXAMPLE.COM");
- context.put("realm_lowercase", "example.com");
- context.put("password", "secret");
- context.put("is_service", true);
- context.put("container_dn", "ou=cluster,DC=EXAMPLE,DC=COM");
-
- Map<String, Object> data;
-
- data = handler.processCreateTemplate(context);
-
- Assert.assertNotNull(data);
- Assert.assertEquals(7, data.size());
- Assert.assertEquals(new ArrayList<String>(Arrays.asList("top", "person", "organizationalPerson", "user")), data.get("objectClass"));
- Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("cn"));
- Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("servicePrincipalName"));
- Assert.assertEquals("nn/c6501.ambari.apache.org@example.com", data.get("userPrincipalName"));
- Assert.assertEquals("\"secret\"", data.get("unicodePwd"));
- Assert.assertEquals("0", data.get("accountExpires"));
- Assert.assertEquals("512", data.get("userAccountControl"));
-
-
- context.put("is_service", false);
- data = handler.processCreateTemplate(context);
-
- Assert.assertNotNull(data);
- Assert.assertEquals(6, data.size());
- Assert.assertEquals(new ArrayList<String>(Arrays.asList("top", "person", "organizationalPerson", "user")), data.get("objectClass"));
- Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("cn"));
- Assert.assertEquals("nn/c6501.ambari.apache.org@example.com", data.get("userPrincipalName"));
- Assert.assertEquals("\"secret\"", data.get("unicodePwd"));
- Assert.assertEquals("0", data.get("accountExpires"));
- Assert.assertEquals("512", data.get("userAccountControl"));
+ List<Attributes> attributesList = capturedAttributes.getValues();
+ Attributes attributes;
- handler.close();
+ attributes = attributesList.get(0);
+ String[] objectClasses = new String[]{"top", "person", "organizationalPerson", "user"};
+
+ Assert.assertNotNull(attributes);
+ Assert.assertEquals(7, attributes.size());
+
+ Assert.assertNotNull(attributes.get("objectClass"));
+ Assert.assertEquals(objectClasses.length, attributes.get("objectClass").size());
+ for (int i = 0; i < objectClasses.length; i++) {
+ Assert.assertEquals(objectClasses[i], attributes.get("objectClass").get(i));
+ }
+
+ Assert.assertNotNull(attributes.get("cn"));
+ Assert.assertEquals("nn/c6501.ambari.apache.org", attributes.get("cn").get());
+
+ Assert.assertNotNull(attributes.get("servicePrincipalName"));
+ Assert.assertEquals("nn/c6501.ambari.apache.org", attributes.get("servicePrincipalName").get());
+
+ Assert.assertNotNull(attributes.get("userPrincipalName"));
+ Assert.assertEquals("nn/c6501.ambari.apache.org@hdp01.local", attributes.get("userPrincipalName").get());
+
+ Assert.assertNotNull(attributes.get("unicodePwd"));
+ Assert.assertEquals("\"secret\"", new String((byte[]) attributes.get("unicodePwd").get(), Charset.forName("UTF-16LE")));
+
+ Assert.assertNotNull(attributes.get("accountExpires"));
+ Assert.assertEquals("0", attributes.get("accountExpires").get());
+
+ Assert.assertNotNull(attributes.get("userAccountControl"));
+ Assert.assertEquals("66048", attributes.get("userAccountControl").get());
+
+ attributes = attributesList.get(1);
+ Assert.assertNotNull(attributes);
+ Assert.assertEquals(6, attributes.size());
+
+ Assert.assertNotNull(attributes.get("objectClass"));
+ Assert.assertEquals(objectClasses.length, attributes.get("objectClass").size());
+ for (int i = 0; i < objectClasses.length; i++) {
+ Assert.assertEquals(objectClasses[i], attributes.get("objectClass").get(i));
+ }
+
+ Assert.assertNotNull(attributes.get("cn"));
+ Assert.assertEquals("hdfs", attributes.get("cn").get());
+
+ Assert.assertNotNull(attributes.get("userPrincipalName"));
+ Assert.assertEquals("hdfs@hdp01.local", attributes.get("userPrincipalName").get());
+
+ Assert.assertNotNull(attributes.get("unicodePwd"));
+ Assert.assertEquals("\"secret\"", new String((byte[]) attributes.get("unicodePwd").get(), Charset.forName("UTF-16LE")));
+
+ Assert.assertNotNull(attributes.get("accountExpires"));
+ Assert.assertEquals("0", attributes.get("accountExpires").get());
+
+ Assert.assertNotNull(attributes.get("userAccountControl"));
+ Assert.assertEquals("66048", attributes.get("userAccountControl").get());
}
@Test
@@ -321,54 +344,52 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
{
put(ADKerberosOperationHandler.KERBEROS_ENV_LDAP_URL, DEFAULT_LDAP_URL);
put(ADKerberosOperationHandler.KERBEROS_ENV_PRINCIPAL_CONTAINER_DN, DEFAULT_PRINCIPAL_CONTAINER_DN);
- put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE, "{" +
+ put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE, "" +
+ "#set( $user = \"${principal_primary}-${principal_digest}\" )" +
+ "{" +
" \"objectClass\": [" +
" \"top\"," +
" \"person\"," +
" \"organizationalPerson\"," +
" \"user\"" +
" ]," +
- " \"cn\": \"$principal@$realm\"," +
- " \"dn\": \"$principal@$realm,$container_dn\"," +
- " \"distinguishedName\": \"$principal@$realm,$container_dn\"," +
- " \"sAMAccountName\": \"$principal\"," +
+ " \"cn\": \"$user\"," +
+ " \"sAMAccountName\": \"$user.substring(0,20)\"," +
" #if( $is_service )" +
- " \"servicePrincipalName\": \"$principal\"," +
+ " \"servicePrincipalName\": \"$principal_name\"," +
" #end" +
- " \"userPrincipalName\": \"$principal@$realm.toLowerCase()\"," +
- " \"unicodePwd\": \"`$password`\"," +
+ " \"userPrincipalName\": \"$normalized_principal.toLowerCase()\"," +
+ " \"unicodePwd\": \"$password\"," +
" \"accountExpires\": \"0\"," +
" \"userAccountControl\": \"66048\"" +
"}");
}
};
+ Capture<Name> capturedName = new Capture<Name>();
+ Capture<Attributes> capturedAttributes = new Capture<Attributes>();
+
ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
.addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
.addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
.createNiceMock();
+ NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
+ expect(searchResult.hasMore()).andReturn(false).once();
+
+ LdapContext ldapContext = createNiceMock(LdapContext.class);
+ expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
+ .andReturn(searchResult)
+ .once();
+
+ expect(ldapContext.createSubcontext(capture(capturedName), capture(capturedAttributes)))
+ .andReturn(createNiceMock(DirContext.class))
+ .once();
+
expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class)))
- .andAnswer(new IAnswer<LdapContext>() {
- @Override
- public LdapContext answer() throws Throwable {
- LdapContext ldapContext = createNiceMock(LdapContext.class);
- expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
- .andAnswer(new IAnswer<NamingEnumeration<SearchResult>>() {
- @Override
- public NamingEnumeration<SearchResult> answer() throws Throwable {
- NamingEnumeration<SearchResult> result = createNiceMock(NamingEnumeration.class);
- expect(result.hasMore()).andReturn(false).once();
- replay(result);
- return result;
- }
- })
- .once();
- replay(ldapContext);
- return ldapContext;
- }
- })
+ .andReturn(ldapContext)
.once();
+
expect(handler.createSearchControls()).andAnswer(new IAnswer<SearchControls>() {
@Override
public SearchControls answer() throws Throwable {
@@ -381,34 +402,43 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
replayAll();
handler.open(kc, DEFAULT_REALM, kerberosEnvMap);
+ handler.createPrincipal("nn/c6501.ambari.apache.org", "secret", true);
+ handler.close();
+ Attributes attributes = capturedAttributes.getValue();
+ String[] objectClasses = new String[]{"top", "person", "organizationalPerson", "user"};
- Map<String, Object> context = new HashMap<String, Object>();
- context.put("principal", "nn/c6501.ambari.apache.org");
- context.put("principal_primary", "nn");
- context.put("principal_instance", "c6501.ambari.apache.org");
- context.put("realm", "EXAMPLE.COM");
- context.put("realm_lowercase", "example.com");
- context.put("password", "secret");
- context.put("is_service", true);
- context.put("container_dn", "ou=cluster,DC=EXAMPLE,DC=COM");
-
- Map<String, Object> data = handler.processCreateTemplate(context);
-
- Assert.assertNotNull(data);
- Assert.assertEquals(10, data.size());
- Assert.assertEquals(new ArrayList<String>(Arrays.asList("top", "person", "organizationalPerson", "user")), data.get("objectClass"));
- Assert.assertEquals("nn/c6501.ambari.apache.org@EXAMPLE.COM", data.get("cn"));
- Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("servicePrincipalName"));
- Assert.assertEquals("nn/c6501.ambari.apache.org@example.com", data.get("userPrincipalName"));
- Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("sAMAccountName"));
- Assert.assertEquals("nn/c6501.ambari.apache.org@EXAMPLE.COM,ou=cluster,DC=EXAMPLE,DC=COM", data.get("distinguishedName"));
- Assert.assertEquals("nn/c6501.ambari.apache.org@EXAMPLE.COM,ou=cluster,DC=EXAMPLE,DC=COM", data.get("dn"));
- Assert.assertEquals("`secret`", data.get("unicodePwd"));
- Assert.assertEquals("0", data.get("accountExpires"));
- Assert.assertEquals("66048", data.get("userAccountControl"));
+ Assert.assertNotNull(attributes);
+ Assert.assertEquals(8, attributes.size());
+
+ Assert.assertNotNull(attributes.get("objectClass"));
+ Assert.assertEquals(objectClasses.length, attributes.get("objectClass").size());
+ for (int i = 0; i < objectClasses.length; i++) {
+ Assert.assertEquals(objectClasses[i], attributes.get("objectClass").get(i));
+ }
+
+ Assert.assertNotNull(attributes.get("cn"));
+ Assert.assertEquals("nn-995e1580db28198e7fda1417ab5d894c877937d2", attributes.get("cn").get());
+
+ Assert.assertNotNull(attributes.get("servicePrincipalName"));
+ Assert.assertEquals("nn/c6501.ambari.apache.org", attributes.get("servicePrincipalName").get());
+
+ Assert.assertNotNull(attributes.get("userPrincipalName"));
+ Assert.assertEquals("nn/c6501.ambari.apache.org@hdp01.local", attributes.get("userPrincipalName").get());
+
+ Assert.assertNotNull(attributes.get("sAMAccountName"));
+ Assert.assertTrue(attributes.get("sAMAccountName").get().toString().length() <= 20);
+ Assert.assertEquals("nn-995e1580db28198e7", attributes.get("sAMAccountName").get());
+
+ Assert.assertNotNull(attributes.get("unicodePwd"));
+ Assert.assertEquals("\"secret\"", new String((byte[]) attributes.get("unicodePwd").get(), Charset.forName("UTF-16LE")));
+
+ Assert.assertNotNull(attributes.get("accountExpires"));
+ Assert.assertEquals("0", attributes.get("accountExpires").get());
+
+ Assert.assertNotNull(attributes.get("userAccountControl"));
+ Assert.assertEquals("66048", attributes.get("userAccountControl").get());
- handler.close();
}
/**
@@ -458,31 +488,40 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
// does the principal already exist?
System.out.println("Principal exists: " + handler.principalExists("nn/c1508.ambari.apache.org"));
- //create principal
-// handler.createPrincipal("nn/c1508.ambari.apache.org@" + DEFAULT_REALM, handler.createSecurePassword(), true);
-
handler.close();
- kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE, "{" +
- "\"objectClass\": [\"top\", \"person\", \"organizationalPerson\", \"user\"]," +
- "\"distinguishedName\": \"CN=$principal@$realm,$container_dn\"," +
- "#if( $is_service )" +
- "\"servicePrincipalName\": \"$principal\"," +
- "#end" +
- "\"userPrincipalName\": \"$principal@$realm.toLowerCase()\"," +
- "\"unicodePwd\": \"\\\"$password\\\"\"," +
- "\"accountExpires\": \"0\"," +
- "\"userAccountControl\": \"66048\"" +
- "}");
+ kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE,
+ "#set( $user = \"${principal_primary}-${principal_digest}\" )" +
+ "{" +
+ " \"objectClass\": [" +
+ " \"top\"," +
+ " \"person\"," +
+ " \"organizationalPerson\"," +
+ " \"user\"" +
+ " ]," +
+ " \"cn\": \"$user\"," +
+ " \"sAMAccountName\": \"$user.substring(0,20)\"," +
+ " #if( $is_service )" +
+ " \"servicePrincipalName\": \"$principal_name\"," +
+ " #end" +
+ " \"userPrincipalName\": \"$normalized_principal.toLowerCase()\"," +
+ " \"unicodePwd\": \"$password\"," +
+ " \"accountExpires\": \"0\"," +
+ " \"userAccountControl\": \"66048\"" +
+ "}"
+ );
handler.open(credentials, realm, kerberosEnvMap);
+
+ // remove the principal
+ handler.removePrincipal("abcdefg");
+ handler.removePrincipal("abcdefg/c1509.ambari.apache.org@" + DEFAULT_REALM);
+
handler.createPrincipal("abcdefg/c1509.ambari.apache.org@" + DEFAULT_REALM, handler.createSecurePassword(), true);
+ handler.createPrincipal("abcdefg@" + DEFAULT_REALM, handler.createSecurePassword(), false);
//update the password
- handler.setPrincipalPassword("nn/c1508.ambari.apache.org", handler.createSecurePassword());
-
- // remove the principal
- // handler.removeServicePrincipal("nn/c1508.ambari.apache.org");
+ handler.setPrincipalPassword("abcdefg/c1509.ambari.apache.org@" + DEFAULT_REALM, handler.createSecurePassword());
handler.close();
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java
new file mode 100644
index 0000000..28dd08a
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.serveraction.kerberos;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class DeconstructedPrincipalTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullPrincipal() throws Exception {
+ DeconstructedPrincipal.valueOf(null, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyPrincipal() throws Exception {
+ DeconstructedPrincipal.valueOf("", null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidPrincipal() throws Exception {
+ DeconstructedPrincipal.valueOf("/invalid", null);
+ }
+
+ @Test
+ public void testPrimary() throws Exception {
+ DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary", "REALM");
+
+ assertNotNull(deconstructedPrincipal);
+ assertEquals("primary", deconstructedPrincipal.getPrimary());
+ assertNull(deconstructedPrincipal.getInstance());
+ assertEquals("REALM", deconstructedPrincipal.getRealm());
+ assertEquals("primary", deconstructedPrincipal.getPrincipalName());
+ assertEquals("primary@REALM", deconstructedPrincipal.getNormalizedPrincipal());
+ }
+
+ @Test
+ public void testPrimaryRealm() throws Exception {
+ DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary@MYREALM", "REALM");
+
+ assertNotNull(deconstructedPrincipal);
+ assertEquals("primary", deconstructedPrincipal.getPrimary());
+ assertNull(deconstructedPrincipal.getInstance());
+ assertEquals("MYREALM", deconstructedPrincipal.getRealm());
+ assertEquals("primary", deconstructedPrincipal.getPrincipalName());
+ assertEquals("primary@MYREALM", deconstructedPrincipal.getNormalizedPrincipal());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInstance() throws Exception {
+ DeconstructedPrincipal.valueOf("/instance", "REALM");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInstanceRealm() throws Exception {
+ DeconstructedPrincipal.valueOf("/instance@MYREALM", "REALM");
+ }
+
+ @Test
+ public void testPrimaryInstance() throws Exception {
+ DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary/instance", "REALM");
+
+ assertNotNull(deconstructedPrincipal);
+ assertEquals("primary", deconstructedPrincipal.getPrimary());
+ assertEquals("instance", deconstructedPrincipal.getInstance());
+ assertEquals("instance", deconstructedPrincipal.getInstance());
+ assertEquals("REALM", deconstructedPrincipal.getRealm());
+ assertEquals("primary/instance", deconstructedPrincipal.getPrincipalName());
+ assertEquals("primary/instance@REALM", deconstructedPrincipal.getNormalizedPrincipal());
+ }
+
+ @Test
+ public void testPrimaryInstanceRealm() throws Exception {
+ DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary/instance@MYREALM", "REALM");
+
+ assertNotNull(deconstructedPrincipal);
+ assertEquals("primary", deconstructedPrincipal.getPrimary());
+ assertEquals("instance", deconstructedPrincipal.getInstance());
+ assertEquals("MYREALM", deconstructedPrincipal.getRealm());
+ assertEquals("primary/instance", deconstructedPrincipal.getPrincipalName());
+ assertEquals("primary/instance@MYREALM", deconstructedPrincipal.getNormalizedPrincipal());
+ }
+
+ @Test
+ public void testOddCharacters() throws Exception {
+ DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("p_ri.ma-ry/i.n_s-tance@M_Y-REALM.COM", "REALM");
+
+ assertNotNull(deconstructedPrincipal);
+ assertEquals("p_ri.ma-ry", deconstructedPrincipal.getPrimary());
+ assertEquals("i.n_s-tance", deconstructedPrincipal.getInstance());
+ assertEquals("M_Y-REALM.COM", deconstructedPrincipal.getRealm());
+ assertEquals("p_ri.ma-ry/i.n_s-tance", deconstructedPrincipal.getPrincipalName());
+ assertEquals("p_ri.ma-ry/i.n_s-tance@M_Y-REALM.COM", deconstructedPrincipal.getNormalizedPrincipal());
+ }
+
+}
\ No newline at end of file