Author: kihwal Date: Mon Apr 28 13:53:27 2014 New Revision: 1590637 URL: http://svn.apache.org/r1590637 Log: HADOOP-10322. Add ability to read principal names from a keytab. Contributed by Benoy Antony and Daryn Sharp.
Modified: hadoop/common/trunk/hadoop-common-project/hadoop-auth/pom.xml hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java hadoop/common/trunk/hadoop-common-project/hadoop-common/CHANGES.txt Modified: hadoop/common/trunk/hadoop-common-project/hadoop-auth/pom.xml URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-common-project/hadoop-auth/pom.xml?rev=1590637&r1=1590636&r2=1590637&view=diff ============================================================================== --- hadoop/common/trunk/hadoop-common-project/hadoop-auth/pom.xml (original) +++ hadoop/common/trunk/hadoop-common-project/hadoop-auth/pom.xml Mon Apr 28 13:53:27 2014 @@ -97,6 +97,12 @@ <artifactId>httpclient</artifactId> <scope>compile</scope> </dependency> + <dependency> + <groupId>org.apache.directory.server</groupId> + <artifactId>apacheds-kerberos-codec</artifactId> + <version>2.0.0-M15</version> + <scope>compile</scope> + </dependency> </dependencies> <build> Modified: hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java?rev=1590637&r1=1590636&r2=1590637&view=diff ============================================================================== --- hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java (original) +++ hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java Mon Apr 28 13:53:27 2014 @@ -17,18 +17,27 @@ */ package org.apache.hadoop.security.authentication.util; +import static org.apache.hadoop.util.PlatformName.IBM_JAVA; + +import java.io.File; +import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; +import org.apache.directory.server.kerberos.shared.keytab.Keytab; +import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; import org.ietf.jgss.GSSException; import org.ietf.jgss.Oid; -import static org.apache.hadoop.util.PlatformName.IBM_JAVA; - public class KerberosUtil { /* Return the Kerberos login module name */ @@ -103,4 +112,48 @@ public class KerberosUtil { // with uppercase characters. return service + "/" + fqdn.toLowerCase(Locale.US); } + + /** + * Get all the unique principals present in the keytabfile. + * + * @param keytabFileName + * Name of the keytab file to be read. + * @return list of unique principals in the keytab. + * @throws IOException + * If keytab entries cannot be read from the file. + */ + static final String[] getPrincipalNames(String keytabFileName) throws IOException { + Keytab keytab = Keytab.read(new File(keytabFileName)); + Set<String> principals = new HashSet<String>(); + List<KeytabEntry> entries = keytab.getEntries(); + for (KeytabEntry entry: entries){ + principals.add(entry.getPrincipalName().replace("\\", "/")); + } + return principals.toArray(new String[0]); + } + + /** + * Get all the unique principals from keytabfile which matches a pattern. + * + * @param keytab + * Name of the keytab file to be read. + * @param pattern + * pattern to be matched. + * @return list of unique principals which matches the pattern. + * @throws IOException + */ + public static final String[] getPrincipalNames(String keytab, + Pattern pattern) throws IOException { + String[] principals = getPrincipalNames(keytab); + if (principals.length != 0) { + List<String> matchingPrincipals = new ArrayList<String>(); + for (String principal : principals) { + if (pattern.matcher(principal).matches()) { + matchingPrincipals.add(principal); + } + } + principals = matchingPrincipals.toArray(new String[0]); + } + return principals; + } } Modified: hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java?rev=1590637&r1=1590636&r2=1590637&view=diff ============================================================================== --- hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java (original) +++ hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java Mon Apr 28 13:53:27 2014 @@ -16,13 +16,39 @@ */ package org.apache.hadoop.security.authentication.util; -import org.junit.Assert; - +import java.io.File; import java.io.IOException; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.directory.server.kerberos.shared.keytab.Keytab; +import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; +import org.apache.directory.shared.kerberos.KerberosTime; +import org.apache.directory.shared.kerberos.codec.types.EncryptionType; +import org.apache.directory.shared.kerberos.components.EncryptionKey; +import org.junit.After; +import org.junit.Assert; import org.junit.Test; public class TestKerberosUtil { + static String testKeytab = "test.keytab"; + static String[] testPrincipals = new String[]{ + "HTTP@testRealm", + "test/testhost@testRealm", + "HTTP/testhost@testRealm", + "HTTP1/testhost@testRealm", + "HTTP/testhostanother@testRealm" + }; + + @After + public void deleteKeytab() { + File keytabFile = new File(testKeytab); + if (keytabFile.exists()){ + keytabFile.delete(); + } + } @Test public void testGetServerPrincipal() throws IOException { @@ -51,4 +77,84 @@ public class TestKerberosUtil { service + "/" + testHost.toLowerCase(), KerberosUtil.getServicePrincipal(service, testHost.toLowerCase())); } + + @Test + public void testGetPrincipalNamesMissingKeytab() { + try { + KerberosUtil.getPrincipalNames(testKeytab); + Assert.fail("Exception should have been thrown"); + } catch (IOException e) { + //expects exception + } + } + + @Test + public void testGetPrincipalNamesMissingPattern() throws IOException { + createKeyTab(testKeytab, new String[]{"test/testhost@testRealm"}); + try { + KerberosUtil.getPrincipalNames(testKeytab, null); + Assert.fail("Exception should have been thrown"); + } catch (Exception e) { + //expects exception + } + } + + @Test + public void testGetPrincipalNamesFromKeytab() throws IOException { + createKeyTab(testKeytab, testPrincipals); + // read all principals in the keytab file + String[] principals = KerberosUtil.getPrincipalNames(testKeytab); + Assert.assertNotNull("principals cannot be null", principals); + + int expectedSize = 0; + List<String> principalList = Arrays.asList(principals); + for (String principal : testPrincipals) { + Assert.assertTrue("missing principal "+principal, + principalList.contains(principal)); + expectedSize++; + } + Assert.assertEquals(expectedSize, principals.length); + } + + @Test + public void testGetPrincipalNamesFromKeytabWithPattern() throws IOException { + createKeyTab(testKeytab, testPrincipals); + // read the keytab file + // look for principals with HTTP as the first part + Pattern httpPattern = Pattern.compile("HTTP/.*"); + String[] httpPrincipals = + KerberosUtil.getPrincipalNames(testKeytab, httpPattern); + Assert.assertNotNull("principals cannot be null", httpPrincipals); + + int expectedSize = 0; + List<String> httpPrincipalList = Arrays.asList(httpPrincipals); + for (String principal : testPrincipals) { + if (httpPattern.matcher(principal).matches()) { + Assert.assertTrue("missing principal "+principal, + httpPrincipalList.contains(principal)); + expectedSize++; + } + } + Assert.assertEquals(expectedSize, httpPrincipals.length); + } + + private void createKeyTab(String fileName, String[] principalNames) + throws IOException { + //create a test keytab file + List<KeytabEntry> lstEntries = new ArrayList<KeytabEntry>(); + for (String principal : principalNames){ + // create 3 versions of the key to ensure methods don't return + // duplicate principals + for (int kvno=1; kvno <= 3; kvno++) { + EncryptionKey key = new EncryptionKey( + EncryptionType.UNKNOWN, "samplekey1".getBytes(), kvno); + KeytabEntry keytabEntry = new KeytabEntry( + principal, 1 , new KerberosTime(), (byte) 1, key); + lstEntries.add(keytabEntry); + } + } + Keytab keytab = Keytab.getInstance(); + keytab.setEntries(lstEntries); + keytab.write(new File(testKeytab)); + } } \ No newline at end of file Modified: hadoop/common/trunk/hadoop-common-project/hadoop-common/CHANGES.txt URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-common-project/hadoop-common/CHANGES.txt?rev=1590637&r1=1590636&r2=1590637&view=diff ============================================================================== --- hadoop/common/trunk/hadoop-common-project/hadoop-common/CHANGES.txt (original) +++ hadoop/common/trunk/hadoop-common-project/hadoop-common/CHANGES.txt Mon Apr 28 13:53:27 2014 @@ -358,6 +358,9 @@ Release 2.5.0 - UNRELEASED HADOOP-10535. Make the retry numbers in ActiveStandbyElector configurable. (jing9) + HADOOP-10322. Add ability to read principal names from a keytab. + (Benoy Antony and Daryn Sharp via kihwal) + OPTIMIZATIONS BUG FIXES