Skip to content

Commit 104fb9b

Browse files
authored
Merge pull request #41 from IBMStreams/develop
Develop
2 parents 7ab349d + ef24ca4 commit 104fb9b

File tree

8 files changed

+148
-41
lines changed

8 files changed

+148
-41
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@
4141
* Enhancemement: Parameter contextResourceBase is now independent from parameter context
4242
* Enhancemement in description for generation of client certitficate which can be used in a brower
4343

44+
## v4.3.0
45+
* Enhancement: New parameter 'sslAppConfigName' to get the SSL server key/certificate and the client trust material from Streams application configuration with this name
46+

com.ibm.streamsx.inetserver/com.ibm.streamsx.inet/namespace-info.spl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* * `keyStore`
3333
* * `keyStorePassword`
3434
* * `keyPassword`
35+
* * `sslAppConfigName`
3536
*
3637
* **Note:** Jetty server instances will fail to start if they are placed on a single host and use the same port number.
3738
*

com.ibm.streamsx.inetserver/impl/java/src/com/ibm/streamsx/inet/rest/engine/ServletEngine.java

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@
44
*/
55
package com.ibm.streamsx.inet.rest.engine;
66

7+
import java.io.ByteArrayInputStream;
78
import java.io.File;
9+
import java.io.IOException;
10+
import java.io.InputStream;
811
import java.lang.management.ManagementFactory;
912
import java.net.URI;
13+
import java.security.KeyStore;
14+
import java.security.KeyStoreException;
15+
import java.security.NoSuchAlgorithmException;
16+
import java.security.cert.CertificateException;
1017
import java.util.ArrayList;
18+
import java.util.Base64;
1119
import java.util.Collections;
1220
import java.util.HashMap;
1321
import java.util.List;
1422
import java.util.Map;
23+
import java.util.Base64.Decoder;
1524
import java.util.concurrent.LinkedBlockingQueue;
1625
import java.util.concurrent.RejectedExecutionException;
1726
import java.util.concurrent.ThreadPoolExecutor;
@@ -49,6 +58,7 @@
4958
import org.eclipse.jetty.util.thread.ThreadPool;
5059

