You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by pr...@apache.org on 2017/01/17 02:07:34 UTC

zeppelin git commit: ZEPPELIN-1935 Add jceks stored password support for jdbc interpreter

Repository: zeppelin
Updated Branches:
  refs/heads/branch-0.7 98f55c099 -> a2d6a8cf0


ZEPPELIN-1935 Add jceks stored password support for jdbc interpreter

### What is this PR for?
Add support for jceks stored password instead of using password in clear text. This security enhancement prevents any user from reading clear passwords from interpreter json using shell/py/spark etc. #1315 is the parent PR which fixed similar a issue in Active Directory (shiro.ini config).

### What type of PR is it?
Improvement

### Todos

### What is the Jira issue?
ZEPPELIN-1935

### How should this be tested?
Create a keystore file using the hadoop credential commandline, for this the hadoop commons should be in the classpath.

`hadoop credential create jdbc.password -provider jceks://file/user/zeppelin/conf/zeppelin.jceks`

Use the jceks file and the key to configure jdbc interpreter. Example interpreter setting:

#### Following are the supported settings for backward compatibility

- Settings with username and jceks
```
default.driver	org.postgresql.Driver
default.jceks.credentialKey	jdbc.password
default.jceks.file	jceks://file/tmp/zeppelin.jceks
default.url	jdbc:postgresql://rkamath-local-1:5432/
default.user	rk-user
```

- Settings with user name and clear text password
```
default.driver	org.postgresql.Driver
default.url	jdbc:postgresql://rkamath-local-1:5432/
default.user	rk-user
default.password	password1
```

### Screenshots (if appropriate)
<img width="1392" alt="screen shot 2017-01-10 at 7 02 12 pm" src="https://cloud.githubusercontent.com/assets/2031306/21808016/5e602982-d767-11e6-88f9-3d15b9a7f0b8.png">

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? yes

Author: Renjith Kamath <re...@gmail.com>

Closes #1881 from r-kamath/ZEPPELIN-1935 and squashes the following commits:

5cc4db1 [Renjith Kamath] ZEPPELIN-1935 revert wildcard imports
dfabe3a [Renjith Kamath] Merge branch 'master' of https://github.com/apache/zeppelin into ZEPPELIN-1935
e62088a [Renjith Kamath] ZEPPELIN-1935 fix log message
d41d56c [Renjith Kamath] ZEPPELIN-1935 Add jceks stored password support for jdbc interpreter

(cherry picked from commit 04d34547a1453e16ea4cb306cfcfa0d79f22acfe)
Signed-off-by: Prabhjyot Singh <pr...@gmail.com>


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

Branch: refs/heads/branch-0.7
Commit: a2d6a8cf05104443b7a30c84fd4d71838136caaf
Parents: 98f55c0
Author: Renjith Kamath <re...@gmail.com>
Authored: Wed Jan 11 22:43:32 2017 +0530
Committer: Prabhjyot Singh <pr...@gmail.com>
Committed: Tue Jan 17 07:37:17 2017 +0530

----------------------------------------------------------------------
 docs/interpreter/jdbc.md                        |  8 +++
 jdbc/pom.xml                                    | 54 +++++++++++++++++
 .../apache/zeppelin/jdbc/JDBCInterpreter.java   | 64 +++++++++++++++-----
 3 files changed, 112 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/a2d6a8cf/docs/interpreter/jdbc.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/jdbc.md b/docs/interpreter/jdbc.md
index 74ac112..32adcba 100644
--- a/docs/interpreter/jdbc.md
+++ b/docs/interpreter/jdbc.md
@@ -159,6 +159,14 @@ There are more JDBC interpreter properties you can specify like below.
     <td>zeppelin.jdbc.keytab.location</td>
     <td>The path to the keytab file</td>
   </tr>
