Repository: drill
Updated Branches:
  refs/heads/master 779703421 -> f1d1945b3


DRILL-5881:Java Client: [Threat Modeling] Drillbit may be spoofed by an 
attacker and this may lead to data being written to the attacker's target 
instead of Drillbit
 Also set connection to null after its closed

This closes #999


Project: http://git-wip-us.apache.org/repos/asf/drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/40d09919
Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/40d09919
Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/40d09919

Branch: refs/heads/master
Commit: 40d0991998a4174cf5d4b12f695380e97aba4fd3
Parents: 7797034
Author: Sorabh Hamirwasia <shamirwa...@maprtech.com>
Authored: Mon Oct 16 14:58:31 2017 -0700
Committer: Parth Chandra <par...@apache.org>
Committed: Thu Oct 19 17:12:38 2017 -0700

----------------------------------------------------------------------
 .../apache/drill/exec/rpc/user/UserClient.java  |  69 +++-
 .../security/TestUserBitSaslCompatibility.java  | 317 +++++++++++++++++++
 .../org/apache/drill/exec/rpc/BasicClient.java  |   1 +
 3 files changed, 377 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/40d09919/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserClient.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserClient.java 
b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserClient.java
index 99614bd..fb78812 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserClient.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/rpc/user/UserClient.java
@@ -202,7 +202,8 @@ public class UserClient
             .checkedGet(sslConfig.getHandshakeTimeout(), 
TimeUnit.MILLISECONDS);
       } catch (TimeoutException e) {
         String msg = new StringBuilder().append(
-            "Connecting to the server timed out. This is sometimes due to a 
mismatch in the SSL configuration between client and server. [ Exception: ")
+            "Connecting to the server timed out. This is sometimes due to a 
mismatch in the SSL configuration between" +
+                " client and server. [ Exception: ")
             .append(e.getMessage()).append("]").toString();
         throw new NonTransientRpcException(msg);
       }
@@ -210,15 +211,8 @@ public class UserClient
       connect(hsBuilder.build(), endpoint).checkedGet();
     }
 
