PHOENIX-3891 Compare current user to JDBC url principal with optional realm
Kerberos realms can be implied (based on configuration), but our comparison did not take this into mind. This adds some slightly more intelligent parsing to lessen the burden on what the user provides. Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/c47ee89a Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/c47ee89a Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/c47ee89a Branch: refs/heads/4.x-HBase-1.1 Commit: c47ee89a1525526f30d8c5690569df7210105a44 Parents: 0f1e7e7 Author: Josh Elser <els...@apache.org> Authored: Fri May 26 14:59:41 2017 -0400 Committer: Josh Elser <els...@apache.org> Committed: Wed May 31 17:22:43 2017 -0400 ---------------------------------------------------------------------- .../phoenix/jdbc/PhoenixEmbeddedDriver.java | 46 ++++++++++++++++++-- .../phoenix/jdbc/PhoenixEmbeddedDriverTest.java | 19 ++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/c47ee89a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriver.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriver.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriver.java index 2a496c6..00dfb5a 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriver.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriver.java @@ -41,6 +41,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.apache.phoenix.coprocessor.MetaDataProtocol; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.exception.SQLExceptionInfo; @@ -202,6 +203,7 @@ public abstract class PhoenixEmbeddedDriver implements Driver, SQLCloseable { private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ConnectionInfo.class); private static final Object KERBEROS_LOGIN_LOCK = new Object(); private static final char WINDOWS_SEPARATOR_CHAR = '\\'; + private static final String REALM_EQUIVALENCY_WARNING_MSG = "Provided principal does not contan a realm and the default realm cannot be determined. Ignoring realm equivalency check."; private static SQLException getMalFormedUrlException(String url) { return new SQLExceptionInfo.Builder(SQLExceptionCode.MALFORMED_CONNECTION_URL) .setMessage(url).build().buildException(); @@ -377,7 +379,7 @@ public abstract class PhoenixEmbeddedDriver implements Driver, SQLCloseable { // Double check the current user, might have changed since we checked last. Don't want // to re-login if it's the same user. currentUser = UserGroupInformation.getCurrentUser(); - if (!currentUser.hasKerberosCredentials() || !currentUser.getUserName().equals(principal)) { + if (!currentUser.hasKerberosCredentials() || !isSameName(currentUser.getUserName(), principal)) { final Configuration config = getConfiguration(props, info, principal, keytab); logger.info("Trying to connect to a secure cluster as {} with keytab {}", config.get(QueryServices.HBASE_CLIENT_PRINCIPAL), config.get(QueryServices.HBASE_CLIENT_KEYTAB)); @@ -404,14 +406,52 @@ public abstract class PhoenixEmbeddedDriver implements Driver, SQLCloseable { // Visible for testing static boolean isSameName(String currentName, String newName) throws IOException { - return isSameName(currentName, newName, null); + return isSameName(currentName, newName, null, getDefaultKerberosRealm()); + } + + /** + * Computes the default kerberos realm if one is available. If one cannot be computed, null + * is returned. + * + * @return The default kerberos realm, or null. + */ + static String getDefaultKerberosRealm() { + try { + return KerberosUtil.getDefaultRealm(); + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + // Include the stacktrace at DEBUG + LOG.debug(REALM_EQUIVALENCY_WARNING_MSG, e); + } else { + // Limit the content at WARN + LOG.warn(REALM_EQUIVALENCY_WARNING_MSG); + } + } + return null; } static boolean isSameName(String currentName, String newName, String hostname) throws IOException { + return isSameName(currentName, newName, hostname, getDefaultKerberosRealm()); + } + + static boolean isSameName(String currentName, String newName, String hostname, String defaultRealm) throws IOException { + final boolean newNameContainsRealm = newName.indexOf('@') != -1; // Make sure to replace "_HOST" if it exists before comparing the principals. if (newName.contains(org.apache.hadoop.security.SecurityUtil.HOSTNAME_PATTERN)) { - newName = org.apache.hadoop.security.SecurityUtil.getServerPrincipal(newName, hostname); + if (newNameContainsRealm) { + newName = org.apache.hadoop.security.SecurityUtil.getServerPrincipal(newName, hostname); + } else { + // If the principal ends with "/_HOST", replace "_HOST" with the hostname. + if (newName.endsWith("/_HOST")) { + newName = newName.substring(0, newName.length() - 5) + hostname; + } + } + } + // The new name doesn't contain a realm and we could compute a default realm + if (!newNameContainsRealm && defaultRealm != null) { + return currentName.equals(newName + "@" + defaultRealm); } + // We expect both names to contain a realm, so we can do a simple equality check return currentName.equals(newName); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/c47ee89a/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriverTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriverTest.java b/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriverTest.java index 59465e8..ba64892 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriverTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixEmbeddedDriverTest.java @@ -167,5 +167,24 @@ public class PhoenixEmbeddedDriverTest { assertTrue(ConnectionInfo.isSameName("user/localh...@example.com", "user/_h...@example.com", "localhost")); assertFalse(ConnectionInfo.isSameName("user/foo...@example.com", "user/_h...@example.com", "localhost")); assertFalse(ConnectionInfo.isSameName("u...@example.com", "user/_h...@example.com", "localhost")); + assertFalse(ConnectionInfo.isSameName("user@FOO", "user@BAR")); + + // NB: We _should_ be able to provide our or krb5.conf for this test to use, but this doesn't + // seem to want to play nicely with the rest of the tests. Instead, we can just provide a default realm + // by hand. + + // For an implied default realm, we should also match that. Users might provide a shortname + // whereas UGI would provide the "full" name. + assertTrue(ConnectionInfo.isSameName("u...@apache.org", "user", null, "APACHE.ORG")); + assertTrue(ConnectionInfo.isSameName("user/localh...@apache.org", "user/localhost", null, "APACHE.ORG")); + assertFalse(ConnectionInfo.isSameName("u...@apache.net", "user", null, "APACHE.ORG")); + assertFalse(ConnectionInfo.isSameName("user/localh...@apache.net", "user/localhost", null, "APACHE.ORG")); + assertTrue(ConnectionInfo.isSameName("u...@apache.org", "u...@apache.org", null, "APACHE.ORG")); + assertTrue(ConnectionInfo.isSameName("user/localh...@apache.org", "user/localh...@apache.org", null, "APACHE.ORG")); + + assertTrue(ConnectionInfo.isSameName("user/localh...@apache.org", "user/_HOST", "localhost", "APACHE.ORG")); + assertTrue(ConnectionInfo.isSameName("user/foo...@apache.org", "user/_HOST", "foobar", "APACHE.ORG")); + assertFalse(ConnectionInfo.isSameName("user/localh...@apache.net", "user/_HOST", "localhost", "APACHE.ORG")); + assertFalse(ConnectionInfo.isSameName("user/foo...@apache.net", "user/_HOST", "foobar", "APACHE.ORG")); } }