From 295b58bb99418b706eb9d6e4e07c18114cf3cdc8 Mon Sep 17 00:00:00 2001 From: Kihwal Lee Date: Mon, 28 Apr 2014 13:53:27 +0000 Subject: [PATCH] HADOOP-10322. Add ability to read principal names from a keytab. Contributed by Benoy Antony and Daryn Sharp. git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1590637 13f79535-47bb-0310-9956-ffa450edef68 --- hadoop-common-project/hadoop-auth/pom.xml | 6 + .../authentication/util/KerberosUtil.java | 57 ++++++++- .../authentication/util/TestKerberosUtil.java | 110 +++++++++++++++++- .../hadoop-common/CHANGES.txt | 3 + 4 files changed, 172 insertions(+), 4 deletions(-) diff --git a/hadoop-common-project/hadoop-auth/pom.xml b/hadoop-common-project/hadoop-auth/pom.xml index 1d913a637c1..8eeafa574ab 100644 --- a/hadoop-common-project/hadoop-auth/pom.xml +++ b/hadoop-common-project/hadoop-auth/pom.xml @@ -97,6 +97,12 @@ httpclient compile + + org.apache.directory.server + apacheds-kerberos-codec + 2.0.0-M15 + compile + diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java index 6435e75f5e3..ca0fce2251e 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java @@ -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 static final String getServicePrincipal(String service, String hostname) // 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 principals = new HashSet(); + List 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 matchingPrincipals = new ArrayList(); + for (String principal : principals) { + if (pattern.matcher(principal).matches()) { + matchingPrincipals.add(principal); + } + } + principals = matchingPrincipals.toArray(new String[0]); + } + return principals; + } } diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java index 7da78aa20e0..b0e8f04a8f7 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java +++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java @@ -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 void testGetServerPrincipal() throws IOException { 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 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 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 lstEntries = new ArrayList(); + 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 diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index efb94c0be27..4d0370a0fe2 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -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