You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by zj...@apache.org on 2019/09/10 01:35:14 UTC
[zeppelin] branch master updated: [ZEPPELIN-4324]: Support two-way
SSL authentication.
This is an automated email from the ASF dual-hosted git repository.
zjffdu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new 79f751a [ZEPPELIN-4324]: Support two-way SSL authentication.
79f751a is described below
commit 79f751a0e03c9fe731a2c44238976cdab479b2d5
Author: fdeantoni <fd...@gmail.com>
AuthorDate: Mon Sep 9 12:56:54 2019 +0800
[ZEPPELIN-4324]: Support two-way SSL authentication.
### What is this PR for?
Livy can run behind a reverse proxy that requires SSL authentication.
To support this, three additional properties have been added:
- zeppelin.livy.ssl.keyStore
- zeppelin.livy.ssl.keyStorePassword
- zeppelin.livy.ssl.keyStoreType
The keystore type can either be JKS or PKCS12. The default is JKS. To
keep things streamlined, a property `zeppelin.livy.ssl.trustStoreType`
has been been added as well. Default value is also JKS.
### What type of PR is it?
Improvement
### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-4324
### How should this be tested?
Set up a livy instance behind a reverse proxy (e.g. HAProxy) that requires two way SSL authentication to access it. Configure the Livy interpreter to access this instance by setting the following properties:
- zeppelin.livy.ssl.keyStore: Path to keystore containing client certificate and key
- zeppelin.livy.ssl.keyStorePassword: Password of keystore
- zeppelin.livy.ssl.keyStoreType: Either JKS or PKCS12
- zeppelin.livy.ssl.trustStore: Path to trust store containing proxy host certificate
- zeppelin.livy.ssl.trustStorePassword: Password of trust store
- zeppelin.livy.ssl.keyStoreType: Either JKS or PKCS12
Author: fdeantoni <fd...@gmail.com>
Closes #3441 from fdeantoni/two-way-ssl and squashes the following commits:
a0f18cc7c [fdeantoni] ZEPPELIN-4324: Support two-way SSL authentication.
---
docs/interpreter/livy.md | 25 ++++
.../apache/zeppelin/livy/BaseLivyInterpreter.java | 127 +++++++++++++--------
2 files changed, 104 insertions(+), 48 deletions(-)
diff --git a/docs/interpreter/livy.md b/docs/interpreter/livy.md
index 954eb8c..c7a96ba 100644
--- a/docs/interpreter/livy.md
+++ b/docs/interpreter/livy.md
@@ -146,6 +146,31 @@ Example: `spark.driver.memory` to `livy.spark.driver.memory`
<td>password for trustStore file. Used when livy ssl is enabled</td>
</tr>
<tr>
+ <td>zeppelin.livy.ssl.trustStoreType</td>
+ <td>JKS</td>
+ <td>type of truststore. Either JKS or PKCS12.</td>
+ </tr>
+ <tr>
+ <td>zeppelin.livy.ssl.keyStore</td>
+ <td></td>
+ <td>client keyStore file. Needed if Livy requires two way SSL authentication.</td>
+ </tr>
+ <tr>
+ <td>zeppelin.livy.ssl.keyStorePassword</td>
+ <td></td>
+ <td>password for keyStore file.</td>
+ </tr>
+ <tr>
+ <td>zeppelin.livy.ssl.keyStoreType</td>
+ <td>JKS</td>
+ <td>type of keystore. Either JKS or PKCS12.</td>
+ </tr>
+ <tr>
+ <td>zeppelin.livy.ssl.keyPassword</td>
+ <td></td>
+ <td>password for key in the keyStore file. Defaults to zeppelin.livy.ssl.keyStorePassword.</td>
+ </tr>
+ <tr>
<td>zeppelin.livy.http.headers</td>
<td>key_1: value_1; key_2: value_2</td>
<td>custom http headers when calling livy rest api. Each http header is separated by `;`, and each header is one key value pair where key value is separated by `:`</td>
diff --git a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterpreter.java b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterpreter.java
index d47a322..afaf55c 100644
--- a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterpreter.java
+++ b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterpreter.java
@@ -34,6 +34,7 @@ import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
+import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
@@ -55,7 +56,7 @@ import org.springframework.web.client.RestTemplate;
import java.io.FileInputStream;
import java.io.IOException;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Principal;
import java.util.ArrayList;
@@ -571,6 +572,59 @@ public abstract class BaseLivyInterpreter extends Interpreter {
callRestAPI("/sessions/" + sessionInfo.id + "/statements/" + statementId + "/cancel", "POST");
}
+ private SSLContext getSslContext() {
+ try {
+ // Build truststore
+ String trustStoreFile = getProperty("zeppelin.livy.ssl.trustStore");
+ String trustStorePassword = getProperty("zeppelin.livy.ssl.trustStorePassword");
+ String trustStoreType = getProperty("zeppelin.livy.ssl.trustStoreType",
+ KeyStore.getDefaultType());
+ if (StringUtils.isBlank(trustStoreFile)) {
+ throw new RuntimeException("No zeppelin.livy.ssl.trustStore specified for livy ssl");
+ }
+ if (StringUtils.isBlank(trustStorePassword)) {
+ throw new RuntimeException("No zeppelin.livy.ssl.trustStorePassword specified " +
+ "for livy ssl");
+ }
+ KeyStore trustStore = getStore(trustStoreFile, trustStoreType, trustStorePassword);
+ SSLContextBuilder builder = SSLContexts.custom();
+ builder.loadTrustMaterial(trustStore);
+
+ // Build keystore
+ String keyStoreFile = getProperty("zeppelin.livy.ssl.keyStore");
+ String keyStorePassword = getProperty("zeppelin.livy.ssl.keyStorePassword");
+ String keyPassword = getProperty("zeppelin.livy.ssl.keyPassword", keyStorePassword);
+ String keyStoreType = getProperty("zeppelin.livy.ssl.keyStoreType",
+ KeyStore.getDefaultType());
+ if (StringUtils.isNotBlank(keyStoreFile)) {
+ KeyStore keyStore = getStore(keyStoreFile, keyStoreType, keyStorePassword);
+ builder.loadKeyMaterial(keyStore, keyPassword.toCharArray()).useTLS();
+ }
+ return builder.build();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create SSL Context", e);
+ }
+ }
+
+ private KeyStore getStore(String file, String type, String password) {
+ FileInputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(file);
+ KeyStore trustStore = KeyStore.getInstance(type);
+ trustStore.load(new FileInputStream(file), password.toCharArray());
+ return trustStore;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to open keystore " + file, e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ LOGGER.error("Failed to close keystore file", e);
+ }
+ }
+ }
+ }
private RestTemplate createRestTemplate() {
String keytabLocation = getProperty("zeppelin.livy.keytab");
@@ -580,47 +634,32 @@ public abstract class BaseLivyInterpreter extends Interpreter {
HttpClient httpClient = null;
if (livyURL.startsWith("https:")) {
- String keystoreFile = getProperty("zeppelin.livy.ssl.trustStore");
- String password = getProperty("zeppelin.livy.ssl.trustStorePassword");
- if (StringUtils.isBlank(keystoreFile)) {
- throw new RuntimeException("No zeppelin.livy.ssl.trustStore specified for livy ssl");
- }
- if (StringUtils.isBlank(password)) {
- throw new RuntimeException("No zeppelin.livy.ssl.trustStorePassword specified " +
- "for livy ssl");
- }
- FileInputStream inputStream = null;
try {
- inputStream = new FileInputStream(keystoreFile);
- KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
- trustStore.load(new FileInputStream(keystoreFile), password.toCharArray());
- SSLContext sslContext = SSLContexts.custom()
- .loadTrustMaterial(trustStore)
- .build();
+ SSLContext sslContext = getSslContext();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(csf);
- RequestConfig reqConfig = new RequestConfig() {
- @Override
- public boolean isAuthenticationEnabled() {
- return true;
- }
- };
- httpClientBuilder.setDefaultRequestConfig(reqConfig);
- Credentials credentials = new Credentials() {
- @Override
- public String getPassword() {
- return null;
- }
-
- @Override
- public Principal getUserPrincipal() {
- return null;
- }
- };
- CredentialsProvider credsProvider = new BasicCredentialsProvider();
- credsProvider.setCredentials(AuthScope.ANY, credentials);
- httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
if (isSpnegoEnabled) {
+ RequestConfig reqConfig = new RequestConfig() {
+ @Override
+ public boolean isAuthenticationEnabled() {
+ return true;
+ }
+ };
+ httpClientBuilder.setDefaultRequestConfig(reqConfig);
+ Credentials credentials = new Credentials() {
+ @Override
+ public String getPassword() {
+ return null;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+ };
+ CredentialsProvider credsProvider = new BasicCredentialsProvider();
+ credsProvider.setCredentials(AuthScope.ANY, credentials);
+ httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
Registry<AuthSchemeProvider> authSchemeProviderRegistry =
RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
@@ -631,18 +670,10 @@ public abstract class BaseLivyInterpreter extends Interpreter {
httpClient = httpClientBuilder.build();
} catch (Exception e) {
throw new RuntimeException("Failed to create SSL HttpClient", e);
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- LOGGER.error("Failed to close keystore file", e);
- }
- }
}
}
- RestTemplate restTemplate = null;
+ RestTemplate restTemplate;
if (isSpnegoEnabled) {
if (httpClient == null) {
restTemplate = new KerberosRestTemplate(keytabLocation, principal);
@@ -657,7 +688,7 @@ public abstract class BaseLivyInterpreter extends Interpreter {
}
}
restTemplate.getMessageConverters().add(0,
- new StringHttpMessageConverter(Charset.forName("UTF-8")));
+ new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}