This is an automated email from the ASF dual-hosted git repository.
andor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push:
new fed6cda ZOOKEEPER-3371: Port unification for Jetty admin server
fed6cda is described below
commit fed6cdad1e7a4fb83b568e8bf3ba3cf1da492db9
Author: Eric Lee <[email protected]>
AuthorDate: Mon Jul 29 17:09:35 2019 +0200
ZOOKEEPER-3371: Port unification for Jetty admin server
Summary:
Jetty does not have support for port unification. Subclassing
of some Java networking libraries is necessary for ZooKeeper to serve
both HTTP and HTTPS traffic on the same port.
Note: The traffic is encrypted, but the authentication isn't right because
the certificates are not set up correctly for HTTP requests. This will be fixed
soon.
Test Plan:
```
[ericlee123 ~/zookeeper] (admin-PU) > ant -Dtestcase=JettyAdminServerTest
test-core-java
...
junit.run-concurrent:
[echo] Running 1 concurrent JUnit processes.
[junit] Running org.apache.zookeeper.server.admin.JettyAdminServerTest
[junit] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed:
3.394 sec
fail.build.on.test.failure:
junit.run:
test-core-java:
BUILD SUCCESSFUL
Total time: 9 seconds
```
Author: Eric Lee <[email protected]>
Author: Eric Lee <[email protected]>
Reviewers: [email protected], [email protected], [email protected]
Closes #924 from ericlee123/ZOOKEEPER-3371 and squashes the following
commits:
74ca00d33 [Eric Lee] Merge branch 'ZOOKEEPER-3371' of
https://github.com/ericlee123/zookeeper into ZOOKEEPER-3371
be4d9c263 [Eric Lee] cleaned up print statements from testing
8cda69cfb [Eric Lee] Merge branch 'master' into ZOOKEEPER-3371
329efe204 [Eric Lee] cleaned up unused imports, handles different types of
key/trust store loading, and cleaned up TLS detection
2e5c2e5d9 [Eric Lee] updated jetty version + fixed deprecation warnings
39279a554 [Eric Lee] small lint
4e21f6a6a [Eric Lee] Fixed build failure + added docs
c55a0193c [Eric Lee] [AdminPU] Port unification for Jetty admin server
---
build.xml | 2 +-
pom.xml | 2 +-
.../src/main/resources/markdown/zookeeperAdmin.md | 8 ++
.../java/org/apache/zookeeper/common/X509Util.java | 52 +++++---
.../zookeeper/server/admin/JettyAdminServer.java | 75 ++++++++++--
.../zookeeper/server/admin/ReadAheadEndpoint.java | 134 +++++++++++++++++++++
.../server/admin/UnifiedConnectionFactory.java | 114 ++++++++++++++++++
.../server/admin/JettyAdminServerTest.java | 103 +++++++++++++++-
8 files changed, 456 insertions(+), 34 deletions(-)
diff --git a/build.xml b/build.xml
index f2b81f0..8c27f3d 100644
--- a/build.xml
+++ b/build.xml
@@ -54,7 +54,7 @@ xmlns:cs="antlib:com.puppycrawl.tools.checkstyle.ant">
<property name="javacc.version" value="5.0"/>
- <property name="jetty.version" value="9.4.15.v20190215"/>
+ <property name="jetty.version" value="9.4.18.v20190429"/>
<property name="jackson.version" value="2.9.9.1"/>
<property name="dependency-check-ant.version" value="4.0.2"/>
diff --git a/pom.xml b/pom.xml
index e06dc48..6176f6b 100755
--- a/pom.xml
+++ b/pom.xml
@@ -279,7 +279,7 @@
<hamcrest.version>1.3</hamcrest.version>
<commons-cli.version>1.2</commons-cli.version>
<netty.version>4.1.36.Final</netty.version>
- <jetty.version>9.4.17.v20190418</jetty.version>
+ <jetty.version>9.4.18.v20190429</jetty.version>
<jackson.version>2.9.9.1</jackson.version>
<json.version>1.1.1</json.version>
<jline.version>2.11</jline.version>
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index d38afd2..c596546 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -1462,6 +1462,14 @@ Both subsystems need to have sufficient amount of
threads to achieve peak read t
#### AdminServer configuration
+**New in 3.6.0:** The following
+options are used to configure the [AdminServer](#sc_adminserver).
+
+* *admin.portUnification* :
+ (Java system property: **zookeeper.admin.portUnification**)
+ Enable the admin port to accept both HTTP and HTTPS traffic.
+ Defaults to disabled.
+
**New in 3.5.0:** The following
options are used to configure the [AdminServer](#sc_adminserver).
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
index 004446a..84ddd44 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
@@ -398,6 +398,38 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
}
}
+ public static KeyStore loadKeyStore(
+ String keyStoreLocation,
+ String keyStorePassword,
+ String keyStoreTypeProp)
+ throws IOException, GeneralSecurityException {
+ KeyStoreFileType storeFileType =
+ KeyStoreFileType.fromPropertyValueOrFileName(
+ keyStoreTypeProp, keyStoreLocation);
+ return FileKeyStoreLoaderBuilderProvider
+ .getBuilderForKeyStoreFileType(storeFileType)
+ .setKeyStorePath(keyStoreLocation)
+ .setKeyStorePassword(keyStorePassword)
+ .build()
+ .loadKeyStore();
+ }
+
+ public static KeyStore loadTrustStore(
+ String trustStoreLocation,
+ String trustStorePassword,
+ String trustStoreTypeProp)
+ throws IOException, GeneralSecurityException {
+ KeyStoreFileType storeFileType =
+ KeyStoreFileType.fromPropertyValueOrFileName(
+ trustStoreTypeProp, trustStoreLocation);
+ return FileKeyStoreLoaderBuilderProvider
+ .getBuilderForKeyStoreFileType(storeFileType)
+ .setTrustStorePath(trustStoreLocation)
+ .setTrustStorePassword(trustStorePassword)
+ .build()
+ .loadTrustStore();
+ }
+
/**
* Creates a key manager by loading the key store from the given file of
* the given type, optionally decrypting it using the given password.
@@ -419,15 +451,7 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
keyStorePassword = "";
}
try {
- KeyStoreFileType storeFileType =
- KeyStoreFileType.fromPropertyValueOrFileName(
- keyStoreTypeProp, keyStoreLocation);
- KeyStore ks = FileKeyStoreLoaderBuilderProvider
- .getBuilderForKeyStoreFileType(storeFileType)
- .setKeyStorePath(keyStoreLocation)
- .setKeyStorePassword(keyStorePassword)
- .build()
- .loadKeyStore();
+ KeyStore ks = loadKeyStore(keyStoreLocation, keyStorePassword,
keyStoreTypeProp);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, keyStorePassword.toCharArray());
@@ -480,15 +504,7 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
trustStorePassword = "";
}
try {
- KeyStoreFileType storeFileType =
- KeyStoreFileType.fromPropertyValueOrFileName(
- trustStoreTypeProp, trustStoreLocation);
- KeyStore ts = FileKeyStoreLoaderBuilderProvider
- .getBuilderForKeyStoreFileType(storeFileType)
- .setTrustStorePath(trustStoreLocation)
- .setTrustStorePassword(trustStorePassword)
- .build()
- .loadTrustStore();
+ KeyStore ts = loadTrustStore(trustStoreLocation,
trustStorePassword, trustStoreTypeProp);
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new
X509CertSelector());
if (crlEnabled || ocspEnabled) {
pbParams.setRevocationEnabled(true);
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
index 005c9fe..6384179 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
@@ -19,22 +19,26 @@
package org.apache.zookeeper.server.admin;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.zookeeper.common.*;
import org.apache.zookeeper.server.ZooKeeperServer;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,6 +63,8 @@ public class JettyAdminServer implements AdminServer {
public static final int DEFAULT_IDLE_TIMEOUT = 30000;
public static final String DEFAULT_COMMAND_URL = "/commands";
private static final String DEFAULT_ADDRESS = "0.0.0.0";
+ public static final int DEFAULT_STS_MAX_AGE = 1 * 24 * 60 * 60; //
seconds in a day
+ public static final int DEFAULT_HTTP_VERSION = 11; // based on
HttpVersion.java in jetty
private final Server server;
private final String address;
@@ -67,24 +73,75 @@ public class JettyAdminServer implements AdminServer {
private final String commandUrl;
private ZooKeeperServer zkServer;
- public JettyAdminServer() throws AdminServerException {
+ public JettyAdminServer() throws AdminServerException, IOException,
GeneralSecurityException {
this(System.getProperty("zookeeper.admin.serverAddress",
DEFAULT_ADDRESS),
Integer.getInteger("zookeeper.admin.serverPort", DEFAULT_PORT),
Integer.getInteger("zookeeper.admin.idleTimeout",
DEFAULT_IDLE_TIMEOUT),
- System.getProperty("zookeeper.admin.commandURL",
DEFAULT_COMMAND_URL));
+ System.getProperty("zookeeper.admin.commandURL",
DEFAULT_COMMAND_URL),
+ Integer.getInteger("zookeeper.admin.httpVersion",
DEFAULT_HTTP_VERSION),
+ Boolean.getBoolean("zookeeper.admin.portUnification"));
}
- public JettyAdminServer(String address, int port, int timeout, String
commandUrl) {
+ public JettyAdminServer(String address,
+ int port,
+ int timeout,
+ String commandUrl,
+ int httpVersion,
+ boolean portUnification) throws IOException,
GeneralSecurityException {
this.port = port;
this.idleTimeout = timeout;
this.commandUrl = commandUrl;
this.address = address;
server = new Server();
- ServerConnector connector = new ServerConnector(server);
+ ServerConnector connector = null;
+
+ if (!portUnification) {
+ connector = new ServerConnector(server);
+ } else {
+ SecureRequestCustomizer customizer = new SecureRequestCustomizer();
+ customizer.setStsMaxAge(DEFAULT_STS_MAX_AGE);
+ customizer.setStsIncludeSubDomains(true);
+
+ HttpConfiguration config = new HttpConfiguration();
+ config.setSecureScheme("https");
+ config.addCustomizer(customizer);
+
+ try (QuorumX509Util x509Util = new QuorumX509Util()) {
+ String privateKeyType =
System.getProperty(x509Util.getSslKeystoreTypeProperty(), "");
+ String privateKeyPath =
System.getProperty(x509Util.getSslKeystoreLocationProperty(), "");
+ String privateKeyPassword =
System.getProperty(x509Util.getSslKeystorePasswdProperty(), "");
+ String certAuthType =
System.getProperty(x509Util.getSslTruststoreTypeProperty(), "");
+ String certAuthPath =
System.getProperty(x509Util.getSslTruststoreLocationProperty(), "");
+ String certAuthPassword =
System.getProperty(x509Util.getSslTruststorePasswdProperty(), "");
+ KeyStore keyStore = null, trustStore = null;
+
+ try {
+ keyStore = X509Util.loadKeyStore(privateKeyPath,
privateKeyPassword, privateKeyType);
+ trustStore = X509Util.loadTrustStore(certAuthPath,
certAuthPassword, certAuthType);
+ LOG.info("Successfully loaded private key from " +
privateKeyPath);
+ LOG.info("Successfully loaded certificate authority from "
+ certAuthPath);
+ } catch (Exception e) {
+ LOG.error("Failed to load authentication certificates for
admin server: " + e);
+ throw e;
+ }
+
+ SslContextFactory sslContextFactory = new
SslContextFactory.Server();
+ sslContextFactory.setKeyStore(keyStore);
+ sslContextFactory.setKeyStorePassword(privateKeyPassword);
+ sslContextFactory.setTrustStore(trustStore);
+ sslContextFactory.setTrustStorePassword(certAuthPassword);
+
+ connector = new ServerConnector(server,
+ new UnifiedConnectionFactory(sslContextFactory,
HttpVersion.fromVersion(httpVersion).asString()),
+ new HttpConnectionFactory(config));
+ }
+ }
+
connector.setHost(address);
connector.setPort(port);
connector.setIdleTimeout(idleTimeout);
+
server.addConnector(connector);
ServletContextHandler context = new
ServletContextHandler(ServletContextHandler.SESSIONS);
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/ReadAheadEndpoint.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/ReadAheadEndpoint.java
new file mode 100644
index 0000000..641f891
--- /dev/null
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/ReadAheadEndpoint.java
@@ -0,0 +1,134 @@
+/**
+ * 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.
+ */
+
+// This code was found and refactored from here:
+//
https://stackoverflow.com/questions/11182192/how-do-i-serve-https-and-http-for-jetty-from-one-port/40076056#40076056
+
+package org.apache.zookeeper.server.admin;
+
+import java.lang.IllegalArgumentException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadPendingException;
+import java.nio.channels.WritePendingException;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+
+public class ReadAheadEndpoint implements EndPoint {
+ private final EndPoint endPoint;
+ private final ByteBuffer start;
+ private final byte[] bytes;
+ private int leftToRead;
+ private IOException pendingException = null;
+
+ @Override public InetSocketAddress getLocalAddress () { return
endPoint.getLocalAddress(); }
+ @Override public InetSocketAddress getRemoteAddress () { return
endPoint.getRemoteAddress(); }
+ @Override public boolean isOpen () { return
endPoint.isOpen(); }
+ @Override public long getCreatedTimeStamp () { return
endPoint.getCreatedTimeStamp(); }
+ @Override public boolean isOutputShutdown () { return
endPoint.isOutputShutdown(); }
+ @Override public boolean isInputShutdown () { return
endPoint.isInputShutdown(); }
+ @Override public void shutdownOutput () {
endPoint.shutdownOutput(); }
+ @Override public void close () {
endPoint.close(); }
+ @Override public Object getTransport () { return
endPoint.getTransport(); }
+ @Override public long getIdleTimeout () { return
endPoint.getIdleTimeout(); }
+ @Override public Connection getConnection () { return
endPoint.getConnection(); }
+ @Override public void onOpen () {
endPoint.onOpen(); }
+ @Override public void onClose () {
endPoint.onClose(); }
+ @Override public boolean isOptimizedForDirectBuffers() { return
endPoint.isOptimizedForDirectBuffers(); }
+ @Override public boolean isFillInterested () { return
endPoint.isFillInterested(); }
+ @Override public boolean tryFillInterested (Callback
v) { return endPoint.tryFillInterested(v); }
+ @Override public boolean flush
(ByteBuffer... v) throws IOException { return endPoint.flush(v); }
+ @Override public void setIdleTimeout (long
v) { endPoint.setIdleTimeout(v); }
+ @Override public void write (Callback
v, ByteBuffer... b) throws WritePendingException { endPoint.write(v, b); }
+ @Override public void setConnection (Connection
v) { endPoint.setConnection(v); }
+ @Override public void upgrade (Connection
v) { endPoint.upgrade(v); }
+ @Override public void fillInterested (Callback
v) throws ReadPendingException { endPoint.fillInterested(v); }
+
+ public ReadAheadEndpoint(final EndPoint channel, final int
readAheadLength){
+ if (channel == null) {
+ throw new IllegalArgumentException("channel cannot be null");
+ }
+
+ this.endPoint = channel;
+ start = ByteBuffer.wrap(bytes = new byte[readAheadLength]);
+ start.flip();
+ leftToRead = readAheadLength;
+ }
+
+ private synchronized void readAhead() throws IOException {
+ if (leftToRead > 0) {
+ int n = 0;
+ do {
+ n = endPoint.fill(start);
+ } while (n == 0 && endPoint.isOpen() &&
!endPoint.isInputShutdown());
+ if (n == -1) {
+ leftToRead = -1;
+ } else {
+ leftToRead -= n;
+ }
+ if (leftToRead <= 0) start.rewind();
+ }
+ }
+
+ private int readFromStart(final ByteBuffer dst) throws IOException {
+ final int n = Math.min(dst.remaining(), start.remaining());
+ if (n > 0) {
+ dst.put(bytes, start.position(), n);
+ start.position(start.position() + n);
+ dst.flip();
+ }
+ return n;
+ }
+
+ @Override
+ public synchronized int fill(final ByteBuffer dst) throws IOException {
+ throwPendingException();
+ if (leftToRead > 0) readAhead();
+ if (leftToRead > 0) return 0;
+ final int sr = start.remaining();
+ if (sr > 0) {
+ dst.compact();
+ final int n = readFromStart(dst);
+ if (n < sr) return n;
+ }
+ return sr + endPoint.fill(dst);
+ }
+
+ public byte[] getBytes() {
+ if (pendingException == null) {
+ try {
+ readAhead();
+ } catch (IOException e) {
+ pendingException = e;
+ }
+ }
+ byte[] ret = new byte[bytes.length];
+ System.arraycopy(bytes, 0, ret, 0, ret.length);
+ return ret;
+ }
+
+ private void throwPendingException() throws IOException {
+ if (pendingException != null) {
+ IOException e = pendingException;
+ pendingException = null;
+ throw e;
+ }
+ }
+}
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/UnifiedConnectionFactory.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/UnifiedConnectionFactory.java
new file mode 100644
index 0000000..89018f4
--- /dev/null
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/UnifiedConnectionFactory.java
@@ -0,0 +1,114 @@
+/**
+ * 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.zookeeper.server.admin;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The purpose of this class is to dynamically determine whether to create
+ * a plaintext or SSL connection whenever newConnection() is called. It works
+ * in conjunction with ReadAheadEndpoint to inspect bytes on the incoming
+ * connection.
+ */
+public class UnifiedConnectionFactory extends AbstractConnectionFactory {
+ private static final Logger LOG =
LoggerFactory.getLogger(UnifiedConnectionFactory.class);
+
+ private final SslContextFactory sslContextFactory;
+ private final String nextProtocol;
+
+ public UnifiedConnectionFactory(String nextProtocol) { this(null,
nextProtocol); }
+
+ public UnifiedConnectionFactory(SslContextFactory factory, String
nextProtocol) {
+ super("SSL");
+ this.sslContextFactory = (factory == null) ? new
SslContextFactory.Server() : factory;
+ this.nextProtocol = nextProtocol;
+ this.addBean(this.sslContextFactory);
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ SSLEngine engine = this.sslContextFactory.newSSLEngine();
+ SSLSession session = engine.getSession();
+ engine.setUseClientMode(false);
+ if (session.getPacketBufferSize() > this.getInputBufferSize()) {
+ this.setInputBufferSize(session.getPacketBufferSize());
+ }
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint
realEndPoint) {
+ ReadAheadEndpoint aheadEndpoint = new ReadAheadEndpoint(realEndPoint,
1);
+ byte[] bytes = aheadEndpoint.getBytes();
+ boolean isSSL;
+
+ if (bytes == null || bytes.length == 0) {
+ isSSL = false;
+ LOG.warn("Incoming connection has no data");
+ } else {
+ byte b = bytes[0]; // TLS first byte is 0x16, let's not support
SSLv3 and below
+ isSSL = b == 0x16; // matches SSL detection in
NettyServerCnxnFactory.java
+ }
+
+ LOG.debug(String.format("UnifiedConnectionFactory: newConnection()
with SSL = %b", isSSL));
+
+ EndPoint plainEndpoint;
+ SslConnection sslConnection;
+
+ if (isSSL) {
+ SSLEngine engine =
this.sslContextFactory.newSSLEngine(aheadEndpoint.getRemoteAddress());
+ engine.setUseClientMode(false);
+ sslConnection = this.newSslConnection(connector, aheadEndpoint,
engine);
+
sslConnection.setRenegotiationAllowed(this.sslContextFactory.isRenegotiationAllowed());
+ this.configure(sslConnection, connector, aheadEndpoint);
+ plainEndpoint = sslConnection.getDecryptedEndPoint();
+ } else {
+ sslConnection = null;
+ plainEndpoint = aheadEndpoint;
+ }
+
+ ConnectionFactory next = connector.getConnectionFactory(nextProtocol);
+ Connection connection = next.newConnection(connector, plainEndpoint);
+ plainEndpoint.setConnection(connection);
+
+ return (sslConnection == null) ? connection : sslConnection;
+ }
+
+ protected SslConnection newSslConnection(final Connector connector, final
EndPoint endPoint, final SSLEngine engine) {
+ return new SslConnection(connector.getByteBufferPool(),
connector.getExecutor(), endPoint, engine);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s@%x{%s->%s}", new
Object[]{this.getClass().getSimpleName(),
+ Integer.valueOf(this.hashCode()), this.getProtocol(),
this.nextProtocol});
+ }
+}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
index bc8aab6..26ca2f9 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
@@ -19,27 +19,45 @@
package org.apache.zookeeper.server.admin;
import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import org.apache.zookeeper.common.KeyStoreFileType;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
import org.apache.zookeeper.PortAssignment;
import org.apache.zookeeper.ZKTestCase;
+import org.apache.zookeeper.common.X509KeyType;
+import org.apache.zookeeper.common.X509TestContext;
import org.apache.zookeeper.server.ZooKeeperServerMainTest;
import org.apache.zookeeper.server.admin.AdminServer.AdminServerException;
import org.apache.zookeeper.server.quorum.QuorumPeerTestBase;
import org.apache.zookeeper.test.ClientBase;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.GeneralSecurityException;
+
public class JettyAdminServerTest extends ZKTestCase{
protected static final Logger LOG =
LoggerFactory.getLogger(JettyAdminServerTest.class);
private static final String URL_FORMAT = "http://localhost:%d/commands";
+ private static final String HTTPS_URL_FORMAT =
"https://localhost:%d/commands";
private static final int jettyAdminPort = PortAssignment.unique();
@Before
@@ -49,11 +67,79 @@ public class JettyAdminServerTest extends ZKTestCase{
System.setProperty("zookeeper.admin.serverPort", "" + jettyAdminPort);
}
+ @Before
+ public void setupEncryption() {
+ Security.addProvider(new BouncyCastleProvider());
+ File tmpDir = null;
+ X509TestContext x509TestContext = null;
+ try {
+ tmpDir = ClientBase.createEmptyTestDir();
+ x509TestContext = X509TestContext.newBuilder()
+ .setTempDir(tmpDir)
+ .setKeyStorePassword("")
+ .setKeyStoreKeyType(X509KeyType.EC)
+ .setTrustStorePassword("")
+ .setTrustStoreKeyType(X509KeyType.EC)
+ .build();
+ System.setProperty("zookeeper.ssl.quorum.keyStore.location",
+
x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath());
+ System.setProperty("zookeeper.ssl.quorum.trustStore.location",
+
x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath());
+ } catch (Exception e) {
+ LOG.info("Problems encountered while setting up encryption for
Jetty admin server test: " + e);
+ }
+ System.setProperty("zookeeper.ssl.quorum.keyStore.password", "");
+ System.setProperty("zookeeper.ssl.quorum.keyStore.type", "PEM");
+ System.setProperty("zookeeper.ssl.quorum.trustStore.password", "");
+ System.setProperty("zookeeper.ssl.quorum.trustStore.type", "PEM");
+ System.setProperty("zookeeper.admin.portUnification", "true");
+
+ // Create a trust manager that does not validate certificate chains
+ TrustManager[] trustAllCerts = new TrustManager[] { new
X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null; }
+ public void checkClientTrusted(X509Certificate[] certs, String
authType) {}
+ public void checkServerTrusted(X509Certificate[] certs, String
authType) {}
+ }};
+
+ // Create all-trusting trust manager
+ SSLContext sc = null;
+ try {
+ sc = SSLContext.getInstance("SSL");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ } catch (Exception e) { LOG.error("Failed to customize encryption for
HTTPS: e"); }
+
+ // Create all-trusting hostname verifier
+ HostnameVerifier allValid = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession session) {
return true; }
+ };
+
+ // This is a temporary fix while we do not yet have certificates set
up to make
+ // HTTPS requests correctly. This is equivalent to the "-k" option in
curl.
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ HttpsURLConnection.setDefaultHostnameVerifier(allValid);
+ }
+
+ @After
+ public void cleanUp() {
+ Security.removeProvider("BC");
+
+ System.clearProperty("zookeeper.admin.enableServer");
+ System.clearProperty("zookeeper.admin.serverPort");
+
+ System.clearProperty("zookeeper.ssl.quorum.keyStore.location");
+ System.clearProperty("zookeeper.ssl.quorum.keyStore.password");
+ System.clearProperty("zookeeper.ssl.quorum.keyStore.type");
+ System.clearProperty("zookeeper.ssl.quorum.trustStore.location");
+ System.clearProperty("zookeeper.ssl.quorum.trustStore.password");
+ System.clearProperty("zookeeper.ssl.quorum.trustStore.type");
+ System.clearProperty("zookeeper.admin.portUnification");
+ }
+
/**
* Tests that we can start and query a JettyAdminServer.
*/
@Test
- public void testJettyAdminServer() throws AdminServerException,
IOException {
+ public void testJettyAdminServer() throws AdminServerException,
IOException, SSLContextException, GeneralSecurityException {
JettyAdminServer server = new JettyAdminServer();;
try {
server.start();
@@ -146,16 +232,23 @@ public class JettyAdminServerTest extends ZKTestCase{
* Check that we can load the commands page of an AdminServer running at
* localhost:port. (Note that this should work even if no zk server is
set.)
*/
- private void queryAdminServer(int port) throws MalformedURLException,
IOException {
- queryAdminServer(String.format(URL_FORMAT, port));
+ private void queryAdminServer(int port) throws MalformedURLException,
IOException, SSLContextException {
+ queryAdminServer(String.format(URL_FORMAT, port), false);
+ queryAdminServer(String.format(HTTPS_URL_FORMAT, port), true);
}
/**
* Check that loading urlStr results in a non-zero length response.
*/
- private void queryAdminServer(String urlStr) throws MalformedURLException,
IOException {
+ private void queryAdminServer(String urlStr, boolean encrypted) throws
MalformedURLException, IOException, SSLContextException {
URL url = new URL(urlStr);
- BufferedReader dis = new BufferedReader(new
InputStreamReader((url.openStream())));
+ BufferedReader dis;
+ if (!encrypted) {
+ dis = new BufferedReader(new
InputStreamReader((url.openStream())));
+ } else {
+ HttpsURLConnection conn = (HttpsURLConnection)
url.openConnection();
+ dis = new BufferedReader(new
InputStreamReader(conn.getInputStream()));
+ }
String line = dis.readLine();
Assert.assertTrue(line.length() > 0);
}