You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by be...@apache.org on 2015/06/27 03:30:50 UTC

[1/2] mesos git commit: Add mutable get() to Try.

Repository: mesos
Updated Branches:
  refs/heads/master 88c37a2a5 -> beac384c7


Add mutable get() to Try<T>.

Review: https://reviews.apache.org/r/35962


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

Branch: refs/heads/master
Commit: aadf4a78a73731ba02bc4d2e6a7d9c41a7bb352d
Parents: 88c37a2
Author: Joris Van Remoortere <jo...@gmail.com>
Authored: Fri Jun 26 18:30:02 2015 -0700
Committer: Benjamin Hindman <be...@gmail.com>
Committed: Fri Jun 26 18:30:02 2015 -0700

----------------------------------------------------------------------
 3rdparty/libprocess/3rdparty/stout/include/stout/try.hpp | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/aadf4a78/3rdparty/libprocess/3rdparty/stout/include/stout/try.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/include/stout/try.hpp b/3rdparty/libprocess/3rdparty/stout/include/stout/try.hpp
index bf1540b..5ad6114 100644
--- a/3rdparty/libprocess/3rdparty/stout/include/stout/try.hpp
+++ b/3rdparty/libprocess/3rdparty/stout/include/stout/try.hpp
@@ -79,6 +79,14 @@ public:
     return data.get();
   }
 
+  T& get()
+  {
+    if (!data.isSome()) {
+      ABORT("Try::get() but state == ERROR: " + message);
+    }
+    return data.get();
+  }
+
   const std::string& error() const { assert(data.isNone()); return message; }
 
 private:


[2/2] mesos git commit: Add SSL tests.

Posted by be...@apache.org.
Add SSL tests.

Review: https://reviews.apache.org/r/35889


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

Branch: refs/heads/master
Commit: beac384c77d4a9c235a813e9286716f4509bdd55
Parents: aadf4a7
Author: Joris Van Remoortere <jo...@gmail.com>
Authored: Fri Jun 26 18:30:12 2015 -0700
Committer: Benjamin Hindman <be...@gmail.com>
Committed: Fri Jun 26 18:30:12 2015 -0700

----------------------------------------------------------------------
 3rdparty/libprocess/Makefile.am              |  24 +-
 3rdparty/libprocess/src/libevent.hpp         |   2 +
 3rdparty/libprocess/src/openssl.cpp          |  76 ++-
 3rdparty/libprocess/src/tests/ssl_client.cpp | 132 ++++
 3rdparty/libprocess/src/tests/ssl_tests.cpp  | 708 ++++++++++++++++++++++
 5 files changed, 909 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/beac384c/3rdparty/libprocess/Makefile.am
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/Makefile.am b/3rdparty/libprocess/Makefile.am
index 0a14eb9..358893c 100644
--- a/3rdparty/libprocess/Makefile.am
+++ b/3rdparty/libprocess/Makefile.am
@@ -4,6 +4,7 @@
 ACLOCAL_AMFLAGS = -I m4
 
 AUTOMAKE_OPTIONS = foreign
+LIBPROCESS_BUILD_DIR=@abs_top_builddir@
 
 # NOTE: The libprocess headers were moved to the Makefile in "include"
 # subdirectory to make it easy to preserve the directory structure of
@@ -71,12 +72,13 @@ libprocess_la_SOURCES +=	\
     src/openssl_util.hpp
 endif
 
-libprocess_la_CPPFLAGS =		\
-  -I$(srcdir)/include			\
-  -I$(srcdir)/$(STOUT)/include		\
-  -I$(BOOST)				\
-  -I$(LIBEV)				\
-  -I$(PICOJSON)				\
+libprocess_la_CPPFLAGS =			\
+  -DBUILD_DIR=\"$(LIBPROCESS_BUILD_DIR)\"	\
+  -I$(srcdir)/include				\
+  -I$(srcdir)/$(STOUT)/include			\
+  -I$(BOOST)					\
+  -I$(LIBEV)					\
+  -I$(PICOJSON)					\
   $(AM_CPPFLAGS)
 
 if ENABLE_LIBEVENT
@@ -154,6 +156,16 @@ tests_LDADD =				\
   $(HTTP_PARSER_LIB)			\
   $(EVENT_LIB)
 
