DRILL-4280: CORE (service login) + Support Drillbit login to KDC using Hadoop's UserGroupInformation library
+ Set hostname in BootstrapContext + Use process user's short name in ImpersonationUtil + Add KerberosUtil class Project: http://git-wip-us.apache.org/repos/asf/drill/repo Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/d732aad2 Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/d732aad2 Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/d732aad2 Branch: refs/heads/master Commit: d732aad2dc59d8db12f6dc64b3a4e60b470d00f6 Parents: 0501a67 Author: Sudheesh Katkam <sudhe...@apache.org> Authored: Wed Jan 25 18:51:32 2017 -0800 Committer: Sudheesh Katkam <sudhe...@apache.org> Committed: Fri Feb 24 19:01:42 2017 -0800 ---------------------------------------------------------------------- .../org/apache/drill/common/KerberosUtil.java | 93 +++++++++++++++++++ .../drill/exec/server/BootStrapContext.java | 98 +++++++++++++++++++- .../org/apache/drill/exec/server/Drillbit.java | 5 +- .../drill/exec/service/ServiceEngine.java | 54 ++++------- .../drill/exec/util/ImpersonationUtil.java | 4 +- 5 files changed, 210 insertions(+), 44 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/drill/blob/d732aad2/common/src/main/java/org/apache/drill/common/KerberosUtil.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/drill/common/KerberosUtil.java b/common/src/main/java/org/apache/drill/common/KerberosUtil.java new file mode 100644 index 0000000..6b8301c --- /dev/null +++ b/common/src/main/java/org/apache/drill/common/KerberosUtil.java @@ -0,0 +1,93 @@ +/* + * 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.drill.common; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class KerberosUtil { + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(KerberosUtil.class); + + // Per this link http://docs.oracle.com/javase/jndi/tutorial/ldap/security/gssapi.html + // "... GSS-API SASL mechanism was retrofitted to mean only Kerberos v5 ..." + public static final String KERBEROS_SASL_NAME = "GSSAPI"; + + public static final String KERBEROS_SIMPLE_NAME = "KERBEROS"; + + public static final String HOSTNAME_PATTERN = "_HOST"; + + /** + * Returns principal of format primary/instance@REALM. + * + * @param primary non-null primary component + * @param instance non-null instance component + * @param realm non-null realm component + * @return principal of format primary/instance@REALM + */ + public static String getPrincipalFromParts(final String primary, final String instance, final String realm) { + return checkNotNull(primary) + "/" + + checkNotNull(instance) + "@" + + checkNotNull(realm); + } + + /** + * Expects principal of the format primary/instance@REALM. + * + * @param principal principal + * @return components + */ + public static String[] splitPrincipalIntoParts(final String principal) { + final String[] components = principal.split("[/@]"); + checkState(components.length == 3); + checkNotNull(components[0]); + checkNotNull(components[1]); + checkNotNull(components[2]); + return components; + } + + public static String canonicalizeInstanceName(String instanceName, final String canonicalName) { + if (instanceName == null || HOSTNAME_PATTERN.equalsIgnoreCase(instanceName)) { + instanceName = canonicalName; + } + + final String lowercaseName = instanceName.toLowerCase(); + if (!instanceName.equals(lowercaseName)) { + logger.warn("Converting service name ({}) to lowercase, see HADOOP-7988.", instanceName); + } + return lowercaseName; + } + + public static String getDefaultRealm() throws ClassNotFoundException, NoSuchMethodException, + IllegalArgumentException, IllegalAccessException, InvocationTargetException { + final Class<?> classRef = System.getProperty("java.vendor").contains("IBM") ? + Class.forName("com.ibm.security.krb5.internal.Config") : + Class.forName("sun.security.krb5.Config"); + + final Method getInstanceMethod = classRef.getMethod("getInstance", new Class[0]); + final Object kerbConf = getInstanceMethod.invoke(classRef, new Object[0]); + final Method getDefaultRealmMethod = classRef.getDeclaredMethod("getDefaultRealm", new Class[0]); + return (String) getDefaultRealmMethod.invoke(kerbConf, new Object[0]); + } + + // prevent instantiation + private KerberosUtil() { + } +} http://git-wip-us.apache.org/repos/asf/drill/blob/d732aad2/exec/java-exec/src/main/java/org/apache/drill/exec/server/BootStrapContext.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/BootStrapContext.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/BootStrapContext.java index c498185..90ab018 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/BootStrapContext.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/BootStrapContext.java @@ -20,26 +20,46 @@ package org.apache.drill.exec.server; import com.codahale.metrics.MetricRegistry; import io.netty.channel.EventLoopGroup; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.SynchronousQueue; -import org.apache.drill.common.DrillAutoCloseables; +import org.apache.drill.common.AutoCloseables; +import org.apache.drill.common.KerberosUtil; import org.apache.drill.common.config.DrillConfig; import org.apache.drill.common.scanner.persistence.ScanResult; import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.exception.DrillbitStartupException; import org.apache.drill.exec.memory.BufferAllocator; import org.apache.drill.exec.memory.RootAllocatorFactory; import org.apache.drill.exec.metrics.DrillMetrics; import org.apache.drill.exec.rpc.NamedThreadFactory; import org.apache.drill.exec.rpc.TransportCheck; +import org.apache.drill.exec.rpc.security.AuthenticatorProvider; +import org.apache.drill.exec.rpc.security.AuthenticatorProviderImpl; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.CommonConfigurationKeys; +import org.apache.hadoop.security.UserGroupInformation; public class BootStrapContext implements AutoCloseable { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BootStrapContext.class); private static final int MIN_SCAN_THREADPOOL_SIZE = 8; // Magic num + // DRILL_HOST_NAME sets custom host name. See drill-env.sh for details. + private static final String customHostName = System.getenv("DRILL_HOST_NAME"); + private static final String processUserName = System.getProperty("user.name"); + + private static final String SERVICE_LOGIN_PREFIX = "drill.exec.security.auth"; + public static final String SERVICE_PRINCIPAL = SERVICE_LOGIN_PREFIX + ".principal"; + public static final String SERVICE_KEYTAB_LOCATION = SERVICE_LOGIN_PREFIX + ".keytab"; + public static final String KERBEROS_NAME_MAPPING = SERVICE_LOGIN_PREFIX + ".auth_to_local"; + private final DrillConfig config; + private final AuthenticatorProvider authProvider; private final EventLoopGroup loop; private final EventLoopGroup loop2; private final MetricRegistry metrics; @@ -48,10 +68,14 @@ public class BootStrapContext implements AutoCloseable { private final ExecutorService executor; private final ExecutorService scanExecutor; private final ExecutorService scanDecodeExecutor; + private final String hostName; - public BootStrapContext(DrillConfig config, ScanResult classpathScan) { + public BootStrapContext(DrillConfig config, ScanResult classpathScan) throws DrillbitStartupException { this.config = config; this.classpathScan = classpathScan; + this.hostName = getCanonicalHostName(); + login(config); + this.authProvider = new AuthenticatorProviderImpl(config, classpathScan); this.loop = TransportCheck.createEventLoopGroup(config.getInt(ExecConstants.BIT_SERVER_RPC_THREADS), "BitServer-"); this.loop2 = TransportCheck.createEventLoopGroup(config.getInt(ExecConstants.BIT_SERVER_RPC_THREADS), "BitClient-"); // Note that metrics are stored in a static instance @@ -85,6 +109,66 @@ public class BootStrapContext implements AutoCloseable { Executors.newFixedThreadPool(scanDecodeThreadPoolSize, new NamedThreadFactory("scan-decode-")); } + private void login(final DrillConfig config) throws DrillbitStartupException { + try { + if (config.hasPath(SERVICE_PRINCIPAL)) { + // providing a service principal => Kerberos mechanism + final Configuration loginConf = new Configuration(); + loginConf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, + UserGroupInformation.AuthenticationMethod.KERBEROS.toString()); + + // set optional user name mapping + if (config.hasPath(KERBEROS_NAME_MAPPING)) { + loginConf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTH_TO_LOCAL, + config.getString(KERBEROS_NAME_MAPPING)); + } + + UserGroupInformation.setConfiguration(loginConf); + + // service principal canonicalization + final String principal = config.getString(SERVICE_PRINCIPAL); + final String parts[] = KerberosUtil.splitPrincipalIntoParts(principal); + if (parts.length != 3) { + throw new DrillbitStartupException( + String.format("Invalid %s, Drill service principal must be of format: primary/instance@REALM", + SERVICE_PRINCIPAL)); + } + parts[1] = KerberosUtil.canonicalizeInstanceName(parts[1], hostName); + + final String canonicalizedPrincipal = KerberosUtil.getPrincipalFromParts(parts[0], parts[1], parts[2]); + final String keytab = config.getString(SERVICE_KEYTAB_LOCATION); + + // login to KDC (AS) + // Note that this call must happen before any call to UserGroupInformation#getLoginUser, + // but there is no way to enforce the order (this static init. call and parameters from + // DrillConfig are both required). + UserGroupInformation.loginUserFromKeytab(canonicalizedPrincipal, keytab); + + logger.info("Process user name: '{}' and logged in successfully as '{}'", processUserName, + canonicalizedPrincipal); + } else { + UserGroupInformation.getLoginUser(); // init + } + + // ugi does not support logout + } catch (final IOException e) { + throw new DrillbitStartupException("Failed to login.", e); + } + + } + + private static String getCanonicalHostName() throws DrillbitStartupException { + try { + return customHostName != null ? customHostName : InetAddress.getLocalHost().getCanonicalHostName(); + } catch (final UnknownHostException e) { + throw new DrillbitStartupException("Could not get canonical hostname.", e); + } + } + + public String getHostName() { + return hostName; + } + public ExecutorService getExecutor() { return executor; } @@ -121,6 +205,10 @@ public class BootStrapContext implements AutoCloseable { return classpathScan; } + public AuthenticatorProvider getAuthProvider() { + return authProvider; + } + @Override public void close() { try { @@ -150,6 +238,10 @@ public class BootStrapContext implements AutoCloseable { } } - DrillAutoCloseables.closeNoChecked(allocator); + try { + AutoCloseables.close(allocator, authProvider); + } catch (final Exception e) { + logger.error("Error while closing", e); + } } } http://git-wip-us.apache.org/repos/asf/drill/blob/d732aad2/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java index b4300e0..f225714 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/Drillbit.java @@ -94,7 +94,7 @@ public class Drillbit implements AutoCloseable { context = new BootStrapContext(config, classpathScan); manager = new WorkManager(context); - webServer = new WebServer(config, context.getMetrics(), manager); + webServer = new WebServer(context, manager); boolean isDistributedMode = false; if (serviceSet != null) { coord = serviceSet.getCoordinator(); @@ -105,8 +105,7 @@ public class Drillbit implements AutoCloseable { isDistributedMode = true; } - engine = new ServiceEngine(manager.getControlMessageHandler(), manager.getUserWorker(), context, - manager.getWorkBus(), manager.getBee(), allowPortHunting, isDistributedMode); + engine = new ServiceEngine(manager, context, allowPortHunting, isDistributedMode); logger.info("Construction completed ({} ms).", w.elapsed(TimeUnit.MILLISECONDS)); } http://git-wip-us.apache.org/repos/asf/drill/blob/d732aad2/exec/java-exec/src/main/java/org/apache/drill/exec/service/ServiceEngine.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/service/ServiceEngine.java b/exec/java-exec/src/main/java/org/apache/drill/exec/service/ServiceEngine.java index 5cad0d4..aa43ee5 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/service/ServiceEngine.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/service/ServiceEngine.java @@ -39,13 +39,10 @@ import org.apache.drill.exec.proto.CoordinationProtos.DrillbitEndpoint; import org.apache.drill.exec.rpc.TransportCheck; import org.apache.drill.exec.rpc.control.Controller; import org.apache.drill.exec.rpc.control.ControllerImpl; -import org.apache.drill.exec.rpc.control.WorkEventBus; import org.apache.drill.exec.rpc.data.DataConnectionCreator; import org.apache.drill.exec.rpc.user.UserServer; import org.apache.drill.exec.server.BootStrapContext; -import org.apache.drill.exec.work.WorkManager.WorkerBee; -import org.apache.drill.exec.work.batch.ControlMessageHandler; -import org.apache.drill.exec.work.user.UserWorker; +import org.apache.drill.exec.work.WorkManager; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; @@ -58,16 +55,17 @@ public class ServiceEngine implements AutoCloseable { private final Controller controller; private final DataConnectionCreator dataPool; private final DrillConfig config; - boolean useIP = false; private final boolean allowPortHunting; private final boolean isDistributedMode; private final BufferAllocator userAllocator; private final BufferAllocator controlAllocator; private final BufferAllocator dataAllocator; + private final String hostName; - public ServiceEngine(ControlMessageHandler controlMessageHandler, UserWorker userWorker, BootStrapContext context, - WorkEventBus workBus, WorkerBee bee, boolean allowPortHunting, boolean isDistributedMode) throws DrillbitStartupException { + public ServiceEngine(final WorkManager manager, final BootStrapContext context, + final boolean allowPortHunting, final boolean isDistributedMode) + throws DrillbitStartupException { userAllocator = newAllocator(context, "rpc:user", "drill.exec.rpc.user.server.memory.reservation", "drill.exec.rpc.user.server.memory.maximum"); controlAllocator = newAllocator(context, "rpc:bit-control", @@ -76,18 +74,15 @@ public class ServiceEngine implements AutoCloseable { "drill.exec.rpc.bit.server.memory.data.reservation", "drill.exec.rpc.bit.server.memory.data.maximum"); final EventLoopGroup eventLoopGroup = TransportCheck.createEventLoopGroup( context.getConfig().getInt(ExecConstants.USER_SERVER_RPC_THREADS), "UserServer-"); - this.userServer = new UserServer( - context.getConfig(), - context.getClasspathScan(), - userAllocator, - eventLoopGroup, - userWorker, - context.getExecutor()); - this.controller = new ControllerImpl(context, controlMessageHandler, controlAllocator, allowPortHunting); - this.dataPool = new DataConnectionCreator(context, dataAllocator, workBus, bee, allowPortHunting); + userServer = new UserServer(context, userAllocator, eventLoopGroup, manager.getUserWorker()); + controller = new ControllerImpl(context, controlAllocator, manager.getControlMessageHandler(), + allowPortHunting); + dataPool = new DataConnectionCreator(context, dataAllocator, manager.getWorkBus(), manager.getBee(), + allowPortHunting); this.config = context.getConfig(); this.allowPortHunting = allowPortHunting; this.isDistributedMode = isDistributedMode; + this.hostName = context.getHostName(); registerMetrics(context.getMetrics()); } @@ -141,23 +136,16 @@ public class ServiceEngine implements AutoCloseable { name, context.getConfig().getLong(initReservation), context.getConfig().getLong(maxAllocation)); } - private String getHostName() throws UnknownHostException{ - // DRILL_HOST_NAME sets custom host name. See drill-env.sh for details. - String customHost = System.getenv("DRILL_HOST_NAME"); - if (customHost != null) { - return customHost; - } - return useIP ? InetAddress.getLocalHost().getHostAddress() : InetAddress.getLocalHost().getCanonicalHostName(); - } + public DrillbitEndpoint start() throws DrillbitStartupException, UnknownHostException { + final int userPort = userServer.bind(config.getInt(ExecConstants.INITIAL_USER_PORT), allowPortHunting); - public DrillbitEndpoint start() throws DrillbitStartupException, UnknownHostException{ - int userPort = userServer.bind(config.getInt(ExecConstants.INITIAL_USER_PORT), allowPortHunting); - String address = getHostName(); - checkLoopbackAddress(address); + // loopback address check + if (isDistributedMode && InetAddress.getByName(hostName).isLoopbackAddress()) { + throw new DrillbitStartupException("Drillbit is disallowed to bind to loopback address in distributed mode."); + } DrillbitEndpoint partialEndpoint = DrillbitEndpoint.newBuilder() - .setAddress(address) - //.setAddress("localhost") + .setAddress(hostName) .setUserPort(userPort) .setVersion(DrillVersionInfo.getVersion()) .build(); @@ -192,12 +180,6 @@ public class ServiceEngine implements AutoCloseable { }); } - private void checkLoopbackAddress(String address) throws DrillbitStartupException, UnknownHostException { - if (isDistributedMode && InetAddress.getByName(address).isLoopbackAddress()) { - throw new DrillbitStartupException("Drillbit is disallowed to bind to loopback address in distributed mode."); - } - } - @Override public void close() throws Exception { // this takes time so close them in parallel http://git-wip-us.apache.org/repos/asf/drill/blob/d732aad2/exec/java-exec/src/main/java/org/apache/drill/exec/util/ImpersonationUtil.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/util/ImpersonationUtil.java b/exec/java-exec/src/main/java/org/apache/drill/exec/util/ImpersonationUtil.java index 93ee7a0..8dab549 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/util/ImpersonationUtil.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/util/ImpersonationUtil.java @@ -1,4 +1,4 @@ -/** +/* * 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 @@ -170,7 +170,7 @@ public class ImpersonationUtil { * @return Drillbit process user. */ public static String getProcessUserName() { - return getProcessUserUGI().getUserName(); + return getProcessUserUGI().getShortUserName(); } /**