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