-    // Check if client needs encryption and server is not configured for 
encryption.
-    final boolean clientNeedsEncryption = 
properties.containsKey(DrillProperties.SASL_ENCRYPT) && Boolean
-        .parseBoolean(properties.getProperty(DrillProperties.SASL_ENCRYPT));
-
-    if (clientNeedsEncryption && !connection.isEncryptionEnabled()) {
-      throw new NonTransientRpcException(
-          "Client needs encrypted connection but server is not configured for "
-              + "encryption. Please check connection parameter or contact your 
administrator");
-    }
+    // Validate if both client and server are compatible in their security 
requirements for the connection
+    validateSaslCompatibility(properties);
 
     if (serverAuthMechanisms != null) {
       try {
@@ -229,6 +223,61 @@ public class UserClient
     }
   }
 
+  /**
+   * Validate that security requirements from client and Drillbit side is 
compatible. For example: It verifies if one
+   * side needs authentication / encryption then other side is also configured 
to support that security properties.
+   * @param properties - DrillClient connection parameters
+   * @throws NonTransientRpcException - When DrillClient security requirements 
doesn't match Drillbit side of security
+   *                                    configurations.
+   */
+  private void validateSaslCompatibility(DrillProperties properties) throws 
NonTransientRpcException {
+
+    final boolean clientNeedsEncryption = 
properties.containsKey(DrillProperties.SASL_ENCRYPT)
+        && 
Boolean.parseBoolean(properties.getProperty(DrillProperties.SASL_ENCRYPT));
+
+    // Check if client needs encryption and server is not configured for 
encryption.
+    if (clientNeedsEncryption && !connection.isEncryptionEnabled()) {
+      throw new NonTransientRpcException(
+          "Client needs encrypted connection but server is not configured for 
encryption." +
+              " Please contact your administrator. [Warn: It may be due to 
wrong config or a security attack in" +
+              " progress.]");
+    }
+
+    // Check if client needs encryption and server doesn't support any 
security mechanisms.
+    if (clientNeedsEncryption && serverAuthMechanisms == null) {
+      throw new NonTransientRpcException(
+          "Client needs encrypted connection but server doesn't support any 
security mechanisms." +
+              " Please contact your administrator. [Warn: It may be due to 
wrong config or a security attack in" +
+              " progress.]");
+    }
+
+    // Check if client needs authentication and server doesn't support any 
security mechanisms.
+    if (clientNeedsAuth(properties) && serverAuthMechanisms == null) {
+      throw new NonTransientRpcException(
+          "Client needs authentication but server doesn't support any security 
mechanisms." +
+              " Please contact your administrator. [Warn: It may be due to 
wrong config or a security attack in" +
+              " progress.]");
+    }
+  }
+
+  /**
+   * Determine if client needs authenticated connection or not. It checks for 
following as an indication of
+   * requiring authentication from client side:
+   * 1) Any auth mechanism is provided in connection properties with 
DrillProperties.AUTH_MECHANISM parameter.
+   * 2) A service principal is provided in connection properties in which case 
we treat AUTH_MECHANISM as Kerberos
+   * 3) Username and Password is provided in connection properties in which 
case we treat AUTH_MECHANISM as Plain
+   * @param props - DrillClient connection parameters
+   * @return - true  - If any of above 3 checks is successful.
+   *         - false - If all the above 3 checks failed.
+   */
+  private boolean clientNeedsAuth(DrillProperties props) {
+
+    return 
!Strings.isNullOrEmpty(props.getProperty(DrillProperties.AUTH_MECHANISM)) ||
+        
!Strings.isNullOrEmpty(props.getProperty(DrillProperties.SERVICE_PRINCIPAL)) ||
+        (props.containsKey(DrillProperties.USER) && 
!Strings.isNullOrEmpty(props.getProperty(DrillProperties.PASSWORD)));
+
+  }
+
   private CheckedFuture<Void, RpcException> connect(final UserToBitHandshake 
handshake,
       final DrillbitEndpoint endpoint) {
     final SettableFuture<Void> connectionSettable = SettableFuture.create();

http://git-wip-us.apache.org/repos/asf/drill/blob/40d09919/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestUserBitSaslCompatibility.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestUserBitSaslCompatibility.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestUserBitSaslCompatibility.java
new file mode 100644
index 0000000..bbb957d
--- /dev/null
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestUserBitSaslCompatibility.java
@@ -0,0 +1,317 @@
+/*
+ * 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.collect.Lists;
+import com.typesafe.config.ConfigValueFactory;
+import org.apache.drill.BaseTestQuery;
+import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.common.config.DrillProperties;
+import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.exec.rpc.NonTransientRpcException;
+import 
org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.security.sasl.SaslException;
+import java.util.Properties;
+
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+
+/**
+ * Helps to test different scenarios based on security configuration on client 
and Drillbit side with respect to SASL
+ * and specifically using PLAIN mechanism
+ */
+public class TestUserBitSaslCompatibility extends BaseTestQuery {
+  //private static final org.slf4j.Logger logger = 
org.slf4j.LoggerFactory.getLogger(TestUserClient.class);
+
+  @BeforeClass
+  public static void setup() {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL,
+            ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE))
+        .withValue(ExecConstants.AUTHENTICATION_MECHANISMS,
+            ConfigValueFactory.fromIterable(Lists.newArrayList("plain")))
+        .withValue(ExecConstants.USER_ENCRYPTION_SASL_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+    connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+
+    updateTestCluster(1, newConfig, connectionProps);
+  }
+
+  /**
+   * Test showing failure before SASL handshake when Drillbit is not 
configured for authentication whereas client
+   * explicitly requested for authentication.
+   * @throws Exception
+   */
+  @Test
+  public void testDisableDrillbitAuth_EnableClientAuth() throws Exception {
+
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+    connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+      fail();
+    } catch (Exception ex) {
+      assertTrue(ex.getCause() instanceof NonTransientRpcException);
+      assertTrue(!(ex.getCause().getCause() instanceof SaslException));
+    }
+  }
+
+  /**
+   * Test showing failure before SASL handshake when Drillbit is not 
configured for authentication whereas client
+   * explicitly requested for encrypted connection.
+   * @throws Exception
+   */
+  @Test
+  public void testDisableDrillbitAuth_EnableClientEncryption() throws 
Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+    connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+    connectionProps.setProperty(DrillProperties.SASL_ENCRYPT, "true");
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+      fail();
+    } catch (Exception ex) {
+      assertTrue(ex.getCause() instanceof NonTransientRpcException);
+      assertTrue(!(ex.getCause().getCause() instanceof SaslException));
+    }
+  }
+
+  /**
+   * Test showing failure before SASL handshake when Drillbit is not 
configured for encryption whereas client explicitly
+   * requested for encrypted connection.
+   * @throws Exception
+   */
+  @Test
+  public void testDisableDrillbitEncryption_EnableClientEncryption() throws 
Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL,
+            ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE))
+        .withValue(ExecConstants.AUTHENTICATION_MECHANISMS,
+            ConfigValueFactory.fromIterable(Lists.newArrayList("plain")))
+        .withValue(ExecConstants.USER_ENCRYPTION_SASL_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+    connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+    connectionProps.setProperty(DrillProperties.SASL_ENCRYPT, "true");
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+      fail();
+    } catch (Exception ex) {
+      assertTrue(ex.getCause() instanceof NonTransientRpcException);
+      assertTrue(!(ex.getCause().getCause() instanceof SaslException));
+    }
+  }
+
+  /**
+   * Test showing failure in SASL handshake when Drillbit is configured for 
authentication only whereas client doesn't
+   * provide any security properties like username/password in this case.
+   * @throws Exception
+   */
+  @Test
+  public void testEnableDrillbitAuth_DisableClientAuth() throws Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL,
+            ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE))
+        .withValue(ExecConstants.AUTHENTICATION_MECHANISMS,
+            ConfigValueFactory.fromIterable(Lists.newArrayList("plain")))
+        .withValue(ExecConstants.USER_ENCRYPTION_SASL_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+      fail();
+    } catch (Exception ex) {
+      assertTrue(ex.getCause() instanceof NonTransientRpcException);
+      assertTrue(ex.getCause().getCause() instanceof SaslException);
+    }
+  }
+
+  /**
+   * Test showing failure in SASL handshake when Drillbit is configured for 
encryption whereas client doesn't provide any
+   * security properties like username/password in this case.
+   * @throws Exception
+   */
+  @Test
+  public void testEnableDrillbitEncryption_DisableClientAuth() throws 
Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL,
+            ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE))
+        .withValue(ExecConstants.AUTHENTICATION_MECHANISMS,
+            ConfigValueFactory.fromIterable(Lists.newArrayList("plain")))
+        .withValue(ExecConstants.USER_ENCRYPTION_SASL_ENABLED,
+            ConfigValueFactory.fromAnyRef(true)),
+        false);
+
+    final Properties connectionProps = new Properties();
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+      fail();
+    } catch (Exception ex) {
+      assertTrue(ex.getCause() instanceof NonTransientRpcException);
+      assertTrue(ex.getCause().getCause() instanceof SaslException);
+    }
+  }
+
+  /**
+   * Test showing successful SASL handshake when both Drillbit and client side 
authentication is enabled using PLAIN
+   * mechanism.
+   * @throws Exception
+   */
+  @Test
+  public void testEnableDrillbitClientAuth() throws Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL,
+            ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE))
+        .withValue(ExecConstants.AUTHENTICATION_MECHANISMS,
+            ConfigValueFactory.fromIterable(Lists.newArrayList("plain")))
+        .withValue(ExecConstants.USER_ENCRYPTION_SASL_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+    connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+    } catch (Exception ex) {
+      fail();
+    }
+  }
+
+  /**
+   * Below test shows the failure in Sasl layer with client and Drillbit side 
encryption enabled using PLAIN
+   * mechanism. This is expected since PLAIN mechanism doesn't support 
encryption using SASL. Whereas same test
+   * setup using Kerberos or any other mechanism with encryption support will 
result in successful SASL handshake.
+   * @throws Exception
+   */
+  @Test
+  public void testEnableDrillbitClientEncryption_UsingPlain() throws Exception 
{
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL,
+            ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE))
+        .withValue(ExecConstants.AUTHENTICATION_MECHANISMS,
+            ConfigValueFactory.fromIterable(Lists.newArrayList("plain")))
+        .withValue(ExecConstants.USER_ENCRYPTION_SASL_ENABLED,
+            ConfigValueFactory.fromAnyRef(true)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+    connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+    connectionProps.setProperty(DrillProperties.SASL_ENCRYPT, "true");
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+      fail();
+    } catch (Exception ex) {
+      assertTrue(ex.getCause() instanceof NonTransientRpcException);
+      assertTrue(ex.getCause().getCause() instanceof SaslException);
+    }
+  }
+
+  /**
+   * Test showing successful handshake when authentication is disabled on 
Drillbit side and client also
+   * doesn't provide any security properties in connection URL.
+   * @throws Exception
+   */
+  @Test
+  public void testDisableDrillbitClientAuth() throws Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(false)),
+        false);
+
+    final Properties connectionProps = new Properties();
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+    } catch (Exception ex) {
+      fail();
+    }
+  }
+
+  /**
+   * Test showing successful handshake when authentication is disabled but 
impersonation is enabled on Drillbit side
+   * and client only provides USERNAME as a security property in connection 
URL.
+   * @throws Exception
+   */
+  @Test
+  public void testEnableDrillbitImpersonation_DisableClientAuth() throws 
Exception {
+    final DrillConfig newConfig = new 
DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+        .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(false))
+        .withValue(ExecConstants.IMPERSONATION_ENABLED,
+            ConfigValueFactory.fromAnyRef(true))
+        .withValue(ExecConstants.IMPERSONATION_MAX_CHAINED_USER_HOPS,
+            ConfigValueFactory.fromAnyRef(3)),
+        false);
+
+    final Properties connectionProps = new Properties();
+    connectionProps.setProperty(DrillProperties.USER, "anonymous");
+
+    try {
+      updateTestCluster(1, newConfig, connectionProps);
+    } catch (Exception ex) {
+      fail();
+    }
+  }
+
+}
+

http://git-wip-us.apache.org/repos/asf/drill/blob/40d09919/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicClient.java
----------------------------------------------------------------------
diff --git a/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicClient.java 
b/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicClient.java
index 0d80df6..0f4ef1b 100644
--- a/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicClient.java
+++ b/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicClient.java
@@ -254,6 +254,7 @@ public abstract class BasicClient<T extends EnumLite, CC 
extends ClientConnectio
 
     if (connection != null) {
       connection.close();
+      connection = null;
     }
   }
 }

Reply via email to