+if ENABLE_SSL
+check_PROGRAMS += ssl-client
+ssl_client_SOURCES = src/tests/ssl_client.cpp
+ssl_client_CPPFLAGS = $(tests_CPPFLAGS)
+ssl_client_LDADD = $(tests_LDADD)
+
+tests_SOURCES +=						\
+  src/tests/ssl_tests.cpp
+endif
+
 benchmarks_SOURCES =			\
   src/tests/benchmarks.cpp
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/beac384c/3rdparty/libprocess/src/libevent.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/libevent.hpp b/3rdparty/libprocess/src/libevent.hpp
index a20f2c3..47b93f1 100644
--- a/3rdparty/libprocess/src/libevent.hpp
+++ b/3rdparty/libprocess/src/libevent.hpp
@@ -1,6 +1,8 @@
 #ifndef __LIBEVENT_HPP__
 #define __LIBEVENT_HPP__
 
+#include <event2/event.h>
+
 #include <stout/lambda.hpp>
 #include <stout/thread.hpp>
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/beac384c/3rdparty/libprocess/src/openssl.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/openssl.cpp b/3rdparty/libprocess/src/openssl.cpp
index 3c0fc4b..6ff4adb 100644
--- a/3rdparty/libprocess/src/openssl.cpp
+++ b/3rdparty/libprocess/src/openssl.cpp
@@ -250,14 +250,13 @@ string error_string(unsigned long code)
 }
 
 
-void initialize()
+// Tests can declare this function and use it to re-configure the SSL
+// environment variables programatically. Without explicitly declaring
+// this function, it is not visible. This is the preferred behavior as
+// we do not want applications changing these settings while they are
+// running (this would be undefined behavior).
+void reinitialize()
 {
-  static Once* initialized = new Once();
-
-  if (initialized->once()) {
-    return;
-  }
-
   // Load all the flags prefixed by SSL_ from the environment. See
   // comment at top of openssl.hpp for a full list.
   Try<Nothing> load = ssl_flags->load("SSL_");
@@ -269,33 +268,42 @@ void initialize()
 
   // Exit early if SSL is not enabled.
   if (!ssl_flags->enabled) {
-    initialized->done();
     return;
   }
 
-  // We MUST have entropy, or else there's no point to crypto.
-  if (!RAND_poll()) {
-    EXIT(EXIT_FAILURE) << "SSL socket requires entropy";
-  }
+  static Once* initialized_single_entry = new Once();
+
+  // We don't want to initialize everything multiple times, as we
+  // don't clean up some of these structures. The things we DO tend
+  // to re-initialize are things that are overwrites of settings,
+  // rather than allocations of new data structures.
+  if (!initialized_single_entry->once()) {
+    // We MUST have entropy, or else there's no point to crypto.
+    if (!RAND_poll()) {
+      EXIT(EXIT_FAILURE) << "SSL socket requires entropy";
+    }
+
+    // Initialize the OpenSSL library.
+    SSL_library_init();
+    SSL_load_error_strings();
 
-  // Initialize the OpenSSL library.
-  SSL_library_init();
-  SSL_load_error_strings();
+    // Prepare mutexes for threading callbacks.
+    mutexes = new std::mutex[CRYPTO_num_locks()];
 
-  // Prepare mutexes for threading callbacks.
-  mutexes = new std::mutex[CRYPTO_num_locks()];
+    // Install SSL threading callbacks.
+    // TODO(jmlvanre): the id mechanism is deprecated in OpenSSL.
+    CRYPTO_set_id_callback(&id_function);
+    CRYPTO_set_locking_callback(&locking_function);
+    CRYPTO_set_dynlock_create_callback(&dyn_create_function);
+    CRYPTO_set_dynlock_lock_callback(&dyn_lock_function);
+    CRYPTO_set_dynlock_destroy_callback(&dyn_destroy_function);
 
-  // Install SSL threading callbacks.
-  // TODO(jmlvanre): the id mechanism is deprecated in OpenSSL.
-  CRYPTO_set_id_callback(&id_function);
-  CRYPTO_set_locking_callback(&locking_function);
-  CRYPTO_set_dynlock_create_callback(&dyn_create_function);
-  CRYPTO_set_dynlock_lock_callback(&dyn_lock_function);
-  CRYPTO_set_dynlock_destroy_callback(&dyn_destroy_function);
+    ctx = SSL_CTX_new(SSLv23_method());
+    CHECK(ctx) << "Failed to create SSL context: "
+                << ERR_error_string(ERR_get_error(), NULL);
 
-  ctx = SSL_CTX_new(SSLv23_method());
-  CHECK(ctx) << "Failed to create SSL context: "
-             << ERR_error_string(ERR_get_error(), NULL);
+    initialized_single_entry->done();
+  }
 
   // Disable SSL session caching.
   SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
@@ -459,6 +467,20 @@ void initialize()
   if (!ssl_flags->enable_tls_v1_2) { ssl_options |= SSL_OP_NO_TLSv1_2; }
 
   SSL_CTX_set_options(ctx, ssl_options);
+}
+
+
+void initialize()
+{
+  static Once* initialized = new Once();
+
+  if (initialized->once()) {
+    return;
+  }
+
+  // We delegate to 'reinitialize()' so that tests can change the SSL
+  // configuration programatically.
+  reinitialize();
 
   initialized->done();
 }

