diff options
20 files changed, 607 insertions, 30 deletions
diff --git a/distribution/src/resources/drill-override-example.conf b/distribution/src/resources/drill-override-example.conf index 4cd342a05..943d6443a 100644 --- a/distribution/src/resources/drill-override-example.conf +++ b/distribution/src/resources/drill-override-example.conf @@ -105,7 +105,13 @@ drill.exec: { path: "/tmp/drill", write: true } - } + }, + security.user.auth { + enabled: false, + packages += "org.apache.drill.exec.rpc.user.security", + impl: "pam", + pam_profiles: [ "sudo", "login" ] + }, trace: { directory: "/tmp/drill-trace", filesystem: "file:///" diff --git a/exec/java-exec/pom.xml b/exec/java-exec/pom.xml index dfb1a6b9d..7c0853458 100644 --- a/exec/java-exec/pom.xml +++ b/exec/java-exec/pom.xml @@ -133,6 +133,10 @@ <artifactId>optiq-core</artifactId> </dependency> <dependency> + <groupId>net.sf.jpam</groupId> + <artifactId>jpam</artifactId> + </dependency> + <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.19</version> diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java index 14e6ad13f..bd93206d5 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java @@ -74,6 +74,10 @@ public interface ExecConstants { public static final String SYS_STORE_PROVIDER_CLASS = "drill.exec.sys.store.provider.class"; public static final String SYS_STORE_PROVIDER_LOCAL_PATH = "drill.exec.sys.store.provider.local.path"; public static final String SYS_STORE_PROVIDER_LOCAL_ENABLE_WRITE = "drill.exec.sys.store.provider.local.write"; + public static final String USER_AUTHENTICATOR_IMPL_PACKAGES = "drill.exec.security.user.auth.packages"; + public static final String USER_AUTHENTICATION_ENABLED = "drill.exec.security.user.auth.enabled"; + public static final String USER_AUTHENTICATOR_IMPL = "drill.exec.security.user.auth.impl"; + public static final String PAM_AUTHENTICATOR_PROFILES = "drill.exec.security.user.auth.pam_profiles"; public static final String ERROR_ON_MEMORY_LEAK = "drill.exec.debug.error_on_leak"; /** Fragment memory planning */ public static final String ENABLE_FRAGMENT_MEMORY_LIMIT = "drill.exec.memory.enable_frag_limit"; diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserServer.java index 17f189dc1..877bc0832 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserServer.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserServer.java @@ -17,6 +17,7 @@ */ package org.apache.drill.exec.rpc.user; +import com.google.common.io.Closeables; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.channel.Channel; @@ -26,6 +27,9 @@ import io.netty.channel.EventLoopGroup; import java.io.IOException; import java.util.UUID; +import org.apache.drill.common.config.DrillConfig; +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.physical.impl.materialize.QueryWritableBatch; import org.apache.drill.exec.proto.GeneralRPCProtos.Ack; @@ -34,8 +38,10 @@ import org.apache.drill.exec.proto.UserBitShared.QueryId; import org.apache.drill.exec.proto.UserBitShared.QueryResult; import org.apache.drill.exec.proto.UserProtos.BitToUserHandshake; import org.apache.drill.exec.proto.UserProtos.HandshakeStatus; +import org.apache.drill.exec.proto.UserProtos.Property; import org.apache.drill.exec.proto.UserProtos.RpcType; import org.apache.drill.exec.proto.UserProtos.RunQuery; +import org.apache.drill.exec.proto.UserProtos.UserProperties; import org.apache.drill.exec.proto.UserProtos.UserToBitHandshake; import org.apache.drill.exec.rpc.BasicServer; import org.apache.drill.exec.rpc.OutOfMemoryHandler; @@ -45,6 +51,9 @@ import org.apache.drill.exec.rpc.RemoteConnection; import org.apache.drill.exec.rpc.Response; import org.apache.drill.exec.rpc.RpcException; import org.apache.drill.exec.rpc.RpcOutcomeListener; +import org.apache.drill.exec.rpc.user.security.UserAuthenticationException; +import org.apache.drill.exec.rpc.user.security.UserAuthenticator; +import org.apache.drill.exec.rpc.user.security.UserAuthenticatorFactory; import org.apache.drill.exec.work.user.UserWorker; import com.google.protobuf.InvalidProtocolBufferException; @@ -55,11 +64,18 @@ public class UserServer extends BasicServer<RpcType, UserServer.UserClientConnec final UserWorker worker; final BufferAllocator alloc; + final UserAuthenticator authenticator; - public UserServer(BufferAllocator alloc, EventLoopGroup eventLoopGroup, UserWorker worker) { + public UserServer(DrillConfig config, BufferAllocator alloc, EventLoopGroup eventLoopGroup, + UserWorker worker) throws DrillbitStartupException { super(UserRpcConfig.MAPPING, alloc.getUnderlyingAllocator(), eventLoopGroup); this.worker = worker; this.alloc = alloc; + if (config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED)) { + authenticator = UserAuthenticatorFactory.createAuthenticator(config); + } else { + authenticator = null; + } } @Override @@ -101,7 +117,6 @@ public class UserServer extends BasicServer<RpcType, UserServer.UserClientConnec } - public class UserClientConnection extends RemoteConnection { private UserSession session; @@ -180,6 +195,23 @@ public class UserServer extends BasicServer<RpcType, UserServer.UserClientConnec return handleFailure(respBuilder, HandshakeStatus.RPC_VERSION_MISMATCH, errMsg, null); } + if (authenticator != null) { + try { + String password = ""; + final UserProperties props = inbound.getProperties(); + for (int i = 0; i < props.getPropertiesCount(); i++) { + Property prop = props.getProperties(i); + if (UserSession.PASSWORD.equalsIgnoreCase(prop.getKey())) { + password = prop.getValue(); + break; + } + } + authenticator.authenticate(inbound.getCredentials().getUserName(), password); + } catch (UserAuthenticationException ex) { + return handleFailure(respBuilder, HandshakeStatus.AUTH_FAILED, ex.getMessage(), ex); + } + } + connection.setUser(inbound); return respBuilder.setStatus(HandshakeStatus.SUCCESS).build(); @@ -221,4 +253,10 @@ public class UserServer extends BasicServer<RpcType, UserServer.UserClientConnec public ProtobufLengthDecoder getDecoder(BufferAllocator allocator, OutOfMemoryHandler outOfMemoryHandler) { return new UserProtobufLengthDecoder(allocator, outOfMemoryHandler); } + + @Override + public void close() throws IOException { + Closeables.closeQuietly(authenticator); + super.close(); + } } diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserSession.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserSession.java index efb0cdf93..19d77b045 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserSession.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserSession.java @@ -33,6 +33,8 @@ public class UserSession { static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(UserSession.class); public static final String SCHEMA = "schema"; + public static final String USER = "user"; + public static final String PASSWORD = "password"; private DrillUser user; private boolean enableExchanges = true; diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/PamUserAuthenticator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/PamUserAuthenticator.java new file mode 100644 index 000000000..2928bfbcd --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/PamUserAuthenticator.java @@ -0,0 +1,71 @@ +/** + * 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.exec.rpc.user.security; + +import net.sf.jpam.Pam; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.common.exceptions.DrillRuntimeException; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.exception.DrillbitStartupException; + +import java.io.IOException; +import java.util.List; + +/** + * Implement {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} based on Pluggable Authentication + * Module (PAM) configuration. Configure the PAM profiles using "drill.exec.security.user.auth.pam_profiles" BOOT + * option. Ex. value <i>[ "login", "sudo" ]</i> (value is an array of strings). + */ +@UserAuthenticatorTemplate(type = "pam") +public class PamUserAuthenticator implements UserAuthenticator { + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(PamUserAuthenticator.class); + + private List<String> profiles; + + @Override + public void setup(DrillConfig drillConfig) throws DrillbitStartupException { + profiles = DrillConfig.create().getStringList(ExecConstants.PAM_AUTHENTICATOR_PROFILES); + + // Create a JPAM object so that it triggers loading of native "jpamlib" needed. Issues in loading/finding native + // "jpamlib" will be found it Drillbit start rather than when authenticating the first user. + try { + new Pam(); + } catch(LinkageError e) { + final String errMsg = "Problem in finding the native library of JPAM (Pluggable Authenticator Module API). " + + "Make sure to set Drillbit JVM option 'java.library.path' to point to the directory where the native " + + "JPAM exists."; + logger.error(errMsg, e); + throw new DrillbitStartupException(errMsg + ":" + e.getMessage(), e); + } + } + + @Override + public void authenticate(String user, String password) throws UserAuthenticationException { + for (String pamProfile : profiles) { + Pam pam = new Pam(pamProfile); + if (!pam.authenticateSuccessful(user, password)) { + throw new UserAuthenticationException(String.format("PAM profile '%s' validation failed", pamProfile)); + } + } + } + + @Override + public void close() throws IOException { + // No-op as no resources are occupied by PAM authenticator. + } +} diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticationException.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticationException.java new file mode 100644 index 000000000..ae1ce326d --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticationException.java @@ -0,0 +1,32 @@ +/** + * 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.exec.rpc.user.security; + +import org.apache.drill.common.exceptions.DrillException; + +public class UserAuthenticationException extends DrillException { + private static final String ERROR_MSG = "Invalid user credentials"; + + public UserAuthenticationException() { + super(ERROR_MSG); + } + + public UserAuthenticationException(String reason) { + super(String.format("%s: %s", ERROR_MSG, reason)); + } +} diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticator.java new file mode 100644 index 000000000..4d83138b2 --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticator.java @@ -0,0 +1,53 @@ +/** + * 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.exec.rpc.user.security; + +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.exec.exception.DrillbitStartupException; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Interface to provide various username/password based implementations for authentication. + */ +public interface UserAuthenticator extends Closeable { + + /** + * Setup for authenticating user credentials. + */ + public void setup(DrillConfig drillConfig) throws DrillbitStartupException; + + /** + * Authenticate the given <i>user</i> and <i>password</i> combination. + * + * @param user + * @param password + * @throws UserAuthenticationException if authentication fails for given user and password. + */ + public void authenticate(String user, String password) throws UserAuthenticationException; + + /** + * Close the authenticator. Used to release resources. Ex. LDAP authenticator opens connections to LDAP server, + * such connections resources are released in a safe manner as part of close. + * + * @throws IOException + */ + @Override + void close() throws IOException; +} diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorFactory.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorFactory.java new file mode 100644 index 000000000..51a597962 --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorFactory.java @@ -0,0 +1,106 @@ +/** + * 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.exec.rpc.user.security; + +import com.google.common.base.Strings; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.common.util.PathScanner; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.exception.DrillbitStartupException; + +import java.lang.reflect.Constructor; +import java.util.Collection; + +import static org.apache.drill.exec.ExecConstants.USER_AUTHENTICATOR_IMPL; + +/** + * Factory class which provides {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} implementation + * based on the BOOT options. + */ +public class UserAuthenticatorFactory { + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(UserAuthenticatorFactory.class); + + /** + * Create a {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} implementation based on BOOT settings in + * given <i>drillConfig</i>. + * + * @param config DrillConfig containing BOOT options. + * @return Initialized {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} implementation instance. + * It is responsibility of the caller to close the authenticator when no longer needed. + * + * @throws DrillbitStartupException when no implementation found for given BOOT options. + */ + public static UserAuthenticator createAuthenticator(final DrillConfig config) throws DrillbitStartupException { + final String authImplConfigured = config.getString(USER_AUTHENTICATOR_IMPL); + + if (Strings.isNullOrEmpty(authImplConfigured)) { + throw new DrillbitStartupException(String.format("Invalid value '%s' for BOOT option '%s'", authImplConfigured, + USER_AUTHENTICATOR_IMPL)); + } + + final Collection<Class<? extends UserAuthenticator>> authImpls = + PathScanner.scanForImplementations(UserAuthenticator.class, + config.getStringList(ExecConstants.USER_AUTHENTICATOR_IMPL_PACKAGES)); + + for(Class<? extends UserAuthenticator> clazz : authImpls) { + final UserAuthenticatorTemplate template = clazz.getAnnotation(UserAuthenticatorTemplate.class); + if (template == null) { + logger.warn("{} doesn't have {} annotation. Skipping.", clazz.getCanonicalName(), UserAuthenticatorTemplate.class); + continue; + } + + if (Strings.isNullOrEmpty(template.type())) { + logger.warn("{} annotation doesn't have valid type field for UserAuthenticator implementation {}. Skipping..", + UserAuthenticatorTemplate.class, clazz.getCanonicalName()); + continue; + } + + if (template.type().equalsIgnoreCase(authImplConfigured)) { + Constructor<?> validConstructor = null; + for (Constructor<?> c : clazz.getConstructors()) { + if (c.getParameterTypes().length == 0) { + validConstructor = c; + break; + } + } + + if (validConstructor == null) { + logger.warn("Skipping UserAuthenticator implementation class '{}' since it doesn't " + + "implement a constructor [{}()]", clazz.getCanonicalName(), clazz.getName()); + continue; + } + + // Instantiate authenticator and initialize it + try { + final UserAuthenticator authenticator = clazz.newInstance(); + authenticator.setup(config); + return authenticator; + } catch(IllegalArgumentException | IllegalAccessException | InstantiationException e) { + throw new DrillbitStartupException( + String.format("Failed to create and initialize the UserAuthenticator class '{}'", + clazz.getCanonicalName()), e); + } + } + } + + String errMsg = String.format("Failed to find the implementation of '{}' for type '{}'", + UserAuthenticator.class.getCanonicalName(), authImplConfigured); + logger.error(errMsg); + throw new DrillbitStartupException(errMsg); + } +} diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTemplate.java b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTemplate.java new file mode 100644 index 000000000..04be8d11b --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTemplate.java @@ -0,0 +1,37 @@ +/** + * 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.exec.rpc.user.security; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} implementation to identify the + * implementation type. Implementation type is set in BOOT option <i>drill.exec.security.user.auth.impl</i>. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface UserAuthenticatorTemplate { + /** + * {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} implementation type. + * @return + */ + String type(); +} 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 2efc9a998..25ea307cd 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 @@ -22,6 +22,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import io.netty.channel.EventLoopGroup; import org.apache.drill.common.config.DrillConfig; import org.apache.drill.exec.ExecConstants; import org.apache.drill.exec.exception.DrillbitStartupException; @@ -49,8 +50,11 @@ public class ServiceEngine implements Closeable{ boolean useIP = false; private final boolean allowPortHunting; - public ServiceEngine(ControlMessageHandler controlMessageHandler, UserWorker userWorker, BootStrapContext context, WorkEventBus workBus, DataResponseHandler dataHandler, boolean allowPortHunting){ - this.userServer = new UserServer(context.getAllocator(), TransportCheck.createEventLoopGroup(context.getConfig().getInt(ExecConstants.USER_SERVER_RPC_THREADS), "UserServer-"), userWorker); + public ServiceEngine(ControlMessageHandler controlMessageHandler, UserWorker userWorker, BootStrapContext context, + WorkEventBus workBus, DataResponseHandler dataHandler, boolean allowPortHunting) throws DrillbitStartupException { + final EventLoopGroup eventLoopGroup = TransportCheck.createEventLoopGroup( + context.getConfig().getInt(ExecConstants.USER_SERVER_RPC_THREADS), "UserServer-"); + this.userServer = new UserServer(context.getConfig(), context.getAllocator(), eventLoopGroup, userWorker); this.controller = new ControllerImpl(context, controlMessageHandler, allowPortHunting); this.dataPool = new DataConnectionCreator(context, workBus, dataHandler, allowPortHunting); this.config = context.getConfig(); diff --git a/exec/java-exec/src/main/resources/drill-module.conf b/exec/java-exec/src/main/resources/drill-module.conf index af225c450..6bd8db0d5 100644 --- a/exec/java-exec/src/main/resources/drill-module.conf +++ b/exec/java-exec/src/main/resources/drill-module.conf @@ -100,7 +100,13 @@ drill.exec: { path: "/tmp/drill", write: true } - } + }, + security.user.auth { + enabled: false, + packages += "org.apache.drill.exec.rpc.user.security", + impl: "pam", + pam_profiles: [ "sudo", "login" ] + }, trace: { directory: "/tmp/drill-trace", filesystem: "file:///" diff --git a/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java b/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java index c602a0100..725594ae2 100644 --- a/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java +++ b/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java @@ -56,16 +56,8 @@ import com.google.common.io.Resources; public class BaseTestQuery extends ExecTest { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BaseTestQuery.class); - /** - * Number of Drillbits in test cluster. Default is 1. - * - * Tests can update the cluster size through {@link #setDrillbitCount(int)} - */ - private static int drillbitCount = 1; - - private int[] columnWidths = new int[] { 8 }; - private static final String ENABLE_FULL_CACHE = "drill.exec.test.use-full-cache"; + private static final int MAX_WIDTH_PER_NODE = 2; @SuppressWarnings("serial") private static final Properties TEST_CONFIGURATIONS = new Properties() { @@ -93,17 +85,37 @@ public class BaseTestQuery extends ExecTest { protected static QuerySubmitter submitter = new QuerySubmitter(); protected static BufferAllocator allocator; - protected static void setDrillbitCount(int newDrillbitCount) { + /** + * Number of Drillbits in test cluster. Default is 1. + * + * Tests can update the cluster size through {@link #updateTestCluster(int, DrillConfig)} + */ + private static int drillbitCount = 1; + + private int[] columnWidths = new int[] { 8 }; + + @BeforeClass + public static void setupDefaultTestCluster() throws Exception { + config = DrillConfig.create(TEST_CONFIGURATIONS); + openClient(); + } + + protected static void updateTestCluster(int newDrillbitCount, DrillConfig newConfig) { Preconditions.checkArgument(newDrillbitCount > 0, "Number of Drillbits must be at least one"); - if (drillbitCount != newDrillbitCount) { + if (drillbitCount != newDrillbitCount || config != null) { // TODO: Currently we have to shutdown the existing Drillbit cluster before starting a new one with the given // Drillbit count. Revisit later to avoid stopping the cluster. try { closeClient(); drillbitCount = newDrillbitCount; + if (newConfig != null) { + // For next test class, updated DrillConfig will be replaced by default DrillConfig in BaseTestQuery as part + // of the @BeforeClass method of test class. + config = newConfig; + } openClient(); } catch(Exception e) { - throw new RuntimeException("Failure while changing the number of Drillbits in test cluster.", e); + throw new RuntimeException("Failure while updating the test Drillbit cluster.", e); } } } @@ -118,14 +130,12 @@ public class BaseTestQuery extends ExecTest { return bits[0].getContext(); } - static void resetClientAndBit() throws Exception{ + private static void resetClientAndBit() throws Exception{ closeClient(); openClient(); } - @BeforeClass - public static void openClient() throws Exception { - config = DrillConfig.create(TEST_CONFIGURATIONS); + private static void openClient() throws Exception { allocator = new TopLevelAllocator(config); if (config.hasPath(ENABLE_FULL_CACHE) && config.getBoolean(ENABLE_FULL_CACHE)) { serviceSet = RemoteServiceSet.getServiceSetWithFullCache(config, allocator); @@ -139,7 +149,22 @@ public class BaseTestQuery extends ExecTest { bits[i].run(); } - client = QueryTestUtil.createClient(config, serviceSet, 2); + client = QueryTestUtil.createClient(config, serviceSet, MAX_WIDTH_PER_NODE, null); + } + + /** + * Close the current <i>client</i> and open a new client using the given <i>properties</i>. All tests executed + * after this method call use the new <i>client</i>. + * + * @param properties + */ + public static void updateClient(Properties properties) throws Exception { + if (client != null) { + client.close(); + client = null; + } + + client = QueryTestUtil.createClient(config, serviceSet, MAX_WIDTH_PER_NODE, properties); } protected static BufferAllocator getAllocator() { diff --git a/exec/java-exec/src/test/java/org/apache/drill/QueryTestUtil.java b/exec/java-exec/src/test/java/org/apache/drill/QueryTestUtil.java index 82f175209..e218d6ce6 100644 --- a/exec/java-exec/src/test/java/org/apache/drill/QueryTestUtil.java +++ b/exec/java-exec/src/test/java/org/apache/drill/QueryTestUtil.java @@ -18,6 +18,7 @@ package org.apache.drill; import java.util.List; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -50,14 +51,14 @@ public class QueryTestUtil { * @param drillConfig * @param remoteServiceSet remote service set * @param maxWidth maximum width per node + * @param props Connection properties contains properties such as "user", "password", "schema" etc * @return the newly created client * @throws RpcException if there is a problem setting up the client */ - public static DrillClient createClient( - final DrillConfig drillConfig, final RemoteServiceSet remoteServiceSet, final int maxWidth) - throws RpcException { + public static DrillClient createClient(final DrillConfig drillConfig, final RemoteServiceSet remoteServiceSet, + final int maxWidth, final Properties props) throws RpcException { final DrillClient drillClient = new DrillClient(drillConfig, remoteServiceSet.getCoordinator()); - drillClient.connect(); + drillClient.connect(props); final List<QueryDataBatch> results = drillClient.runQuery( QueryType.SQL, String.format("alter session set `%s` = %d", diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java index 08655e382..2080fced0 100644 --- a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java @@ -109,7 +109,7 @@ public class TestLocalExchange extends PlanTestBase { @BeforeClass public static void setupClusterSize() { - setDrillbitCount(CLUSTER_SIZE); + updateTestCluster(CLUSTER_SIZE, null); } @BeforeClass diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/partitionsender/TestPartitionSender.java b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/partitionsender/TestPartitionSender.java index bdb020b98..46bcc6014 100644 --- a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/partitionsender/TestPartitionSender.java +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/partitionsender/TestPartitionSender.java @@ -148,7 +148,7 @@ public class TestPartitionSender extends PlanTestBase { final TopNBatch.SimpleRecordBatch incoming = new TopNBatch.SimpleRecordBatch(container, sv, null); - setDrillbitCount(DRILLBITS_COUNT); + updateTestCluster(DRILLBITS_COUNT, null); test("ALTER SESSION SET `planner.slice_target`=1"); String plan = getPlanInString("EXPLAIN PLAN FOR " + groupByQuery, JSON_FORMAT); diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestCustomUserAuthenticator.java b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestCustomUserAuthenticator.java new file mode 100644 index 000000000..06a2d1ad9 --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestCustomUserAuthenticator.java @@ -0,0 +1,102 @@ +/** + * 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.exec.rpc.user.security; + +import org.apache.drill.BaseTestQuery; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.rpc.RpcException; +import org.apache.drill.exec.rpc.user.UserSession; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Properties; + +import static org.apache.drill.exec.rpc.user.security.UserAuthenticatorTestImpl.TEST_USER_1; +import static org.apache.drill.exec.rpc.user.security.UserAuthenticatorTestImpl.TEST_USER_1_PASSWORD; +import static org.apache.drill.exec.rpc.user.security.UserAuthenticatorTestImpl.TEST_USER_2; +import static org.apache.drill.exec.rpc.user.security.UserAuthenticatorTestImpl.TEST_USER_2_PASSWORD; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class TestCustomUserAuthenticator extends BaseTestQuery { + + @BeforeClass + public static void setupCluster() { + // Create a new DrillConfig which has user authentication enabled and authenticator set to + // UserAuthenticatorTestImpl. + final Properties props = new Properties(); + props.setProperty(ExecConstants.USER_AUTHENTICATION_ENABLED, "true"); + props.setProperty(ExecConstants.USER_AUTHENTICATOR_IMPL, UserAuthenticatorTestImpl.TYPE); + final DrillConfig newConfig = DrillConfig.create(props); + + updateTestCluster(3, newConfig); + } + + @Test + public void positiveUserAuth() throws Exception { + runTest(TEST_USER_1, TEST_USER_1_PASSWORD); + runTest(TEST_USER_2, TEST_USER_2_PASSWORD); + } + + + @Test + public void negativeUserAuth() throws Exception { + negativeAuthHelper(TEST_USER_1, "blah.. blah.."); + negativeAuthHelper(TEST_USER_2, "blah.. blah.."); + negativeAuthHelper(TEST_USER_2, ""); + negativeAuthHelper("", "blah.. blah.."); + } + + @Test + public void positiveUserAuthAfterNegativeUserAuth() throws Exception { + negativeAuthHelper("blah.. blah..", "blah.. blah.."); + runTest(TEST_USER_2, TEST_USER_2_PASSWORD); + } + + private static void negativeAuthHelper(final String user, final String password) throws Exception { + RpcException negativeAuthEx = null; + try { + runTest(user, password); + } catch (RpcException e) { + negativeAuthEx = e; + } + + assertNotNull("Expected RpcException.", negativeAuthEx); + final String exMsg = negativeAuthEx.getMessage(); + assertThat(exMsg, containsString("HANDSHAKE_VALIDATION : Status: AUTH_FAILED")); + assertThat(exMsg, containsString("Invalid user credentials")); + } + + private static void runTest(final String user, final String password) throws Exception { + final Properties connectionProps = new Properties(); + + connectionProps.setProperty(UserSession.USER, user); + connectionProps.setProperty(UserSession.PASSWORD, password); + + updateClient(connectionProps); + + // Run few queries using the new client + test("SHOW SCHEMAS"); + test("USE INFORMATION_SCHEMA"); + test("SHOW TABLES"); + test("SELECT * FROM INFORMATION_SCHEMA.`TABLES` WHERE TABLE_NAME LIKE 'COLUMNS'"); + test("SELECT * FROM cp.`region.json` LIMIT 5"); + } +} diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTestImpl.java b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTestImpl.java new file mode 100644 index 000000000..c89471fe9 --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTestImpl.java @@ -0,0 +1,62 @@ +/** + * 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.exec.rpc.user.security; + +import com.google.common.base.Strings; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.exec.exception.DrillbitStartupException; + +import java.io.IOException; + +/* + * Implement {@link org.apache.drill.exec.rpc.user.security.UserAuthenticator} for testing UserAuthenticator and + * authentication of users from Java client to Drillbit. + */ +@UserAuthenticatorTemplate(type = UserAuthenticatorTestImpl.TYPE) +public class UserAuthenticatorTestImpl implements UserAuthenticator { + public static final String TYPE = "drillTestAuthenticator"; + + public static final String TEST_USER_1 = "testUser1"; + public static final String TEST_USER_2 = "testUser2"; + public static final String TEST_USER_1_PASSWORD = "testUser1Password"; + public static final String TEST_USER_2_PASSWORD = "testUser2Password"; + + @Override + public void setup(DrillConfig drillConfig) throws DrillbitStartupException { + // Nothing to setup. + } + + @Override + public void authenticate(String user, String password) throws UserAuthenticationException { + + if ("anonymous".equals(user)) { + // Allow user "anonymous" for test framework to work. + return; + } + + if (!(TEST_USER_1.equals(user) && TEST_USER_1_PASSWORD.equals(password)) && + !(TEST_USER_2.equals(user) && TEST_USER_2_PASSWORD.equals(password))) { + throw new UserAuthenticationException(); + } + } + + @Override + public void close() throws IOException { + // Nothing to cleanup. + } +} diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestDrillbitResilience.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestDrillbitResilience.java index 64033a58e..e03098aed 100644 --- a/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestDrillbitResilience.java +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestDrillbitResilience.java @@ -143,7 +143,7 @@ public class TestDrillbitResilience extends ExecTest { // create a client final DrillConfig drillConfig = zkHelper.getConfig(); - drillClient = QueryTestUtil.createClient(drillConfig, remoteServiceSet, 1); + drillClient = QueryTestUtil.createClient(drillConfig, remoteServiceSet, 1, null); } @AfterClass @@ -969,6 +969,30 @@ </exclusions> </dependency> + <dependency> + <groupId>net.sf.jpam</groupId> + <artifactId>jpam</artifactId> + <version>1.1</version> + <exclusions> + <exclusion> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + </exclusion> + <exclusion> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </exclusion> + <exclusion> + <groupId>checkstyle</groupId> + <artifactId>checkstyle</artifactId> + </exclusion> + <exclusion> + <groupId>checkstyle</groupId> + <artifactId>checkstyle-optional</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- Test Dependencies --> <dependency> <groupId>org.apache.hadoop</groupId> |