+  <tr>
+    <td>default.jceks.file</td>
+    <td>jceks store path (e.g: jceks://file/tmp/zeppelin.jceks)</td>
+  </tr>
+  <tr>
+    <td>default.jceks.credentialKey</td>
+    <td>jceks credential key</td>
+  </tr>
 </table>
 
 You can also add more properties by using this [method](http://docs.oracle.com/javase/7/docs/api/java/sql/DriverManager.html#getConnection%28java.lang.String,%20java.util.Properties%29).

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/a2d6a8cf/jdbc/pom.xml
----------------------------------------------------------------------
diff --git a/jdbc/pom.xml b/jdbc/pom.xml
index 1786b71..9a6e97a 100644
--- a/jdbc/pom.xml
+++ b/jdbc/pom.xml
@@ -39,6 +39,7 @@
     <hadoop.common.version>2.7.2</hadoop.common.version>
     <h2.version>1.4.190</h2.version>
     <commons.dbcp2.version>2.0.1</commons.dbcp2.version>
+    <hadoop-common.version>2.6.0</hadoop-common.version>
 
     <!--test library versions-->
     <mockrunner.jdbc.version>1.0.8</mockrunner.jdbc.version>
@@ -117,6 +118,59 @@
       <version>${mockrunner.jdbc.version}</version>
       <scope>test</scope>
     </dependency>
+
+    <dependency>
+      <groupId>org.apache.hadoop</groupId>
+      <artifactId>hadoop-common</artifactId>
+      <version>${hadoop-common.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>com.sun.jersey</groupId>
+          <artifactId>jersey-core</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.sun.jersey</groupId>
+          <artifactId>jersey-json</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.sun.jersey</groupId>
+          <artifactId>jersey-server</artifactId>
+        </exclusion>
+
+        <exclusion>
+          <groupId>javax.servlet</groupId>
+          <artifactId>servlet-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.avro</groupId>
+          <artifactId>avro</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.jackrabbit</groupId>
+          <artifactId>jackrabbit-webdav</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>io.netty</groupId>
+          <artifactId>netty</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>commons-httpclient</groupId>
+          <artifactId>commons-httpclient</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.zookeeper</groupId>
+          <artifactId>zookeeper</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.eclipse.jgit</groupId>
+          <artifactId>org.eclipse.jgit</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.jcraft</groupId>
+          <artifactId>jsch</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/a2d6a8cf/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
index 778dcf2..8d6577e 100644
--- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
+++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
@@ -15,12 +15,12 @@
 package org.apache.zeppelin.jdbc;
 
 import static org.apache.commons.lang.StringUtils.containsIgnoreCase;
-
+import static org.apache.commons.lang.StringUtils.isEmpty;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-
 import java.security.PrivilegedExceptionAction;
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -40,10 +40,12 @@ import org.apache.commons.dbcp2.ConnectionFactory;
 import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
 import org.apache.commons.dbcp2.PoolableConnectionFactory;
 import org.apache.commons.dbcp2.PoolingDriver;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.pool2.ObjectPool;
 import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.alias.CredentialProvider;
+import org.apache.hadoop.security.alias.CredentialProviderFactory;
 import org.apache.zeppelin.interpreter.*;
 import org.apache.zeppelin.interpreter.InterpreterResult.Code;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
@@ -87,8 +89,6 @@ public class JDBCInterpreter extends Interpreter {
   private Logger logger = LoggerFactory.getLogger(JDBCInterpreter.class);
 
   static final String INTERPRETER_NAME = "jdbc";
-  static final String JDBC_DEFAULT_USER_KEY = "default.user";
-  static final String JDBC_DEFAULT_PASSWORD_KEY = "default.password";
   static final String COMMON_KEY = "common";
   static final String MAX_LINE_KEY = "max_count";
   static final int MAX_LINE_DEFAULT = 1000;
@@ -98,6 +98,8 @@ public class JDBCInterpreter extends Interpreter {
   static final String URL_KEY = "url";
   static final String USER_KEY = "user";
   static final String PASSWORD_KEY = "password";
+  static final String JDBC_JCEKS_FILE = "jceks.file";
+  static final String JDBC_JCEKS_CREDENTIAL_KEY = "jceks.credentialKey";
   static final String DOT = ".";
 
   private static final char WHITESPACE = ' ';
@@ -105,7 +107,6 @@ public class JDBCInterpreter extends Interpreter {
   private static final char TAB = '\t';
   private static final String TABLE_MAGIC_TAG = "%table ";
   private static final String EXPLAIN_PREDICATE = "EXPLAIN ";
-  private static final String UPDATE_COUNT_HEADER = "Update Count";
 
   static final String COMMON_MAX_LINE = COMMON_KEY + DOT + MAX_LINE_KEY;
 
@@ -182,7 +183,7 @@ public class JDBCInterpreter extends Interpreter {
     }
     logger.debug("JDBC PropretiesMap: {}", basePropretiesMap);
 
-    if (!StringUtils.isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) {
+    if (!isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) {
       JDBCSecurityImpl.createSecureConfiguration(property);
     }
     for (String propertyKey : basePropretiesMap.keySet()) {
@@ -261,9 +262,9 @@ public class JDBCInterpreter extends Interpreter {
     return driverName.toString();
   }
 
-  private boolean existAccountInBaseProperty() {
-    return property.containsKey(JDBC_DEFAULT_USER_KEY) &&
-      property.containsKey(JDBC_DEFAULT_PASSWORD_KEY);
+  private boolean existAccountInBaseProperty(String propertyKey) {
+    return basePropretiesMap.get(propertyKey).containsKey(USER_KEY) &&
+        basePropretiesMap.get(propertyKey).containsKey(PASSWORD_KEY);
   }
 
   private UsernamePassword getUsernamePassword(InterpreterContext interpreterContext,
@@ -295,15 +296,22 @@ public class JDBCInterpreter extends Interpreter {
   }
 
   private void setUserProperty(String propertyKey, InterpreterContext interpreterContext)
-      throws SQLException {
+      throws SQLException, IOException {
 
     String user = interpreterContext.getAuthenticationInfo().getUser();
 
     JDBCUserConfigurations jdbcUserConfigurations =
       getJDBCConfiguration(user);
+    if (basePropretiesMap.get(propertyKey).containsKey(USER_KEY) &&
+        !basePropretiesMap.get(propertyKey).getProperty(USER_KEY).isEmpty()) {
+      String password = getPassword(basePropretiesMap.get(propertyKey));
+      if (!isEmpty(password)) {
+        basePropretiesMap.get(propertyKey).setProperty(PASSWORD_KEY, password);
+      }
+    }
     jdbcUserConfigurations.setPropertyMap(propertyKey, basePropretiesMap.get(propertyKey));
 
-    if (existAccountInBaseProperty()) {
+    if (existAccountInBaseProperty(propertyKey)) {
       return;
     }
     jdbcUserConfigurations.cleanUserProperty(propertyKey);
@@ -344,7 +352,7 @@ public class JDBCInterpreter extends Interpreter {
   }
 
   public Connection getConnection(String propertyKey, InterpreterContext interpreterContext)
-      throws ClassNotFoundException, SQLException, InterpreterException {
+      throws ClassNotFoundException, SQLException, InterpreterException, IOException {
     final String user =  interpreterContext.getAuthenticationInfo().getUser();
     Connection connection;
     if (propertyKey == null || basePropretiesMap.get(propertyKey) == null) {
@@ -357,7 +365,7 @@ public class JDBCInterpreter extends Interpreter {
     final Properties properties = jdbcUserConfigurations.getPropertyMap(propertyKey);
     final String url = properties.getProperty(URL_KEY);
 
-    if (StringUtils.isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) {
+    if (isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) {
       connection = getConnectionFromPool(url, user, propertyKey, properties);
     } else {
       UserGroupInformation.AuthenticationMethod authType = JDBCSecurityImpl.getAuthtype(property);
@@ -410,6 +418,34 @@ public class JDBCInterpreter extends Interpreter {
     return connection;
   }
 
+  private String getPassword(Properties properties) throws IOException {
+    if (isNotEmpty(properties.getProperty(PASSWORD_KEY))) {
+      return properties.getProperty(PASSWORD_KEY);
+    } else if (isNotEmpty(properties.getProperty(JDBC_JCEKS_FILE))
+        && isNotEmpty(properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY))) {
+      try {
+        Configuration configuration = new Configuration();
+        configuration.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,
+            properties.getProperty(JDBC_JCEKS_FILE));
+        CredentialProvider provider = CredentialProviderFactory.getProviders(configuration).get(0);
+        CredentialProvider.CredentialEntry credEntry =
+            provider.getCredentialEntry(properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY));
+        if (credEntry != null) {
+          return new String(credEntry.getCredential());
+        } else {
+          throw new InterpreterException("Failed to retrieve password from JCEKS from key: "
+              + properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY));
+        }
+      } catch (Exception e) {
+        logger.error("Failed to retrieve password from JCEKS \n" +
+            "For file: " + properties.getProperty(JDBC_JCEKS_FILE) +
+            "\nFor key: " + properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY), e);
+        throw e;
+      }
+    }
+    return null;
+  }
+
   private String getResults(ResultSet resultSet, boolean isTableType)
       throws SQLException {
     ResultSetMetaData md = resultSet.getMetaData();