http://git-wip-us.apache.org/repos/asf/mesos/blob/beac384c/3rdparty/libprocess/src/tests/ssl_client.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/ssl_client.cpp b/3rdparty/libprocess/src/tests/ssl_client.cpp
new file mode 100644
index 0000000..4e91bda
--- /dev/null
+++ b/3rdparty/libprocess/src/tests/ssl_client.cpp
@@ -0,0 +1,132 @@
+#include <iostream>
+
+#include <glog/logging.h>
+
+#include <gmock/gmock.h>
+
+#include <gtest/gtest.h>
+
+#include <process/gmock.hpp>
+#include <process/gtest.hpp>
+#include <process/socket.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/try.hpp>
+
+#include "openssl.hpp"
+
+using namespace process;
+using namespace process::network;
+
+using std::cout;
+using std::endl;
+using std::string;
+
+// We only run these tests if we have configured with '--enable-ssl'.
+#ifdef USE_SSL_SOCKET
+
+/**
+ * The flags that control the test SSL client behavior.
+ *
+ * These flags augment the environment variables prefixed by 'SSL_'
+ * that are introduced by @see process::network::openssl::Flags.
+ */
+class Flags : public virtual flags::FlagsBase
+{
+public:
+  Flags()
+  {
+    add(&Flags::use_ssl,
+      "use_ssl",
+      "Whether to try and connect using an SSL based socket. This is "
+      "separate from whether SSL_ENABLED is set. We use this for "
+      "testing failure cases.",
+      true);
+
+    add(&Flags::data,
+      "data",
+      "The message to send as the client.",
+      "Hello World");
+
+    add(&Flags::server, "server", "IP address of server", "127.0.0.1");
+
+    add(&Flags::port, "port", "Port of server", 5050);
+  }
+
+  bool use_ssl;
+  string data;
+  string server;
+  uint16_t port;
+};
+
+
+class SSLClientTest : public ::testing::Test
+{
+public:
+  static Flags flags;
+};
+
+
+Flags SSLClientTest::flags;
+
+
+int main(int argc, char** argv)
+{
+  // Load all the client flags.
+  Try<Nothing> load = SSLClientTest::flags.load("", argc, argv);
+  if (load.isError()) {
+    EXIT(EXIT_FAILURE) << "Failed to load flags: " << load.error();
+  }
+
+  if (SSLClientTest::flags.help) {
+    cout << SSLClientTest::flags.usage() << endl;
+    return EXIT_SUCCESS;
+  }
+
+  process::initialize();
+
+  openssl::initialize();
+
+  // Initialize Google Mock/Test.
+  testing::InitGoogleMock(&argc, argv);
+
+  // Add the libprocess test event listeners.
+  ::testing::TestEventListeners& listeners =
+    ::testing::UnitTest::GetInstance()->listeners();
+
+  listeners.Append(process::ClockTestEventListener::instance());
+  listeners.Append(process::FilterTestEventListener::instance());
+
+  return RUN_ALL_TESTS();
+}
+
+
+TEST_F(SSLClientTest, client)
+{
+  // Create the socket based on the 'use_ssl' flag. We use this to
+  // test whether a regular socket could connect to an SSL server
+  // socket.
+  const Try<Socket> create =
+    Socket::create(flags.use_ssl ? Socket::SSL : Socket::POLL);
+  ASSERT_SOME(create);
+
+  Socket socket = create.get();
+
+  Try<net::IP> ip = net::IP::parse(flags.server, AF_INET);
+  EXPECT_SOME(ip);
+
+  // Connect to the server socket located at `ip:port`.
+  const Future<Nothing> connect =
+    socket.connect(Address(ip.get(), flags.port));
+
+  // Verify that the client views the connection as established.
+  AWAIT_EXPECT_READY(connect);
+
+  // Send 'data' from the client to the server.
+  AWAIT_EXPECT_READY(socket.send(flags.data));
+
+  // Verify the client received the message back from the server.
+  AWAIT_EXPECT_EQ(flags.data, socket.recv());
+}
+
+#endif // USE_SSL_SOCKET
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mesos/blob/beac384c/3rdparty/libprocess/src/tests/ssl_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/ssl_tests.cpp b/3rdparty/libprocess/src/tests/ssl_tests.cpp
new file mode 100644
index 0000000..c077aae
--- /dev/null
+++ b/3rdparty/libprocess/src/tests/ssl_tests.cpp
@@ -0,0 +1,708 @@
+#include <stdio.h>
+
+#include <openssl/rsa.h>
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include <process/future.hpp>
+#include <process/gtest.hpp>
+#include <process/io.hpp>
+#include <process/socket.hpp>
+#include <process/subprocess.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+
+#include "openssl.hpp"
+#include "openssl_util.hpp"
+
+using std::map;
+using std::string;
+using std::vector;
+
+// We only run these tests if we have configured with '--enable-ssl'.
+#ifdef USE_SSL_SOCKET
+
+namespace process {
+namespace network {
+namespace openssl {
+
+// Forward declare the `reinitialize()` function since we want to
+// programatically change SSL flags during tests.
+void reinitialize();
+
+} // namespace openssl {
+} // namespace network {
+} // namespace process {
+
+using namespace process;
+using namespace process::network;
+
+
+// Wait for a subprocess and test the status code for the following
+// conditions of 'expected_status':
+//   1. 'None' = Anything but '0'.
+//   2. 'Some' = the value of 'expected_status'.
+// Returns Nothing if the resulting status code matches the
+// expectation otherwise a Failure with the output of the subprocess.
+// TODO(jmlvanre): Turn this into a generally useful abstraction for
+// gtest where we can have a more straigtforward 'expected_status'.
+Future<Nothing> await_subprocess(
+    const Subprocess& subprocess,
+    const Option<int>& expected_status = None())
+{
+  // Dup the pipe fd of the subprocess so we can read the output if
+  // needed.
+  int out = dup(subprocess.out().get());
+
+  // Once we get the status of the process.
+  return subprocess.status()
+    .then([=](const Option<int>& status) -> Future<Nothing> {
+      // If the status is not set, fail out.
+      if (status.isNone()) {
+        return Failure("Subprocess status is none");
+      }
+
+      // If the status is not what we expect then fail out with the
+      // output of the subprocess. The failure message will include
+      // the assertion failures of the subprocess.
+      if ((expected_status.isSome() && status.get() != expected_status.get()) ||
+          (expected_status.isNone() && status.get() == 0)) {
+        return io::read(out)
+          .then([](const string& output) -> Future<Nothing> {
+            return Failure("\n[++++++++++] Subprocess output.\n" + output +
+                           "[++++++++++]\n");
+          });
+      }
+
+      // If the subprocess ran successfully then return nothing.
+      return Nothing();
+    }).onAny([=]() {
+      os::close(out);
+    });
+}
+
+
+/**
+ * A Test fixture that sets up SSL keys and certificates.
+ *
+ * This class sets up a private key and certificate pair at
+ * SSLTest::key_path and SSLTest::certificate_path.
+ * It also sets up an independent 'scrap' pair that can be used to
+ * test an invalid certificate authority chain. These can be found at
+ * SSLTest::scrap_key_path and SSLTest::scrap_certificate_path.
+ *
+ * There are some helper functions like SSLTest::setup_server and
+ * SSLTest::launch_client that factor out common behavior used in
+ * tests.
+ */
+class SSLTest : public ::testing::Test
+{
+protected:
+  SSLTest() : data("Hello World!") {}
+
+  /**
+   * @return The path to the authorized private key.
+   */
+  static const Path& key_path()
+  {
+    static Path path(path::join(os::getcwd(), "key.pem"));
+    return path;
+  }
+
+  /**
+   * @return The path to the authorized certificate.
+   */
+  static const Path& certificate_path()
+  {
+    static Path path(path::join(os::getcwd(), "cert.pem"));
+    return path;
+  }
+
+  /**
+   * @return The path to the unauthorized private key.
+   */
+  static const Path& scrap_key_path()
+  {
+    static Path path(path::join(os::getcwd(), "scrap_key.pem"));
+    return path;
+  }
+
+  /**
+   * @return The path to the unauthorized certificate.
+   */
+  static const Path& scrap_certificate_path()
+  {
+    static Path path(path::join(os::getcwd(), "scrap_cert.pem"));
+    return path;
+  }
+
+  static void SetUpTestCase()
+  {
+    // We store the allocated objects in these results so that we can
+    // have a consolidated 'cleanup()' function. This makes all the
+    // 'EXIT()' calls more readable and less error prone.
+    Result<EVP_PKEY*> private_key = None();
+    Result<X509*> certificate = None();
+    Result<EVP_PKEY*> scrap_key = None();
+    Result<X509*> scrap_certificate = None();
+
+    auto cleanup = [&private_key, &certificate, &scrap_key, &scrap_certificate](
+        bool failure = true) {
+      if (private_key.isSome()) { EVP_PKEY_free(private_key.get()); }
+      if (certificate.isSome()) { X509_free(certificate.get()); }
+      if (scrap_key.isSome()) { EVP_PKEY_free(scrap_key.get()); }
+      if (scrap_certificate.isSome()) { X509_free(scrap_certificate.get()); }
+
+      // If we are under a failure condition, clean up any files we
+      // already generated. The expected behavior is that they will be
+      // cleaned up in 'TearDownTestCase()'; however, we call ABORT
+      // during 'SetUpTestCase()' failures.
+      if (failure) {
+        os::rm(key_path().value);
+        os::rm(certificate_path().value);
+        os::rm(scrap_key_path().value);
+        os::rm(scrap_certificate_path().value);
+      }
+    };
+
+    // Generate the authority key.
+    private_key = openssl::generate_private_rsa_key();
+    if (private_key.isError()) {
+      ABORT("Could not generate private key: " + private_key.error());
+    }
+
+    // Generate an authorized certificate.
+    certificate = openssl::generate_x509(private_key.get(), private_key.get());
+
+    if (certificate.isError()) {
+      cleanup();
+      ABORT("Could not generate certificate: " + certificate.error());
+    }
+
+    // Write the authority key to disk.
+    Try<Nothing> key_write =
+      openssl::write_key_file(private_key.get(), key_path());
+
+    if (key_write.isError()) {
+      cleanup();
+      ABORT("Could not write private key to disk: " + key_write.error());
+    }
+
+    // Write the authorized certificate to disk.
+    Try<Nothing> certificate_write =
+      openssl::write_certificate_file(certificate.get(), certificate_path());
+
+    if (certificate_write.isError()) {
+      cleanup();
+      ABORT("Could not write certificate to disk: " +
+            certificate_write.error());
+    }
+
+    // Generate a scrap key.
+    scrap_key = openssl::generate_private_rsa_key();
+    if (scrap_key.isError()) {
+      cleanup();
+      ABORT("Could not generate a scrap private key: " + scrap_key.error());
+    }
+
+    // Write the scrap key to disk.
+    key_write = openssl::write_key_file(scrap_key.get(), scrap_key_path());
+
+    if (key_write.isError()) {
+      cleanup();
+      ABORT("Could not write scrap key to disk: " + key_write.error());
+    }
+
+    // Generate a scrap certificate.
+    scrap_certificate =
+      openssl::generate_x509(scrap_key.get(), scrap_key.get());
+
+    if (scrap_certificate.isError()) {
+      cleanup();
+      ABORT("Could not generate a scrap certificate: " +
+            scrap_certificate.error());
+    }
+
+    // Write the scrap certificate to disk.
+    certificate_write = openssl::write_certificate_file(
+        scrap_certificate.get(),
+        scrap_certificate_path());
+
+    if (certificate_write.isError()) {
+      cleanup();
+      ABORT("Could not write scrap certificate to disk: " +
+            certificate_write.error());
+    }
+
+    // Since we successfully set up all our state, we call cleanup
+    // with failure set to 'false'.
+    cleanup(false);
+  }
+
+  static void TearDownTestCase()
+  {
+    // Clean up all the pem files we generated.
+    os::rm(key_path().value);
+    os::rm(certificate_path().value);
+    os::rm(scrap_key_path().value);
+    os::rm(scrap_certificate_path().value);
+  }
+
+  virtual void SetUp()
+  {
+    // This unsets all the SSL environment variables. Necessary for
+    // ensuring a clean starting slate between tests.
+    os::unsetenv("SSL_ENABLED");
+    os::unsetenv("SSL_CERT_FILE");
+    os::unsetenv("SSL_KEY_FILE");
+    os::unsetenv("SSL_VERIFY_CERT");
+    os::unsetenv("SSL_REQUIRE_CERT");
+    os::unsetenv("SSL_VERIFY_DEPTH");
+    os::unsetenv("SSL_CA_DIR");
+    os::unsetenv("SSL_CA_FILE");
+    os::unsetenv("SSL_CIPHERS");
+    os::unsetenv("SSL_ENABLE_SSL_V2");
+    os::unsetenv("SSL_ENABLE_SSL_V3");
+    os::unsetenv("SSL_ENABLE_TLS_V1_0");
+    os::unsetenv("SSL_ENABLE_TLS_V1_1");
+    os::unsetenv("SSL_ENABLE_TLS_V1_2");
+  }
+
+  /**
+   * Initializes a listening server.
+   *
+   * @param environment The SSL environment variables to launch the
+   *     server socket with.
+   *
+   * @return Socket if successful otherwise an Error.
+   */
+  Try<Socket> setup_server(const map<string, string>& environment)
+  {
+    foreachpair (const string& name, const string& value, environment) {
+      os::setenv(name, value);
+    }
+    openssl::reinitialize();
+
+    const Try<Socket> create = Socket::create(Socket::SSL);
+    if (create.isError()) {
+      return Error(create.error());
+    }
+
+    Socket server = create.get();
+
+    const Try<Nothing> listen = server.listen(BACKLOG);
+    if (listen.isError()) {
+      return Error(listen.error());
+    }
+
+    return server;
+  }
+
+  /**
+   * Launches a test SSL client as a subprocess connecting to the
+   * server.
+   *
+   * The subprocess calls the 'ssl-client' binary with the provided
+   * environment.
+   *
+   * @param environment The SSL environment variables to launch the
+   *     SSL client subprocess with.
+   * @param use_ssl_socket Whether the SSL client will try to connect
+   *     using an SSL socket or a POLL socket.
+   *
+   * @return Subprocess if successful otherwise an Error.
+   */
+  Try<Subprocess> launch_client(
+      const map<string, string>& environment,
+      const Socket& server,
+      bool use_ssl_socket)
+  {
+    const Try<Address> address = server.address();
+    if (address.isError()) {
+      return Error(address.error());
+    }
+
+    // Set up arguments to be passed to the 'client-ssl' binary.
+    const vector<string> argv = {
+      "ssl-client",
+      "--use_ssl=" + stringify(use_ssl_socket),
+      "--server=127.0.0.1",
+      "--port=" + stringify(address.get().port),
+      "--data=" + data};
+
+    Result<string> path = os::realpath(BUILD_DIR);
+    if (!path.isSome()) {
+      return Error("Could not establish build directory path");
+    }
+
+    return subprocess(
+        path::join(path.get(), "ssl-client"),
+        argv,
+        Subprocess::PIPE(),
+        Subprocess::PIPE(),
+        Subprocess::FD(STDERR_FILENO),
+        None(),
+        environment);
+  }
+
+  static constexpr size_t BACKLOG = 5;
+
+  const string data;
+};
+
+
+// Ensure that we can't create an SSL socket when SSL is not enabled.
+TEST(SSL, Disabled)
+{
+  os::setenv("SSL_ENABLED", "false");
+  openssl::reinitialize();
+  EXPECT_ERROR(Socket::create(Socket::SSL));
+}
+
+
+// Test a basic back-and-forth communication within the same OS
+// process.
+TEST_F(SSLTest, BasicSameProcess)
+{
+  os::setenv("SSL_ENABLED", "true");
+  os::setenv("SSL_KEY_FILE", key_path().value);
+  os::setenv("SSL_CERT_FILE", certificate_path().value);
+  os::setenv("SSL_REQUIRE_CERT", "true");
+  os::setenv("SSL_CA_DIR", os::getcwd());
+  os::setenv("SSL_CA_FILE", certificate_path().value);
+
+  openssl::reinitialize();
+
+  const Try<Socket> server_create = Socket::create(Socket::SSL);
+  ASSERT_SOME(server_create);
+
+  const Try<Socket> client_create = Socket::create(Socket::SSL);
+  ASSERT_SOME(client_create);
+
+  Socket server = server_create.get();
+  Socket client = client_create.get();
+
+  const Try<Nothing> listen = server.listen(BACKLOG);
+  ASSERT_SOME(listen);
+
+  const Try<Address> server_address = server.address();
+  ASSERT_SOME(server_address);
+
+  const Future<Socket> _socket = server.accept();
+
+  const Future<Nothing> connect = client.connect(server_address.get());
+
+  // Wait for the server to have accepted the client connection.
+  AWAIT_ASSERT_READY(_socket);
+  Socket socket = _socket.get(); // TODO(jmlvanre): Remove const copy.
+
+  // Verify that the client also views the connection as established.
+  AWAIT_ASSERT_READY(connect);
+
+  // Send a message from the client to the server.
+  const string data = "Hello World!";
+  AWAIT_ASSERT_READY(client.send(data));
+
+  // Verify the server received the message.
+  AWAIT_ASSERT_EQ(data, socket.recv());
+
+  // Send the message back from the server to the client.
+  AWAIT_ASSERT_READY(socket.send(data));
+
+  // Verify the client received the message.
+  AWAIT_ASSERT_EQ(data, client.recv());
+}
+
+
+// Test a basic back-and-forth communication using the 'ssl-client'
+// subprocess.
+TEST_F(SSLTest, SSLSocket)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}},
+      server.get(),
+      true);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  // TODO(jmlvanre): Remove const copy.
+  AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv());
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(data));
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), 0));
+}
+
+
+// Ensure that a POLL based socket can't connect to an SSL based
+// socket, even when SSL is enabled.
+TEST_F(SSLTest, NonSSLSocket)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}},
+      server.get(),
+      false);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_FAILED(socket);
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
+}
+
+
+// Ensure that a certificate that was not generated using the
+// certificate authority is still allowed to communicate as long as
+// the SSL_VERIFY_CERT and SSL_REQUIRE_CERT flags are disabled.
+TEST_F(SSLTest, NoVerifyBadCA)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_VERIFY_CERT", "false"},
+      {"SSL_REQUIRE_CERT", "false"}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", scrap_key_path().value},
+      {"SSL_CERT_FILE", scrap_certificate_path().value},
+      {"SSL_REQUIRE_CERT", "true"},
+      {"SSL_CA_FILE", certificate_path().value}},
+      server.get(),
+      true);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  // TODO(jmlvanre): Remove const copy.
+  AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv());
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(data));
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), 0));
+}
+
+
+// Ensure that a certificate that was not generated using the
+// certificate authority is NOT allowed to communicate when the
+// SSL_REQUIRE_CERT flag is enabled.
+TEST_F(SSLTest, RequireBadCA)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_REQUIRE_CERT", "true"}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", scrap_key_path().value},
+      {"SSL_CERT_FILE", scrap_certificate_path().value},
+      {"SSL_REQUIRE_CERT", "false"}},
+      server.get(),
+      true);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_FAILED(socket);
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
+}
+
+
+// Ensure that a certificate that was not generated using the
+// certificate authority is NOT allowed to communicate when the
+// SSL_VERIFY_CERT flag is enabled.
+TEST_F(SSLTest, VerifyBadCA)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_VERIFY_CERT", "true"}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", scrap_key_path().value},
+      {"SSL_CERT_FILE", scrap_certificate_path().value},
+      {"SSL_REQUIRE_CERT", "false"}},
+      server.get(),
+      true);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_FAILED(socket);
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
+}
+
+
+// Ensure that a certificate that WAS generated using the certificate
+// authority is NOT allowed to communicate when the SSL_VERIFY_CERT
+// flag is enabled.
+TEST_F(SSLTest, VerifyCertificate)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_VERIFY_CERT", "true"}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_REQUIRE_CERT", "true"}},
+      server.get(),
+      true);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  // TODO(jmlvanre): Remove const copy.
+  AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv());
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(data));
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), 0));
+}
+
+
+// Ensure that a certificate that WAS generated using the certificate
+// authority is NOT allowed to communicate when the SSL_REQUIRE_CERT
+// flag is enabled.
+TEST_F(SSLTest, RequireCertificate)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_REQUIRE_CERT", "true"}});
+  ASSERT_SOME(server);
+
+  Try<Subprocess> client = launch_client({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value},
+      {"SSL_REQUIRE_CERT", "true"}},
+      server.get(),
+      true);
+  ASSERT_SOME(client);
+
+  Future<Socket> socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  // TODO(jmlvanre): Remove const copy.
+  AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv());
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(data));
+
+  AWAIT_ASSERT_READY(await_subprocess(client.get(), 0));
+}
+
+
+// Test all the combinations of protocols. Ensure that they can only
+// communicate if the opposing end allows the given protocol, and not
+// otherwise.
+TEST_F(SSLTest, ProtocolMismatch)
+{
+  const vector<string> protocols = {
+    // Openssl can be compiled with SSLV2 and/or SSLV3 disabled
+    // completely, so we conditionally test these protocol.
+#ifndef OPENSSL_NO_SSL2
+    "SSL_ENABLE_SSL_V2",
+#endif
+#ifndef OPENSSL_NO_SSL3
+    "SSL_ENABLE_SSL_V3",
+#endif
+    "SSL_ENABLE_TLS_V1_0",
+    "SSL_ENABLE_TLS_V1_1",
+    "SSL_ENABLE_TLS_V1_2"
+  };
+
+  // For each server protocol.
+  foreach (const string& server_protocol, protocols) {
+    // For each client protocol.
+    foreach (const string& client_protocol, protocols) {
+      LOG(INFO) << "Testing server protocol '" << server_protocol
+                << "' with client protocol '" << client_protocol << "'\n";
+
+      // Set up the default server environment variables.
+      map<string, string> server_environment = {
+        {"SSL_ENABLED", "true"},
+        {"SSL_KEY_FILE", key_path().value},
+        {"SSL_CERT_FILE", certificate_path().value}
+      };
+
+      // Set up the default client environment variables.
+      map<string, string> client_environment = {
+        {"SSL_ENABLED", "true"},
+        {"SSL_KEY_FILE", key_path().value},
+        {"SSL_CERT_FILE", certificate_path().value},
+      };
+
+      // Disable all protocols except for the one we're testing.
+      foreach (const string& protocol, protocols) {
+        server_environment.emplace(
+            protocol,
+            stringify(protocol == server_protocol));
+
+        client_environment.emplace(
+            protocol,
+            stringify(protocol == client_protocol));
+      }
+
+      // Set up the server.
+      Try<Socket> server = setup_server(server_environment);
+      ASSERT_SOME(server);
+
+      // Launch the client.
+      Try<Subprocess> client =
+        launch_client(client_environment, server.get(), true);
+      ASSERT_SOME(client);
+
+      if (server_protocol == client_protocol) {
+        // If the protocols are the same, it is valid.
+        Future<Socket> socket = server.get().accept();
+        AWAIT_ASSERT_READY(socket);
+
+        // TODO(jmlvanre): Remove const copy.
+        AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv());
+        AWAIT_ASSERT_READY(Socket(socket.get()).send(data));
+
+        AWAIT_ASSERT_READY(await_subprocess(client.get(), 0));
+      } else {
+        // If the protocols are NOT the same, it is invalid.
+        Future<Socket> socket = server.get().accept();
+        AWAIT_ASSERT_FAILED(socket);
+
+        AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
+      }
+    }
+  }
+}
+
+#endif // USE_SSL_SOCKET