aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--distribution/src/resources/drill-override-example.conf8
-rw-r--r--exec/java-exec/pom.xml4
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java4
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserServer.java42
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserSession.java2
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/PamUserAuthenticator.java71
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticationException.java32
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticator.java53
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorFactory.java106
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTemplate.java37
-rw-r--r--exec/java-exec/src/main/java/org/apache/drill/exec/service/ServiceEngine.java8
-rw-r--r--exec/java-exec/src/main/resources/drill-module.conf8
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java59
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/QueryTestUtil.java9
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java2
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/partitionsender/TestPartitionSender.java2
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestCustomUserAuthenticator.java102
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/UserAuthenticatorTestImpl.java62
-rw-r--r--exec/java-exec/src/test/java/org/apache/drill/exec/server/TestDrillbitResilience.java2
-rw-r--r--pom.xml24
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
diff --git a/pom.xml b/pom.xml
index 8c9f09e7c..35a65f2e5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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>