Repository: mesos Updated Branches: refs/heads/master a9574b461 -> 37674a667
Support for SSL and non-SSL traffic simultaneously. Add a flag SSL_SUPPORT_DOWNGRADE which allows: 1. An SSL accepting socket to peek at the incoming data. If the hello handshake bits are not set, then accept as a Socket::POLL socket instead. 2. When calling Process::link or Process:send(Message), if a new connection is required, allow a second attempt using Socket::POLL if an SSL connection was first attempted. Review: https://reviews.apache.org/r/31207 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/37674a66 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/37674a66 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/37674a66 Branch: refs/heads/master Commit: 37674a667a57880daddb3dc49fedd4930edcf6cd Parents: a9574b4 Author: Joris Van Remoortere <[email protected]> Authored: Tue Jun 30 06:37:04 2015 -0700 Committer: Benjamin Hindman <[email protected]> Committed: Tue Jun 30 06:47:32 2015 -0700 ---------------------------------------------------------------------- 3rdparty/libprocess/include/process/socket.hpp | 5 + 3rdparty/libprocess/src/libevent_ssl_socket.cpp | 89 ++++++- 3rdparty/libprocess/src/libevent_ssl_socket.hpp | 18 +- 3rdparty/libprocess/src/openssl.cpp | 14 ++ 3rdparty/libprocess/src/openssl.hpp | 2 + 3rdparty/libprocess/src/poll_socket.hpp | 2 + 3rdparty/libprocess/src/process.cpp | 245 +++++++++++++++++-- 3rdparty/libprocess/src/tests/ssl_tests.cpp | 171 +++++++++++++ 8 files changed, 519 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/include/process/socket.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/include/process/socket.hpp b/3rdparty/libprocess/include/process/socket.hpp index f53d2e1..d79d2e4 100644 --- a/3rdparty/libprocess/include/process/socket.hpp +++ b/3rdparty/libprocess/include/process/socket.hpp @@ -37,6 +37,9 @@ public: // Returns the default kind of implementation of Socket. static const Kind& DEFAULT_KIND(); + // The kind representing the underlying implementation. + Kind kind() const { return impl->kind(); } + // Each socket is a reference counted, shared by default, concurrent // object. However, since we want to support multiple // implementations we use the Pimpl pattern (often called the @@ -105,6 +108,8 @@ public: } } + virtual Socket::Kind kind() const = 0; + // Construct a new Socket from the given impl. This is a proxy // function, as Impls derived from this won't have access to the // Socket::Socket(...) constructors. http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/src/libevent_ssl_socket.cpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/libevent_ssl_socket.cpp b/3rdparty/libprocess/src/libevent_ssl_socket.cpp index 2920e0e..583526b 100644 --- a/3rdparty/libprocess/src/libevent_ssl_socket.cpp +++ b/3rdparty/libprocess/src/libevent_ssl_socket.cpp @@ -776,7 +776,73 @@ Future<Socket> LibeventSSLSocketImpl::accept() } -// Only runs in event loop. +void LibeventSSLSocketImpl::peek_callback( + evutil_socket_t fd, + short what, + void* arg) +{ + CHECK(__in_event_loop__); + + CHECK(what & EV_READ); + char data[6]; + + // Try to peek the first 6 bytes of the message. + ssize_t size = ::recv(fd, data, 6, MSG_PEEK); + + // Based on the function 'ssl23_get_client_hello' in openssl, we + // test whether to dispatch to the SSL or non-SSL based accept based + // on the following rules: + // 1. If there are fewer than 3 bytes: non-SSL. + // 2. If the 1st bit of the 1st byte is set AND the 3rd byte is + // equal to SSL2_MT_CLIENT_HELLO: SSL. + // 3. If the 1st byte is equal to SSL3_RT_HANDSHAKE AND the 2nd + // byte is equal to SSL3_VERSION_MAJOR and the 6th byte is + // equal to SSL3_MT_CLIENT_HELLO: SSL. + // 4. Otherwise: non-SSL. + + // For an ascii based protocol to falsely get dispatched to SSL it + // needs to: + // 1. Start with an invalid ascii character (0x80). + // 2. OR have the first 2 characters be a SYN followed by ETX, and + // then the 6th character be SOH. + // These conditions clearly do not constitute valid HTTP requests, + // and are unlikely to collide with other existing protocols. + + bool ssl = false; // Default to rule 4. + + if (size < 2) { // Rule 1. + ssl = false; + } else if ((data[0] & 0x80) && data[2] == SSL2_MT_CLIENT_HELLO) { // Rule 2. + ssl = true; + } else if (data[0] == SSL3_RT_HANDSHAKE && + data[1] == SSL3_VERSION_MAJOR && + data[5] == SSL3_MT_CLIENT_HELLO) { // Rule 3. + ssl = true; + } + + AcceptRequest* request = reinterpret_cast<AcceptRequest*>(arg); + + // We call 'event_free()' here because it ensures the event is made + // non-pending and inactive before it gets deallocated. + event_free(request->peek_event); + request->peek_event = NULL; + + if (ssl) { + accept_SSL_callback(request); + } else { + // Downgrade to a non-SSL socket. + Try<Socket> create = Socket::create(Socket::POLL, fd); + if (create.isError()) { + request->promise.fail(create.error()); + } else { + request->promise.set(create.get()); + } + + delete request; + } +} + + void LibeventSSLSocketImpl::accept_callback(AcceptRequest* request) { CHECK(__in_event_loop__); @@ -785,6 +851,27 @@ void LibeventSSLSocketImpl::accept_callback(AcceptRequest* request) // verify. accept_queue.put(request->promise.future()); + // If we support downgrading the connection, first wait for this + // socket to become readable. We will then MSG_PEEK it to test + // whether we want to dispatch as SSL or non-SSL. + if (openssl::flags().support_downgrade) { + request->peek_event = event_new( + base, + request->socket, + EV_READ, + &LibeventSSLSocketImpl::peek_callback, + request); + event_add(request->peek_event, NULL); + } else { + accept_SSL_callback(request); + } +} + + +void LibeventSSLSocketImpl::accept_SSL_callback(AcceptRequest* request) +{ + CHECK(__in_event_loop__); + // Set up SSL object. SSL* ssl = SSL_new(openssl::context()); if (ssl == NULL) { http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/src/libevent_ssl_socket.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/libevent_ssl_socket.hpp b/3rdparty/libprocess/src/libevent_ssl_socket.hpp index 4f2cd35..87c7835 100644 --- a/3rdparty/libprocess/src/libevent_ssl_socket.hpp +++ b/3rdparty/libprocess/src/libevent_ssl_socket.hpp @@ -57,9 +57,11 @@ private: int _socket, evconnlistener* _listener, const Option<net::IP>& _ip) - : listener(_listener), + : peek_event(NULL), + listener(_listener), socket(_socket), ip(_ip) {} + event* peek_event; Promise<Socket> promise; evconnlistener* listener; int socket; @@ -96,9 +98,21 @@ private: Option<std::string>&& peer_hostname); // This is called when the equivalent of 'accept' returns. The role - // of this function is to set up the SSL object and bev. + // of this function is to set up the SSL object and bev. If we + // support both SSL and non-SSL traffic simultaneously then we first + // wait for data to be ready and test the hello handshake to + // disambiguate between the kinds of traffic. void accept_callback(AcceptRequest* request); + // This is the continuation of 'accept_callback' that handles an SSL + // connection. + static void accept_SSL_callback(AcceptRequest* request); + + // This function peeks at the data on an accepted socket to see if + // there is an SSL handshake or not. It then dispatches to the + // SSL handling function or creates a non-SSL socket. + static void peek_callback(evutil_socket_t fd, short what, void* arg); + // The following are function pairs of static functions to member // functions. The static functions test and hold the weak pointer to // the socket before calling the member functions. This protects http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/src/openssl.cpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/openssl.cpp b/3rdparty/libprocess/src/openssl.cpp index 6ff4adb..8ffc409 100644 --- a/3rdparty/libprocess/src/openssl.cpp +++ b/3rdparty/libprocess/src/openssl.cpp @@ -41,6 +41,13 @@ Flags::Flags() "Whether SSL is enabled.", false); + add(&Flags::support_downgrade, + "support_downgrade", + "Enable downgrading SSL accepting sockets to non-SSL traffic. When this " + "is enabled, no protocol may be used on non-SSL connections that " + "conflics with the protocol headers for SSL.", + false); + add(&Flags::cert_file, "cert_file", "Path to certifcate."); @@ -323,6 +330,13 @@ void reinitialize() LOG(FATAL) << "Session id context size exceeds maximum"; } + // Notify users of the 'SSL_SUPPORT_DOWNGRADE' flag that this + // setting allows insecure connections. + if (ssl_flags->support_downgrade) { + LOG(WARNING) << + "Failed SSL connections will be downgraded to a non-SSL socket"; + } + // Now do some validation of the flags/environment variables. if (ssl_flags->key_file.isNone()) { EXIT(EXIT_FAILURE) << "SSL requires key! NOTE: Set path with SSL_KEY_FILE"; http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/src/openssl.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/openssl.hpp b/3rdparty/libprocess/src/openssl.hpp index 60c7b07..16f3d56 100644 --- a/3rdparty/libprocess/src/openssl.hpp +++ b/3rdparty/libprocess/src/openssl.hpp @@ -22,6 +22,7 @@ public: Flags(); bool enabled; + bool support_downgrade; Option<std::string> cert_file; Option<std::string> key_file; bool verify_cert; @@ -44,6 +45,7 @@ const Flags& flags(); // context gets initialized using the environment variables: // // SSL_ENABLED=(false|0,true|1) +// SSL_SUPPORT_DOWNGRADE=(false|0,true|1) // SSL_CERT_FILE=(path to certificate) // SSL_KEY_FILE=(path to key) // SSL_VERIFY_CERT=(false|0,true|1) http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/src/poll_socket.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/poll_socket.hpp b/3rdparty/libprocess/src/poll_socket.hpp index 553aa64..a14d63f 100644 --- a/3rdparty/libprocess/src/poll_socket.hpp +++ b/3rdparty/libprocess/src/poll_socket.hpp @@ -23,6 +23,8 @@ public: virtual Future<size_t> recv(char* data, size_t size); virtual Future<size_t> send(const char* data, size_t size); virtual Future<size_t> sendfile(int fd, off_t offset, size_t size); + + virtual Socket::Kind kind() const { return Socket::POLL; } }; } // namespace network { http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/3rdparty/libprocess/src/process.cpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/process.cpp b/3rdparty/libprocess/src/process.cpp index 52649fb..d99947c 100644 --- a/3rdparty/libprocess/src/process.cpp +++ b/3rdparty/libprocess/src/process.cpp @@ -85,6 +85,9 @@ #include "encoder.hpp" #include "event_loop.hpp" #include "gate.hpp" +#ifdef USE_SSL_SOCKET +#include "openssl.hpp" +#endif #include "process_reference.hpp" using namespace process::firewall; @@ -267,7 +270,9 @@ public: void accepted(const Socket& socket); - void link(ProcessBase* process, const UPID& to); + void link(ProcessBase* process, + const UPID& to, + const Socket::Kind& kind = Socket::DEFAULT_KIND()); PID<HttpProxy> proxy(const Socket& socket); @@ -275,7 +280,8 @@ public: void send(const Response& response, const Request& request, const Socket& socket); - void send(Message* message); + void send(Message* message, + const Socket::Kind& kind = Socket::DEFAULT_KIND()); Encoder* next(int s); @@ -299,6 +305,24 @@ private: hashmap<Address, hashset<UPID>> remotes; } links; + // Switch the underlying socket that a remote end is talking to. + // This manipulates the datastructures below by swapping all data + // mapped to 'from' to being mapped to 'to'. This is useful for + // downgrading a socket from SSL to POLL based. + void swap_implementing_socket(const Socket& from, Socket* to); + + // Helper function for link(). + void link_connect( + const Future<Nothing>& future, + Socket* socket, + const UPID& to); + + // Helper function for send(). + void send_connect( + const Future<Nothing>& future, + Socket* socket, + Message* message); + // Collection of all actice sockets. map<int, Socket*> sockets; @@ -757,6 +781,15 @@ void initialize(const string& delegate) signal(SIGPIPE, SIG_IGN); #endif // __sun__ +#ifdef USE_SSL_SOCKET + // Notify users of the 'SSL_SUPPORT_DOWNGRADE' flag that this + // setting allows insecure connections. + if (network::openssl::flags().support_downgrade) { + LOG(WARNING) << + "Failed SSL connections will be downgraded to a non-SSL socket"; + } +#endif + // Create a new ProcessManager and SocketManager. process_manager = new ProcessManager(delegate); socket_manager = new SocketManager(); @@ -1233,14 +1266,71 @@ void ignore_recv_data( void send(Encoder* encoder, Socket* socket); -void link_connect(const Future<Nothing>& future, Socket* socket) +} // namespace internal { + + +void SocketManager::link_connect( + const Future<Nothing>& future, + Socket* socket, + const UPID& to) { if (future.isDiscarded() || future.isFailed()) { if (future.isFailed()) { VLOG(1) << "Failed to link, connect: " << future.failure(); } + + // Check if SSL is enabled, and whether we allow a downgrade to + // non-SSL traffic. +#ifdef USE_SSL_SOCKET + bool attempt_downgrade = + future.isFailed() && + network::openssl::flags().enabled && + network::openssl::flags().support_downgrade && + socket->kind() == Socket::SSL; + + Option<Socket> poll_socket = None(); + + // If we allow downgrading from SSL to non-SSL, then retry as a + // POLL socket. + if (attempt_downgrade) { + synchronized (mutex) { + Try<Socket> create = Socket::create(Socket::POLL); + if (create.isError()) { + VLOG(1) << "Failed to link, create socket: " << create.error(); + socket_manager->close(*socket); + delete socket; + return; + } + + poll_socket = create.get(); + + // Update all the datastructures that are mapped to the socket + // that just failed to connect. They will now point to the new + // POLL socket we are about to try to connect. Even if the + // process has exited, persistent links will stay around, and + // temporary links will get cleaned up as they would otherwise. + swap_implementing_socket(*socket, new Socket(poll_socket.get())); + } + + CHECK_SOME(poll_socket); + poll_socket.get().connect(to.address) + .onAny(lambda::bind( + &SocketManager::link_connect, + this, + lambda::_1, + new Socket(poll_socket.get()), + to)); + + // We don't need to 'shutdown()' the socket as it was never + // connected. + delete socket; + return; + } +#endif + socket_manager->close(*socket); delete socket; + return; } @@ -1249,7 +1339,7 @@ void link_connect(const Future<Nothing>& future, Socket* socket) socket->recv(data, size) .onAny(lambda::bind( - &ignore_recv_data, + &internal::ignore_recv_data, lambda::_1, socket, data, @@ -1269,15 +1359,15 @@ void link_connect(const Future<Nothing>& future, Socket* socket) Encoder* encoder = socket_manager->next(*socket); if (encoder != NULL) { - send(encoder, new Socket(*socket)); + internal::send(encoder, new Socket(*socket)); } } -} // namespace internal { - - -void SocketManager::link(ProcessBase* process, const UPID& to) +void SocketManager::link( + ProcessBase* process, + const UPID& to, + const Socket::Kind& kind) { // TODO(benh): The semantics we want to support for link are such // that if there is nobody to link to (local or remote) then an @@ -1287,7 +1377,7 @@ void SocketManager::link(ProcessBase* process, const UPID& to) // work remotely ... but if there is someone listening remotely just // not at that id, then it will silently continue executing. - CHECK(process != NULL); + CHECK_NOTNULL(process); Option<Socket> socket = None(); bool connect = false; @@ -1296,7 +1386,10 @@ void SocketManager::link(ProcessBase* process, const UPID& to) // Check if the socket address is remote and there isn't a persistant link. if (to.address != __address__ && persists.count(to.address) == 0) { // Okay, no link, let's create a socket. - Try<Socket> create = Socket::create(); + // The kind of socket we create is passed in as an argument. + // This allows us to support downgrading the connection type + // from SSL to POLL if enabled. + Try<Socket> create = Socket::create(kind); if (create.isError()) { VLOG(1) << "Failed to link, create socket: " << create.error(); return; @@ -1330,9 +1423,11 @@ void SocketManager::link(ProcessBase* process, const UPID& to) CHECK_SOME(socket); Socket(socket.get()).connect(to.address) // Copy to drop const. .onAny(lambda::bind( - &internal::link_connect, + &SocketManager::link_connect, + this, lambda::_1, - new Socket(socket.get()))); + new Socket(socket.get()), + to)); } } @@ -1496,9 +1591,7 @@ void SocketManager::send( } -namespace internal { - -void send_connect( +void SocketManager::send_connect( const Future<Nothing>& future, Socket* socket, Message* message) @@ -1508,8 +1601,60 @@ void send_connect( VLOG(1) << "Failed to send '" << message->name << "' to '" << message->to.address << "', connect: " << future.failure(); } + + // Check if SSL is enabled, and whether we allow a downgrade to + // non-SSL traffic. +#ifdef USE_SSL_SOCKET + bool attempt_downgrade = + future.isFailed() && + network::openssl::flags().enabled && + network::openssl::flags().support_downgrade && + socket->kind() == Socket::SSL; + + Option<Socket> poll_socket = None(); + + // If we allow downgrading from SSL to non-SSL, then retry as a + // POLL socket. + if (attempt_downgrade) { + synchronized (mutex) { + Try<Socket> create = Socket::create(Socket::POLL); + if (create.isError()) { + VLOG(1) << "Failed to link, create socket: " << create.error(); + socket_manager->close(*socket); + delete message; + delete socket; + return; + } + + poll_socket = create.get(); + + // Update all the datastructures that are mapped to the socket + // that just failed to connect. They will now point to the new + // POLL socket we are about to try to connect. Even if the + // process has exited, persistent links will stay around, and + // temporary links will get cleaned up as they would otherwise. + swap_implementing_socket(*socket, new Socket(poll_socket.get())); + } + + CHECK_SOME(poll_socket); + poll_socket.get().connect(message->to.address) + .onAny(lambda::bind( + &SocketManager::send_connect, + this, + lambda::_1, + new Socket(poll_socket.get()), + message)); + + // We don't need to 'shutdown()' the socket as it was never + // connected. + delete socket; + return; + } +#endif + socket_manager->close(*socket); delete socket; + delete message; return; } @@ -1524,7 +1669,7 @@ void send_connect( socket->recv(data, size) .onAny(lambda::bind( - &ignore_recv_data, + &internal::ignore_recv_data, lambda::_1, new Socket(*socket), data, @@ -1533,10 +1678,8 @@ void send_connect( internal::send(encoder, socket); } -} // namespace internal { - -void SocketManager::send(Message* message) +void SocketManager::send(Message* message, const Socket::Kind& kind) { CHECK(message != NULL); @@ -1569,9 +1712,12 @@ void SocketManager::send(Message* message) } } else { - // No peristent or temporary socket to the socket address + // No persistent or temporary socket to the socket address // currently exists, so we create a temporary one. - Try<Socket> create = Socket::create(); + // The kind of socket we create is passed in as an argument. + // This allows us to support downgrading the connection type + // from SSL to POLL if enabled. + Try<Socket> create = Socket::create(kind); if (create.isError()) { VLOG(1) << "Failed to send, create socket: " << create.error(); delete message; @@ -1595,9 +1741,10 @@ void SocketManager::send(Message* message) if (connect) { CHECK_SOME(socket); - Socket(socket.get()).connect(address) // Copy to drop const. + socket.get().connect(address) .onAny(lambda::bind( - &internal::send_connect, + &SocketManager::send_connect, + this, lambda::_1, new Socket(socket.get()), message)); @@ -1882,6 +2029,56 @@ void SocketManager::exited(ProcessBase* process) } +void SocketManager::swap_implementing_socket(const Socket& from, Socket* to) +{ + const int from_fd = from.get(); + const int to_fd = to->get(); + + // Make sure the 'from' and 'to' are valid to swap. + CHECK(sockets.count(from_fd) > 0); + CHECK(sockets.count(to_fd) == 0); + + synchronized (mutex) { + sockets.erase(from_fd); + sockets[to_fd] = to; + + // Update the dispose set if this is a temporary link. + if (dispose.count(from_fd) > 0) { + dispose.insert(to_fd); + dispose.erase(from_fd); + } + + // Update the fd that this address is associated with. Once we've + // done this we can update the 'temps' and 'persists' + // datastructures using this updated address. + addresses[to_fd] = addresses[from_fd]; + addresses.erase(from_fd); + + // If this address is a temporary link. + if (temps.count(addresses[to_fd]) > 0) { + temps[addresses[to_fd]] = to_fd; + // No need to erase as we're changing the value, not the key. + } + + // If this address is a persistent link. + if (persists.count(addresses[to_fd]) > 0) { + persists[addresses[to_fd]] = to_fd; + // No need to erase as we're changing the value, not the key. + } + + // Move any encoders queued against this link to the new socket. + outgoing[to_fd] = std::move(outgoing[from_fd]); + outgoing.erase(from_fd); + + // Update the fd any proxies are associated with. + if (proxies.count(from_fd) > 0) { + proxies[to_fd] = proxies[from_fd]; + proxies.erase(from_fd); + } + } +} + + ProcessManager::ProcessManager(const string& _delegate) : delegate(_delegate) { http://git-wip-us.apache.org/repos/asf/mesos/blob/37674a66/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 index c077aae..e6917c1 100644 --- a/3rdparty/libprocess/src/tests/ssl_tests.cpp +++ b/3rdparty/libprocess/src/tests/ssl_tests.cpp @@ -705,4 +705,175 @@ TEST_F(SSLTest, ProtocolMismatch) } } + +// Ensure we can communicate between a POLL based socket and an SSL +// socket if 'SSL_SUPPORT_DOWNGRADE' is enabled. +TEST_F(SSLTest, ValidDowngrade) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_SUPPORT_DOWNGRADE", "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", "false"}}, + server.get(), + false); + 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 we can NOT communicate between a POLL based socket and an +// SSL socket if 'SSL_SUPPORT_DOWNGRADE' is not enabled. +TEST_F(SSLTest, NoValidDowngrade) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_SUPPORT_DOWNGRADE", "false"}, + {"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", "false"}}, + server.get(), + false); + ASSERT_SOME(client); + + Future<Socket> socket = server.get().accept(); + AWAIT_ASSERT_FAILED(socket); + + AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); +} + + +// For each protocol: ensure we can communicate between a POLL based +// socket and an SSL socket if 'SSL_SUPPORT_DOWNGRADE' is enabled. +TEST_F(SSLTest, ValidDowngradeEachProtocol) +{ + 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 protocol. + foreach (const string& server_protocol, protocols) { + LOG(INFO) << "Testing server protocol '" << server_protocol << "'\n"; + + // Set up the default server environment variables. + map<string, string> server_environment = { + {"SSL_ENABLED", "true"}, + {"SSL_SUPPORT_DOWNGRADE", "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)); + } + + // Set up the server. + Try<Socket> server = setup_server(server_environment); + ASSERT_SOME(server); + + // Launch the client with a POLL socket. + Try<Subprocess> client = launch_client({ + {"SSL_ENABLED", "false"}}, + server.get(), + false); + 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)); + } +} + + +// For each protocol: ensure we can NOT communicate between a POLL +// based socket and an SSL socket if 'SSL_SUPPORT_DOWNGRADE' is not +// enabled. +TEST_F(SSLTest, NoValidDowngradeEachProtocol) +{ + 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 protocol. + foreach (const string& server_protocol, protocols) { + LOG(INFO) << "Testing server protocol '" << server_protocol << "'\n"; + + // Set up the default server environment variables. + map<string, string> server_environment = { + {"SSL_ENABLED", "true"}, + {"SSL_SUPPORT_DOWNGRADE", "false"}, + {"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)); + } + + // Set up the server. + Try<Socket> server = setup_server(server_environment); + ASSERT_SOME(server); + + // Launch the client with a POLL socket. + Try<Subprocess> client = launch_client({ + {"SSL_ENABLED", "false"}}, + server.get(), + false); + ASSERT_SOME(client); + + Future<Socket> socket = server.get().accept(); + AWAIT_ASSERT_FAILED(socket); + + AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); + } +} + #endif // USE_SSL_SOCKET
