PHOENIX-4749 Allow impersonation when SPNEGO is disabled Client impersonation is no longer tied to SPNEGO auth.
Signed-off-by: Josh Elser <els...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/838914e0 Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/838914e0 Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/838914e0 Branch: refs/heads/4.x-HBase-1.1 Commit: 838914e07a53f89586d35763466332e40b6533e8 Parents: 0132bf6 Author: Alex Araujo <alexara...@gmail.com> Authored: Wed May 23 10:28:48 2018 -0500 Committer: Josh Elser <els...@apache.org> Committed: Fri May 25 16:21:42 2018 -0400 ---------------------------------------------------------------------- .../phoenix/queryserver/server/QueryServer.java | 86 +++++++++++--------- .../server/QueryServerConfigurationTest.java | 72 ++++++++++++++++ 2 files changed, 121 insertions(+), 37 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/838914e0/phoenix-queryserver/src/main/java/org/apache/phoenix/queryserver/server/QueryServer.java ---------------------------------------------------------------------- diff --git a/phoenix-queryserver/src/main/java/org/apache/phoenix/queryserver/server/QueryServer.java b/phoenix-queryserver/src/main/java/org/apache/phoenix/queryserver/server/QueryServer.java index 6b1fcfe..e3f0f52 100644 --- a/phoenix-queryserver/src/main/java/org/apache/phoenix/queryserver/server/QueryServer.java +++ b/phoenix-queryserver/src/main/java/org/apache/phoenix/queryserver/server/QueryServer.java @@ -54,6 +54,7 @@ import org.apache.phoenix.queryserver.register.Registry; import org.apache.phoenix.util.InstanceResolver; import java.io.File; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.InetAddress; @@ -197,7 +198,7 @@ public final class QueryServer extends Configured implements Tool, Runnable { QueryServicesOptions.DEFAULT_QUERY_SERVER_DISABLE_KERBEROS_LOGIN); // handle secure cluster credentials - if (isKerberos && !disableSpnego && !disableLogin) { + if (isKerberos && !disableLogin) { hostname = Strings.domainNamePointerToHostName(DNS.getDefaultHost( getConf().get(QueryServices.QUERY_SERVER_DNS_INTERFACE_ATTRIB, "default"), getConf().get(QueryServices.QUERY_SERVER_DNS_NAMESERVER_ATTRIB, "default"))); @@ -230,43 +231,9 @@ public final class QueryServer extends Configured implements Tool, Runnable { final HttpServer.Builder builder = new HttpServer.Builder().withPort(port) .withHandler(service, getSerialization(getConf())); - // Enable SPNEGO and Impersonation when using Kerberos + // Enable client auth when using Kerberos auth for HBase if (isKerberos) { - UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - LOG.debug("Current user is " + ugi); - if (!ugi.hasKerberosCredentials()) { - ugi = UserGroupInformation.getLoginUser(); - LOG.debug("Current user does not have Kerberos credentials, using instead " + ugi); - } - - // Make sure the proxyuser configuration is up to date - ProxyUsers.refreshSuperUserGroupsConfiguration(getConf()); - - String keytabPath = getConf().get(QueryServices.QUERY_SERVER_KEYTAB_FILENAME_ATTRIB); - File keytab = new File(keytabPath); - String httpKeytabPath = getConf().get(QueryServices.QUERY_SERVER_HTTP_KEYTAB_FILENAME_ATTRIB, null); - String httpPrincipal = getConf().get(QueryServices.QUERY_SERVER_KERBEROS_HTTP_PRINCIPAL_ATTRIB, null); - File httpKeytab = null; - if (null != httpKeytabPath) - httpKeytab = new File(httpKeytabPath); - - String realmsString = getConf().get(QueryServices.QUERY_SERVER_KERBEROS_ALLOWED_REALMS, null); - String[] additionalAllowedRealms = null; - if (null != realmsString) { - additionalAllowedRealms = StringUtils.split(realmsString, ','); - } - - // Enable SPNEGO and impersonation (through standard Hadoop configuration means) - if ((null != httpKeytabPath) && (null != httpPrincipal)) - builder.withSpnego(httpPrincipal, additionalAllowedRealms) - .withAutomaticLogin(httpKeytab) - .withImpersonation(new PhoenixDoAsCallback(ugi, getConf())); - else - builder.withSpnego(ugi.getUserName(), additionalAllowedRealms) - .withAutomaticLogin(keytab) - .withImpersonation(new PhoenixDoAsCallback(ugi, getConf())); - - + configureClientAuthentication(builder, disableSpnego); } setRemoteUserExtractorIfNecessary(builder, getConf()); @@ -290,6 +257,51 @@ public final class QueryServer extends Configured implements Tool, Runnable { } } + @VisibleForTesting + void configureClientAuthentication(final HttpServer.Builder builder, boolean disableSpnego) throws IOException { + UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + LOG.debug("Current user is " + ugi); + if (!ugi.hasKerberosCredentials()) { + ugi = UserGroupInformation.getLoginUser(); + LOG.debug("Current user does not have Kerberos credentials, using instead " + ugi); + } + + // Make sure the proxyuser configuration is up to date + ProxyUsers.refreshSuperUserGroupsConfiguration(getConf()); + + // Always enable impersonation for the proxy user (through standard Hadoop configuration means) + builder.withImpersonation(new PhoenixDoAsCallback(ugi, getConf())); + + // Enable SPNEGO for client authentication unless it's explicitly disabled + if (!disableSpnego) { + String keytabPath = getConf().get(QueryServices.QUERY_SERVER_KEYTAB_FILENAME_ATTRIB); + File keytab = new File(keytabPath); + String httpKeytabPath = + getConf().get(QueryServices.QUERY_SERVER_HTTP_KEYTAB_FILENAME_ATTRIB, null); + String httpPrincipal = + getConf().get(QueryServices.QUERY_SERVER_KERBEROS_HTTP_PRINCIPAL_ATTRIB, null); + // Backwards compat for a configuration key change + if (httpPrincipal == null) { + httpPrincipal = + getConf().get(QueryServices.QUERY_SERVER_KERBEROS_HTTP_PRINCIPAL_ATTRIB_LEGACY, null); + } + File httpKeytab = null; + if (null != httpKeytabPath) httpKeytab = new File(httpKeytabPath); + + String realmsString = getConf().get(QueryServices.QUERY_SERVER_KERBEROS_ALLOWED_REALMS, null); + String[] additionalAllowedRealms = null; + if (null != realmsString) { + additionalAllowedRealms = StringUtils.split(realmsString, ','); + } + if ((null != httpKeytabPath) && (null != httpPrincipal)) { + builder.withSpnego(httpPrincipal, additionalAllowedRealms).withAutomaticLogin(httpKeytab); + } else { + builder.withSpnego(ugi.getUserName(), additionalAllowedRealms) + .withAutomaticLogin(keytab); + } + } + } + public synchronized void stop() { server.stop(); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/838914e0/phoenix-queryserver/src/test/java/org/apache/phoenix/queryserver/server/QueryServerConfigurationTest.java ---------------------------------------------------------------------- diff --git a/phoenix-queryserver/src/test/java/org/apache/phoenix/queryserver/server/QueryServerConfigurationTest.java b/phoenix-queryserver/src/test/java/org/apache/phoenix/queryserver/server/QueryServerConfigurationTest.java new file mode 100644 index 0000000..f2a1022 --- /dev/null +++ b/phoenix-queryserver/src/test/java/org/apache/phoenix/queryserver/server/QueryServerConfigurationTest.java @@ -0,0 +1,72 @@ +/* + * 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.phoenix.queryserver.server; + +import java.io.File; +import java.io.IOException; + +import org.apache.calcite.avatica.server.DoAsRemoteUserCallback; +import org.apache.calcite.avatica.server.HttpServer; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.phoenix.query.QueryServices; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.mockito.Mockito.*; + +public class QueryServerConfigurationTest { + private static final Configuration CONF = HBaseConfiguration.create(); + + @Rule public TemporaryFolder testFolder = new TemporaryFolder(); + + private HttpServer.Builder builder; + private QueryServer queryServer; + + @Before + public void setup() throws IOException { + File keytabFile = testFolder.newFile("test.keytab"); + CONF.set(QueryServices.QUERY_SERVER_KEYTAB_FILENAME_ATTRIB, keytabFile.getAbsolutePath()); + builder = mock(HttpServer.Builder.class); + queryServer = new QueryServer(new String[0], CONF); + } + + @Test + public void testSpnegoEnabled() throws IOException { + // SPENEGO settings will be provided to the builder when enabled + doReturn(builder).when(builder).withSpnego(anyString(), any(String[].class)); + configureAndVerifyImpersonation(builder, false); + // A keytab file will also be provided for automatic login + verify(builder).withAutomaticLogin(any(File.class)); + } + + @Test + public void testSpnegoDisabled() throws IOException { + configureAndVerifyImpersonation(builder, true); + verify(builder, never()).withSpnego(anyString(), any(String[].class)); + verify(builder, never()).withAutomaticLogin(any(File.class)); + } + + private void configureAndVerifyImpersonation(HttpServer.Builder builder, boolean disableSpnego) + throws IOException { + queryServer.configureClientAuthentication(builder, disableSpnego); + verify(builder).withImpersonation(any(DoAsRemoteUserCallback.class)); + } +}