You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2017/04/28 09:52:16 UTC
zeppelin git commit: SSL Support for Groovy Interpreter HTTP requests
[ZEPPELIN-2443]
Repository: zeppelin
Updated Branches:
refs/heads/master ec8461365 -> 925d09cef
SSL Support for Groovy Interpreter HTTP requests [ZEPPELIN-2443]
### What is this PR for?
Target: Create ability to call http services with custom keystores in a groovy way.
The following should work:
```groovy
//connect to host xxx.yyy with special keystore
HTTP.get(
url: "https://xxx.yyy/zzz",
ssl: " HTTP.getKeystoreSSLContext('./xxx_yyy_keystore.jks', 'testpass') "
)
//take context initialization code from groovy interpreter properties
HTTP.get(
url: "https://xxx.yyy/zzz",
ssl: g.SSL_CONTEXT_FROM_GROOVY_INTERPRET_PROPERTIES
)
//connect to host xxx.yyy with trust all (do not check trust certificates - dev mode only)
HTTP.get(
url: "https://xxx.yyy/zzz",
ssl: " HTTP.getNaiveSSLContext() "
)
//
HTTP.get(
url: "https://xxx.yyy/zzz",
ssl: " MyCustomSSLBuilder.build() "
)
```
### What type of PR is it?
Improvement
### Todos
* [ ] - Task
### What is the Jira issue?
[ZEPPELIN-2443]
### How should this be tested?
follow the samples above or in documentation
### Questions:
* Does the licenses files need update? NO
* Is there breaking changes for older versions? NO
* Does this needs documentation? YES
Author: dlukyanov <dl...@ukr.net>
Closes #2287 from dlukyanov/master and squashes the following commits:
4baa22e [dlukyanov] ZEPPELIN-2443
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/925d09ce
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/925d09ce
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/925d09ce
Branch: refs/heads/master
Commit: 925d09cef54e3056ae10ec0e547f46097e639158
Parents: ec84613
Author: dlukyanov <dl...@ukr.net>
Authored: Wed Apr 26 09:38:05 2017 +0300
Committer: Lee moon soo <mo...@apache.org>
Committed: Fri Apr 28 02:52:13 2017 -0700
----------------------------------------------------------------------
docs/interpreter/groovy.md | 56 +++++++++++-----
groovy/src/main/resources/HTTP.groovy | 104 +++++++++++++++++++++++++++--
2 files changed, 136 insertions(+), 24 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/925d09ce/docs/interpreter/groovy.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/groovy.md b/docs/interpreter/groovy.md
index 01074a3..f64cbde 100644
--- a/docs/interpreter/groovy.md
+++ b/docs/interpreter/groovy.md
@@ -39,7 +39,8 @@ def r = HTTP.get(
headers: [
'Accept':'application/json',
//'Authorization:' : g.getProperty('search_auth'),
- ]
+ ],
+ ssl : g.getProperty('search_ssl') // assume groovy interpreter property search_ssl = HTTP.getNaiveSSLContext()
)
//check response code
if( r.response.code==200 ) {
@@ -76,41 +77,62 @@ g.table(
* `g.angular(String name)`
-Returns angular object by name. Look up notebook scope first and then global scope.
+ Returns angular object by name. Look up notebook scope first and then global scope.
* `g.angularBind(String name, Object value)`
-
-Assign a new `value` into angular object `name`
+
+ Assign a new `value` into angular object `name`
* `java.util.Properties g.getProperties()`
-returns all properties defined for this interpreter
+ returns all properties defined for this interpreter
* `String g.getProperty('PROPERTY_NAME')`
-```groovy
-g.PROPERTY_NAME
-g.'PROPERTY_NAME'
-g['PROPERTY_NAME']
-g.getProperties().getProperty('PROPERTY_NAME')
-```
+ ```groovy
+ g.PROPERTY_NAME
+ g.'PROPERTY_NAME'
+ g['PROPERTY_NAME']
+ g.getProperties().getProperty('PROPERTY_NAME')
+ ```
-All above the accessor to named property defined in groovy interpreter.
-In this case with name `PROPERTY_NAME`
+ All above the accessor to named property defined in groovy interpreter.
+ In this case with name `PROPERTY_NAME`
* `groovy.xml.MarkupBuilder g.html()`
-Starts or continues rendering of `%angular` to output and returns [groovy.xml.MarkupBuilder](http://groovy-lang.org/processing-xml.html#_markupbuilder)
-MarkupBuilder is usefull to generate html (xml)
+ Starts or continues rendering of `%angular` to output and returns [groovy.xml.MarkupBuilder](http://groovy-lang.org/processing-xml.html#_markupbuilder)
+ MarkupBuilder is usefull to generate html (xml)
* `void g.table(obj)`
-starts or continues rendering table rows.
+ starts or continues rendering table rows.
+
+ obj: List(rows) of List(columns) where first line is a header
+
+
+* `g.input(name, value )`
+
+ Creates `text` input with value specified. The parameter `value` is optional.
+
+* `g.select(name, default, Map<Object, String> options)`
+
+ Creates `select` input with defined options. The parameter `default` is optional.
+
+ ```g.select('sex', 'm', ['m':'man', 'w':'woman'])```
+
+* `g.checkbox(name, Collection checked, Map<Object, String> options)`
-obj: List(rows) of List(columns) where first line is a header
+ Creates `checkbox` input.
+
+* `g.get(name, default)`
+ Returns interpreter-based variable. Visibility depends on interpreter scope. The parameter `default` is optional.
+* `g.put(name, value)`
+ Stores new value into interpreter-based variable. Visibility depends on interpreter scope.
+
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/925d09ce/groovy/src/main/resources/HTTP.groovy
----------------------------------------------------------------------
diff --git a/groovy/src/main/resources/HTTP.groovy b/groovy/src/main/resources/HTTP.groovy
index fe4eb36..63fdc04 100644
--- a/groovy/src/main/resources/HTTP.groovy
+++ b/groovy/src/main/resources/HTTP.groovy
@@ -17,6 +17,16 @@
import groovy.json.JsonOutput
+import java.security.KeyStore;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.SSLContext;
+import java.security.SecureRandom;
+import javax.net.ssl.HttpsURLConnection;
+
/**
* simple http rest client for groovy
* by dlukyanov@ukr.net
@@ -44,6 +54,11 @@ public class HTTP{
return send(ctx);
}
+ public static Map<String,Object> head(Map<String,Object> ctx)throws IOException{
+ ctx.put('method','HEAD');
+ return send(ctx);
+ }
+
public static Map<String,Object> post(Map<String,Object> ctx)throws IOException{
ctx.put('method','POST');
return send(ctx);
@@ -59,6 +74,16 @@ public class HTTP{
return send(ctx);
}
+ /**
+ * @param url string where to send request
+ * @param query Map parameters to append to url
+ * @param method http method to be used in request. standard methods: GET, POST, PUT, DELETE, HEAD
+ * @param headers key-value map with headers that should be sent with request
+ * @param body request body/data to send to url (InputStream, CharSequence, or Map for json and x-www-form-urlencoded context types)
+ * @param encoding encoding name to use to send/receive data - default UTF-8
+ * @param receiver Closure that will be called to receive data from server. Defaults: `HTTP.JSON_RECEIVER` for json content-type and `HTTP.TEXT_RECEIVER` otherwise. Available: `HTTP.FILE_RECEIVER(File)` - stores response to file.
+ * @param ssl javax.net.ssl.SSLContext or String that evaluates the javax.net.ssl.SSLContext. example: send( url:..., ssl: "HTTP.getKeystoreSSLContext('./keystore.jks', 'testpass')" )
+ */
public static Map<String,Object> send(Map<String,Object> ctx)throws IOException{
String url = ctx.url;
Map<String,String> headers = (Map<String,String>)ctx.headers;
@@ -67,6 +92,7 @@ public class HTTP{
String encoding = ctx.encoding?:"UTF-8";
Closure receiver = (Closure)ctx.receiver;
Map<String,String> query = (Map<String,String>)ctx.query;
+ Object sslCtxObj= ctx.ssl;
//copy context and set default values
ctx = [:] + ctx;
@@ -78,14 +104,28 @@ public class HTTP{
}
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ if(sslCtxObj!=null && connection instanceof HttpsURLConnection){
+ SSLContext sslCtx = null;
+ if(sslCtxObj instanceof SSLContext){
+ sslCtx = (SSLContext)sslCtxObj;
+ }else if(sslCtxObj instanceof CharSequence){
+ //assume this is a groovy code to get ssl context
+ sslCtx = evaluateSSLContext((CharSequence)sslCtxObj);
+ }else{
+ throw new IllegalArgumentException("Unsupported ssl parameter ${sslCtxObj.getClass()}")
+ }
+ ((HttpsURLConnection)connection).setSSLSocketFactory(sslCtx.getSocketFactory());
+ }
connection.setDoOutput(true);
connection.setRequestMethod(method);
if ( headers!=null && !headers.isEmpty() ) {
//add headers
for (Map.Entry<String, String> entry : headers.entrySet()) {
- connection.addRequestProperty(entry.getKey(), entry.getValue());
- if("content-type".equals(entry.getKey().toLowerCase()))contentType=entry.getValue();
+ if(entry.getValue()){
+ connection.addRequestProperty(entry.getKey(), entry.getValue());
+ if("content-type".equals(entry.getKey().toLowerCase()))contentType=entry.getValue();
+ }
}
}
@@ -97,18 +137,20 @@ public class HTTP{
}else if(body instanceof InputStream){
out << (InputStream)body;
}else if(body instanceof Map){
- if( contentType.matches("(?i)[^/]+/json") ){
+ if( contentType =~ "(?i)[^/]+/json" ) {
out.withWriter((String)ctx.encoding){
it.append( JsonOutput.toJson((Map)body) );
- it.flush();
}
- }else{
- throw new IOException("Map body type supported only for */json content-type");
+ } else if( contentType =~ "(?i)[^/]+/x-www-form-urlencoded" ) {
+ out.withWriter((String)ctx.encoding) {
+ it.append( ((Map)body).collect{k,v-> ""+k+"="+URLEncoder.encode((String)v,'UTF-8') }.join('&') )
+ }
+ } else {
+ throw new IOException("Map body type supported only for */json of */x-www-form-urlencoded content-type");
}
}else if(body instanceof CharSequence){
out.withWriter((String)ctx.encoding){
it.append((CharSequence)body);
- it.flush();
}
}else{
throw new IOException("Unsupported body type: "+body.getClass());
@@ -151,4 +193,52 @@ public class HTTP{
}
return ctx;
}
+
+ @groovy.transform.Memoized
+ public static SSLContext getKeystoreSSLContext(String keystorePath, String keystorePass, String keystoreType="JKS", String keyPass = null){
+ if(keyPass == null) keyPass=keystorePass;
+ KeyStore clientStore = KeyStore.getInstance(keystoreType);
+ clientStore.load(new File( keystorePath ).newInputStream(), keystorePass.toCharArray());
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(clientStore, keyPass.toCharArray());
+ KeyManager[] kms = kmf.getKeyManagers();
+ //init TrustCerts
+ TrustManager[] trustCerts = new TrustManager[1];
+ trustCerts[0] = new X509TrustManager() {
+ public void checkClientTrusted( final X509Certificate[] chain, final String authType ) { }
+ public void checkServerTrusted( final X509Certificate[] chain, final String authType ) { }
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kms, trustCerts, new SecureRandom());
+ return sslContext;
+ }
+
+ @groovy.transform.Memoized
+ public static SSLContext getNaiveSSLContext(){
+ System.err.println("HTTP.getNaiveSSLContext() used. Must be disabled on prod!");
+ KeyManager[] kms = new KeyManager[0];
+ TrustManager[] trustCerts = new TrustManager[1];
+ trustCerts[0] = new X509TrustManager() {
+ public void checkClientTrusted( final X509Certificate[] chain, final String authType ) { }
+ public void checkServerTrusted( final X509Certificate[] chain, final String authType ) { }
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, trustCerts, new SecureRandom());
+ return sslContext;
+ }
+
+ /**
+ * evaluates code that should return SSLContext
+ */
+ @groovy.transform.Memoized
+ public static SSLContext evaluateSSLContext(CharSequence code) {
+ Object ssl = new GroovyShell( HTTP.class.getClassLoader() ).evaluate( code as String );
+ return (SSLContext) ssl;
+ }
}