5160
import com.ibm.streams.operator.OperatorContext;
61+
import com.ibm.streams.operator.ProcessingElement;
5262
import com.ibm.streams.operator.StreamingData;
5363
import com.ibm.streams.operator.management.OperatorManagement;
5464
import com.ibm.streamsx.inet.rest.ops.Functions;
@@ -87,6 +97,8 @@ public class ServletEngine implements ServletEngineMBean, MBeanRegistration {
8797

8898
public static final String SSL_TRUSTSTORE_PARAM = "trustStore";
8999
public static final String SSL_TRUSTSTORE_PASSWORD_PARAM = "trustStorePassword";
100+
101+
public static final String SSL_APP_CONFIG_NAME_PARAM = "sslAppConfigName";
90102

91103
public static final int IDLE_TIMEOUT = 30000;
92104
public static final int STRICT_TRANSPORT_SECURITY_MAX_AGE = 2000;
@@ -168,7 +180,8 @@ public void join() throws InterruptedException {
168180
server = new Server(tp);
169181
handlers = new ContextHandlerCollection();
170182

171-
if (operatorContext.getParameterNames().contains(SSL_CERT_ALIAS_PARAM))
183+
if ( (operatorContext.getParameterNames().contains(SSL_CERT_ALIAS_PARAM))
184+
|| (operatorContext.getParameterNames().contains(SSL_APP_CONFIG_NAME_PARAM)))
172185
setHTTPSConnector(operatorContext, server, portNumber, host);
173186
else
174187
setHTTPConnector(operatorContext, server, portNumber, host);
@@ -208,47 +221,102 @@ private void setHTTPConnector(OperatorContext operatorContext, Server server, in
208221

209222
/**
210223
* Setup an HTTPS connector.
224+
* @throws KeyStoreException
225+
* @throws IOException
226+
* @throws CertificateException
227+
* @throws NoSuchAlgorithmException
211228
*/
212-
private void setHTTPSConnector(OperatorContext operatorContext, Server server, int httpsPort, String host) {
229+
private void setHTTPSConnector(OperatorContext operatorContext, Server server, int httpsPort, String host)
230+
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
213231
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
214-
//Key Store is required
215-
String keyStorePath = operatorContext.getParameterValues(SSL_KEYSTORE_PARAM).get(0);
216-
File keyStorePathFile = new File(keyStorePath);
217-
if (!keyStorePathFile.isAbsolute())
218-
keyStorePathFile = new File(operatorContext.getPE().getApplicationDirectory(), keyStorePath);
219-
String keyStorePathToLoad = keyStorePathFile.getAbsolutePath();
220-
System.out.println("keyStorePathToLoad=" + keyStorePathToLoad);
221-
sslContextFactory.setKeyStorePath(keyStorePathToLoad);
222-
//the key store password is optional
223-
if (operatorContext.getParameterNames().contains(SSL_KEYSTORE_PASSWORD_PARAM)) {
224-
String keyStorePassword = operatorContext.getParameterValues(SSL_KEYSTORE_PASSWORD_PARAM).get(0);
225-
System.out.println("keyStorePassword=****");
226-
sslContextFactory.setKeyStorePassword(Functions.obfuscate(keyStorePassword));
227-
}
228-
//Key password is required
229-
String keyPassword = operatorContext.getParameterValues(SSL_KEY_PASSWORD_PARAM).get(0);
230-
System.out.println("keyPassword=****");
231-
sslContextFactory.setKeyManagerPassword(Functions.obfuscate(keyPassword));
232-
//Key alias
233-
String alias = operatorContext.getParameterValues(SSL_CERT_ALIAS_PARAM).get(0);
234-
sslContextFactory.setCertAlias(alias);
235-
//Trust Store & password
236-
if (operatorContext.getParameterNames().contains(SSL_TRUSTSTORE_PARAM)) {
237-
String trustStorePath = operatorContext.getParameterValues(SSL_TRUSTSTORE_PARAM).get(0);
238-
File trustStorePathFile = new File(trustStorePath);
239-
if (!trustStorePathFile.isAbsolute())
240-
trustStorePathFile = new File(operatorContext.getPE().getApplicationDirectory(), trustStorePath);
241-
String trustStorePathToLoad = trustStorePathFile.getAbsolutePath();
242-
System.out.println("trustStorePathToLoad=" + trustStorePathToLoad);
243-
sslContextFactory.setTrustStorePath(trustStorePathToLoad);
244-
sslContextFactory.setNeedClientAuth(true);
245-
if (operatorContext.getParameterNames().contains(SSL_TRUSTSTORE_PASSWORD_PARAM)) {
246-
String trustStorePassword = operatorContext.getParameterValues(SSL_TRUSTSTORE_PASSWORD_PARAM).get(0);
247-
System.out.println("trustStorePassword=****");
248-
sslContextFactory.setTrustStorePassword(Functions.obfuscate(trustStorePassword));
232+
233+
//ssl configuration with legacy parameters
234+
if (operatorContext.getParameterNames().contains(SSL_CERT_ALIAS_PARAM)) {
235+
236+
//Key Store is required
237+
String keyStorePath = operatorContext.getParameterValues(SSL_KEYSTORE_PARAM).get(0);
238+
File keyStorePathFile = new File(keyStorePath);
239+
if (!keyStorePathFile.isAbsolute())
240+
keyStorePathFile = new File(operatorContext.getPE().getApplicationDirectory(), keyStorePath);
241+
String keyStorePathToLoad = keyStorePathFile.getAbsolutePath();
242+
System.out.println("keyStorePathToLoad=" + keyStorePathToLoad);
243+
sslContextFactory.setKeyStorePath(keyStorePathToLoad);
244+
245+
//the key store password is optional
246+
if (operatorContext.getParameterNames().contains(SSL_KEYSTORE_PASSWORD_PARAM)) {
247+
String keyStorePassword = operatorContext.getParameterValues(SSL_KEYSTORE_PASSWORD_PARAM).get(0);
248+
System.out.println("keyStorePassword=****");
249+
sslContextFactory.setKeyStorePassword(Functions.obfuscate(keyStorePassword));
250+
}
251+
252+
//Key password is required
253+
String keyPassword = operatorContext.getParameterValues(SSL_KEY_PASSWORD_PARAM).get(0);
254+
System.out.println("keyPassword=****");
255+
sslContextFactory.setKeyManagerPassword(Functions.obfuscate(keyPassword));
256+
257+
//Key alias
258+
String alias = operatorContext.getParameterValues(SSL_CERT_ALIAS_PARAM).get(0);
259+
sslContextFactory.setCertAlias(alias);
260+
261+
//Trust Store & password if necessary
262+
if (operatorContext.getParameterNames().contains(SSL_TRUSTSTORE_PARAM)) {
263+
String trustStorePath = operatorContext.getParameterValues(SSL_TRUSTSTORE_PARAM).get(0);
264+
File trustStorePathFile = new File(trustStorePath);
265+
if (!trustStorePathFile.isAbsolute())
266+
trustStorePathFile = new File(operatorContext.getPE().getApplicationDirectory(), trustStorePath);
267+
String trustStorePathToLoad = trustStorePathFile.getAbsolutePath();
268+
System.out.println("trustStorePathToLoad=" + trustStorePathToLoad);
269+
sslContextFactory.setTrustStorePath(trustStorePathToLoad);
270+
sslContextFactory.setNeedClientAuth(true);
271+
if (operatorContext.getParameterNames().contains(SSL_TRUSTSTORE_PASSWORD_PARAM)) {
272+
String trustStorePassword = operatorContext.getParameterValues(SSL_TRUSTSTORE_PASSWORD_PARAM).get(0);
273+
System.out.println("trustStorePassword=****");
274+
sslContextFactory.setTrustStorePassword(Functions.obfuscate(trustStorePassword));
275+
}
276+
}
277+
278+
//Configuration with application configuration
279+
} else {
280+
281+
ProcessingElement pe = operatorContext.getPE();
282+
String certAppConfigName = operatorContext.getParameterValues(SSL_APP_CONFIG_NAME_PARAM).get(0);
283+
Map<String, String> certProps = pe.getApplicationConfiguration(certAppConfigName);
284+
System.out.println("streams-certs len: " + new Integer(certProps.size()).toString() + " keyset: " + certProps.keySet().toString());
285+
if (certProps.isEmpty())
286+
throw new IllegalArgumentException("app config with name " + certAppConfigName + " is required");
287+
288+
//Key Store and password is required
289+
if ( ! certProps.containsKey("server.jks"))
290+
throw new IllegalArgumentException("Property server.jks is required in app config " + certAppConfigName);
291+
String keyB64Str = certProps.get("server.jks");
292+
Decoder decoder = Base64.getDecoder();
293+
byte[] keyBytes = decoder.decode(keyB64Str);
294+
InputStream keyStream = new ByteArrayInputStream(keyBytes);
295+
296+
if ( ! certProps.containsKey("server.pass"))
297+
throw new IllegalArgumentException("Property server.pass is required in app config " + certAppConfigName);
298+
String password = certProps.get("server.pass");
299+
300+
System.out.println("Load key store and passwd from app config " + certAppConfigName);
301+
KeyStore keyStore = KeyStore.getInstance("JKS");
302+
keyStore.load(keyStream, password.toCharArray());
303+
sslContextFactory.setKeyStore(keyStore);
304+
sslContextFactory.setKeyManagerPassword(password);
305+
306+
//Set optionally trust material
307+
if (certProps.containsKey("cacerts.jks")) {
308+
String trustB64Str = certProps.get("cacerts.jks");
309+
byte[] trustBytes = decoder.decode(trustB64Str);
310+
InputStream trustStream = new ByteArrayInputStream(trustBytes);
311+
312+
System.out.println("Load trust store and passwd from app config " + certAppConfigName);
313+
KeyStore trustStore = KeyStore.getInstance("JKS");
314+
trustStore.load(trustStream, password.toCharArray());
315+
sslContextFactory.setTrustStore(trustStore);
316+
sslContextFactory.setNeedClientAuth(true);
249317
}
250318
}
251-
319+
252320
sslContextFactory.setRenegotiationAllowed(false);
253321
sslContextFactory.setIncludeProtocols("TLSv1.2");
254322
String[] specs = {"^.*_(MD5|SHA|SHA1)$","^TLS_RSA_.*$","^.*_NULL_.*$","^.*_anon_.*$"};

com.ibm.streamsx.inetserver/impl/java/src/com/ibm/streamsx/inet/rest/ops/ServletOperator.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ public void setTrustStore(String ks) {}
100100
@Parameter(optional = true, description = "Password to the trust store.")
101101
public void setTrustStorePassword(String ksp) {}
102102

103+
@Parameter(optional = true, description = "The operator can get the SSL server key/certificate and the client trust "
104+
+ "material from Streams application configuration with this name. If this parameter is present, `"
105+
+ ServletEngine.SSL_CERT_ALIAS_PARAM +"`, `" + ServletEngine.SSL_KEYSTORE_PARAM + "`, `" + ServletEngine.SSL_KEY_PASSWORD_PARAM
106+
+ "`, `" + ServletEngine.SSL_TRUSTSTORE_PARAM + "` and `" + ServletEngine.SSL_TRUSTSTORE_PASSWORD_PARAM + "` are "
107+
+ "not allowed. The application configuration must contain the folowing properties:\\n"
108+
+ " \\t\\t* server.jks - Base 64 encoded representation of the Java key store with one key-certificate pair to be used as server key and certitficate.\\n"
109+
+ " \\t\\t* server.pass -Password for server.jks (and its key) and cacerts.jks.\\n"
110+
+ "The app configuration may contain property:\\n"
111+
+ " \\t\\t* cacerts.jks - Base 64 encoded representation of the Java trust store with the client trust material.\\n\\n"
112+
+ " If the property cacerts.jks is present, the operator requests client certificates."
113+
+ " To create the Streams application configuration you may use a comand like:\\n"
114+
+ " streamtool mkappconfig --description 'server cert and trust store' --property \\\"server.jks=$(base64 --wrap=0 etc/keystore.jks)\\\" --property \\\"server.pass=password\\\" --property \\\"cacerts.jks=$(base64 --wrap=0 etc/cacerts.jks)\\\" streams-certs")
115+
public void setSslAppConfigName(String ac) {}
116+
103117
// Creates a metric that the ServletEngine will fill in.
104118
private Metric serverPort;
105119
@CustomMetric(description="Jetty (HTTP/HTTPS) server port if the operator hosts the server, 0 otherwise", kind=Kind.GAUGE)
@@ -113,7 +127,7 @@ public void setTrustStorePassword(String ksp) {}
113127
public Metric getHttps() { return https; }
114128

115129
@ContextCheck
116-
public static void checkContextParameters(OperatorContextChecker checker) {
130+
public static void checkContextParameters(OperatorContextChecker checker) {
117131

118132
checker.checkDependentParameters(ServletEngine.SSL_CERT_ALIAS_PARAM, ServletEngine.SSL_KEYSTORE_PARAM, ServletEngine.SSL_KEY_PASSWORD_PARAM);
119133
checker.checkDependentParameters(ServletEngine.SSL_KEY_PASSWORD_PARAM, ServletEngine.SSL_CERT_ALIAS_PARAM);
@@ -122,6 +136,10 @@ public static void checkContextParameters(OperatorContextChecker checker) {
122136
checker.checkDependentParameters(ServletEngine.SSL_TRUSTSTORE_PARAM, ServletEngine.SSL_CERT_ALIAS_PARAM);
123137
checker.checkDependentParameters(ServletEngine.SSL_TRUSTSTORE_PASSWORD_PARAM, ServletEngine.SSL_TRUSTSTORE_PARAM);
124138

139+
checker.checkExcludedParameters(ServletEngine.SSL_APP_CONFIG_NAME_PARAM,
140+
ServletEngine.SSL_CERT_ALIAS_PARAM, ServletEngine.SSL_KEYSTORE_PARAM, ServletEngine.SSL_KEY_PASSWORD_PARAM,
141+
ServletEngine.SSL_TRUSTSTORE_PARAM, ServletEngine.SSL_TRUSTSTORE_PASSWORD_PARAM);
142+
125143
checker.checkDependentParameters(ServletEngine.CONTEXT_RESOURCE_BASE_PARAM, ServletEngine.CONTEXT_PARAM);
126144
}
127145

com.ibm.streamsx.inetserver/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This toolkit separates its functionality into a number of namespaces:
1414
* [namespace:com.ibm.streamsx.inet.wsserver|com.ibm.streamsx.inet.wsserver]: Operators that embed a (jetty) WebSocket server to expose streaming data as WebSocket messages.
1515

1616
</description>
17-
<version>4.2.0</version>
17+
<version>4.3.0</version>
1818
<requiredProductVersion>4.0.1.0</requiredProductVersion>
1919
</identity>
2020
<dependencies/>

samples/HTTPTupleInjectAndView/com.ibm.streamsx.inet.sample.rest/SimpleInject.spl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ use com.ibm.streamsx.inet.rest::*;
3535
* output/bin/standalone
3636
* Point browser to [https://localhost:1443] and accept the SEC_ERROR_UNKNOWN_ISSUER error and add an exception.
3737
*
38+
* This sample is enabled to use a secure connection by default. The server key and self signed certificate are stored
39+
* in key store etc/keystore.jks.
40+
*
41+
* To enable client authentication with client certificate add parameter `trustStore: "etc/cacerts.jks` to all three
42+
* rest operators and import the client key/certificate file `etc/client.pfx` into your browser certificate manager.
43+
*
44+
* Alternatively the import of the key- and trust-material from an Streams application configuration is supported.
45+
* Add parameter `sslAppConfigName: "streams-certs"` to all tree rest operators and remove `certificateAlias`,
46+
* `keyStore`, `keyPassword` and `trustStore`.
47+
* Generate the application configuration:
48+
* streamtool mkappconfig --description 'server cert and trust store' --property "server.jks=$(base64 --wrap=0 etc/keystore.jks)" --property "server.pass=changeit" --property "cacerts.jks=$(base64 --wrap=0 etc/cacerts.jks)" streams-certs
49+
*
3850
*/
3951
public composite SimpleInject {
4052

@@ -45,7 +57,8 @@ public composite SimpleInject {
4557
certificateAlias: "mykey";
4658
keyStore: "etc/keystore.jks";
4759
keyPassword: "changeit";
48-
//context: "sensors";
60+
//trustStore: "etc/cacerts.jks";
61+
//sslAppConfigName: "streams-certs";
4962
config
5063
// Ensure the operators are in a single PE to have a single web-server
5164
placement: partitionColocation("jetty1443");
@@ -59,6 +72,8 @@ public composite SimpleInject {
5972
certificateAlias: "mykey";
6073
keyStore: "etc/keystore.jks";
6174
keyPassword: "changeit";
75+
//trustStore: "etc/cacerts.jks";
76+
//sslAppConfigName: "streams-certs";
6277
context: "state";
6378
contextResourceBase: "opt/statetest";
6479
config
@@ -72,6 +87,8 @@ public composite SimpleInject {
7287
certificateAlias: "mykey";
7388
keyStore: "etc/keystore.jks";
7489
keyPassword: "changeit";
90+
//trustStore: "etc/cacerts.jks";
91+
//sslAppConfigName: "streams-certs";
7592
context: "wct";
7693
contextResourceBase: "opt/wctest";
7794
config
971 Bytes
Binary file not shown.
2.67 KB
Binary file not shown.

0 commit comments

Comments
 (0)