blob: c90d1da626fabd97cb77002a09a045c0f5356efb [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
#include <stdio.h>
#include <map>
#include <string>
#include <vector>
#include <process/clock.hpp>
#include <process/future.hpp>
#include <process/gtest.hpp>
#include <process/http.hpp>
#include <process/io.hpp>
#include <process/network.hpp>
#include <process/socket.hpp>
#include <process/subprocess.hpp>
#include <process/ssl/gtest.hpp>
#include <process/ssl/utilities.hpp>
#include <stout/foreach.hpp>
#include <stout/gtest.hpp>
#include <stout/nothing.hpp>
#include <stout/option.hpp>
#include <stout/os.hpp>
#include <stout/try.hpp>
#include "openssl.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
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
#define HOSTNAME_VALIDATION_OPENSSL
#endif
namespace http = process::http;
namespace io = process::io;
namespace network = process::network;
namespace openssl = network::openssl;
#ifndef __WINDOWS__
namespace unix = process::network::unix;
#endif // __WINDOWS__
using network::inet::Address;
using network::inet::Socket;
using network::internal::SocketImpl;
using process::Clock;
using process::Failure;
using process::Future;
using process::Subprocess;
// 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.
Try<int_fd> dup = os::dup(subprocess.out().get());
if (dup.isError()) {
return Failure(dup.error());
}
int_fd out = dup.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);
});
}
// The SSL protocols that we support through configuration flags.
static const vector<string> protocols = {
// OpenSSL can be compiled with SSLV3 disabled completely, so we
// conditionally test for this protocol.
#ifndef OPENSSL_NO_SSL3
"LIBPROCESS_SSL_ENABLE_SSL_V3",
#endif
"LIBPROCESS_SSL_ENABLE_TLS_V1_0",
"LIBPROCESS_SSL_ENABLE_TLS_V1_1",
"LIBPROCESS_SSL_ENABLE_TLS_V1_2",
// On some platforms, we need to build against OpenSSL versions that
// do not support TLS 1.3 yet.
#ifdef SSL_OP_NO_TLSv1_3
"LIBPROCESS_SSL_ENABLE_TLS_V1_3",
#endif
};
static const vector<string> hostname_validation_schemes = {
"legacy",
#ifdef HOSTNAME_VALIDATION_OPENSSL
"openssl",
#endif
};
// Ensure that we can't create an SSL socket when SSL is not enabled.
TEST(SSL, Disabled)
{
os::setenv("LIBPROCESS_SSL_ENABLED", "false");
openssl::reinitialize();
EXPECT_ERROR(Socket::create(SocketImpl::Kind::SSL));
}
// Test a basic back-and-forth communication using the 'ssl-client'
// subprocess.
TEST_F(SSLTest, SSLSocket)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}},
server.get(),
true);
ASSERT_SOME(client);
Future<Socket> socket = server->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({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}},
server.get(),
false);
ASSERT_SOME(client);
Future<Socket> socket = server->accept();
AWAIT_ASSERT_FAILED(socket);
AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
}
class SSLTestStringParameter
: public SSLTest,
public ::testing::WithParamInterface<std::string> {};
INSTANTIATE_TEST_CASE_P(HostnameValidationScheme,
SSLTestStringParameter,
::testing::ValuesIn(hostname_validation_schemes));
// Ensure that a certificate that was not generated using the
// certificate authority is still allowed to communicate as long as
// the LIBPROCESS_SSL_VERIFY_SERVER_CERT and LIBPROCESS_SSL_REQUIRE_CLIENT_CERT
// flags are disabled.
TEST_P(SSLTestStringParameter, NoVerifyBadCA)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "false"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", scrap_key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", scrap_certificate_path().string()},
{"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "false"},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}},
server.get(),
true);
ASSERT_SOME(client);
Future<Socket> socket = server->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 client certificate that was not generated using the
// certificate authority is NOT allowed to communicate when the
// LIBPROCESS_SSL_REQUIRE_CLIENT_CERT flag is enabled.
//
// NOTE: We cannot run this test with the 'legacy' hostname
// validation scheme due to MESOS-9867.
#ifdef HOSTNAME_VALIDATION_OPENSSL
TEST_F(SSLTest, RequireBadCA)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", scrap_key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", scrap_certificate_path().string()},
{"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "false"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}},
server.get(),
true);
ASSERT_SOME(client);
Future<Socket> socket = server->accept();
AWAIT_ASSERT_FAILED(socket);
AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
}
#endif // HOSTNAME_VALIDATION_OPENSSL
// Ensure that a server certificate that was not generated using the
// certificate authority is NOT allowed to communicate when the
// LIBPROCESS_SSL_VERIFY_SERVER_CERT flag is enabled.
TEST_P(SSLTestStringParameter, VerifyBadCA)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", scrap_key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", scrap_certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "false"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}});
ASSERT_SOME(server);
Try<std::string> hostname = net::getHostname(process::address().ip);
ASSERT_SOME(hostname);
Try<Address> address = server->address();
ASSERT_SOME(address);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}},
*hostname,
address->ip,
address->port,
true);
ASSERT_SOME(client);
Future<Socket> socket = server->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 allowed to communicate when the
// LIBPROCESS_SSL_VERIFY_SERVER_CERT and LIBPROCESS_SSL_REQUIRE_CLIENT_CERT
// flags are enabled.
//
// NOTE: If this test is failing for the 'legacy' scheme, subsequent
// tests may be affected due to MESOS-9867.
TEST_P(SSLTestStringParameter, VerifyCertificate)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}});
ASSERT_SOME(server);
Try<std::string> hostname = net::getHostname(process::address().ip);
ASSERT_SOME(hostname);
Try<Address> address = server->address();
ASSERT_SOME(address);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}},
*hostname,
address->ip,
address->port,
true);
ASSERT_SOME(client);
Future<Socket> socket = server->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 server presenting a valid certificate with a not matching
// hostname is NOT allowed to communicate when the
// LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME flag is set to 'openssl'.
#ifdef HOSTNAME_VALIDATION_OPENSSL
TEST_F(SSLTest, HostnameMismatch)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}});
ASSERT_SOME(server);
Try<Address> address = server->address();
ASSERT_SOME(address);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"},
{"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}},
"invalid.example.org",
address->ip,
address->port,
true);
ASSERT_SOME(client);
Future<Socket> socket = server->accept();
AWAIT_ASSERT_FAILED(socket);
AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
}
#endif // HOSTNAME_VALIDATION_OPENSSL
// Ensure that a server that attempts to present no certificate at all
// is NOT allowed to communicate when the LIBPROCESS_SSL_VERIFY_SERVER_CERT
// flag is enabled in the client.
TEST_F(SSLTest, NoAnonymousCipherIfVerify)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
// ADH stands for "Anonymous Diffie-Hellman", and is the only
// anonymous cipher supported by OpenSSL out of the box.
{"LIBPROCESS_SSL_CIPHERS", "ADH-AES256-SHA"}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"},
{"LIBPROCESS_SSL_CIPHERS", "ADH-AES256-SHA"}},
server.get(),
true);
ASSERT_SOME(client);
Future<Socket> socket = server->accept();
AWAIT_ASSERT_FAILED(socket);
AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
}
// Ensure that key exchange using ECDHE algorithm works.
TEST_F(SSLTest, ECDHESupport)
{
// Set up the default server environment variables.
map<string, string> server_environment = {
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CIPHERS",
"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA"}
};
// Set up the default client environment variables.
map<string, string> client_environment = {
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CIPHERS",
"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA"}
};
// 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);
Future<Socket> socket = server->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 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({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "false"}},
server.get(),
false);
ASSERT_SOME(client);
Future<Socket> socket = server->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 CANNOT 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({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "false"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "false"}},
server.get(),
false);
ASSERT_SOME(client);
Future<Socket> socket = server->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)
{
// 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 = {
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}
};
// 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({
{"LIBPROCESS_SSL_ENABLED", "false"}},
server.get(),
false);
ASSERT_SOME(client);
Future<Socket> socket = server->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 CANNOT communicate between a POLL
// based socket and an SSL socket if 'SSL_SUPPORT_DOWNGRADE' is not
// enabled.
TEST_F(SSLTest, NoValidDowngradeEachProtocol)
{
// 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 = {
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "false"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}
};
// 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({
{"LIBPROCESS_SSL_ENABLED", "false"}},
server.get(),
false);
ASSERT_SOME(client);
Future<Socket> socket = server->accept();
AWAIT_ASSERT_FAILED(socket);
AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
}
}
// Verify that the 'peer()' address call works correctly.
TEST_F(SSLTest, PeerAddress)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
const Try<Socket> client_create = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client_create);
Socket client = client_create.get();
Future<Socket> socket = server->accept();
const Try<Address> server_address = server->address();
ASSERT_SOME(server_address);
// Pass `None()` as hostname because this test is still
// using the 'legacy' hostname validation scheme.
const Future<Nothing> connect = client.connect(
server_address.get(),
openssl::create_tls_client_config(None()));
AWAIT_ASSERT_READY(socket);
AWAIT_ASSERT_READY(connect);
const Try<Address> socket_address = socket->address();
ASSERT_SOME(socket_address);
// Ensure the client thinks its peer is the server.
ASSERT_SOME_EQ(socket_address.get(), client.peer());
// Ensure the client has an address, and that the server thinks its
// peer is the client.
ASSERT_SOME(client.address());
ASSERT_SOME_EQ(client.address().get(), socket->peer());
}
// Basic Https GET test.
TEST_F(SSLTest, HTTPSGet)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
ASSERT_SOME(server->address());
Try<std::string> serverHostname = server->address()->lookup_hostname();
ASSERT_SOME(serverHostname);
Future<Socket> socket = server->accept();
// Create URL from server hostname and port.
const http::URL url(
"https", serverHostname.get(), server->address()->port);
// Send GET request.
Future<http::Response> response = http::get(url);
AWAIT_ASSERT_READY(socket);
// Construct response and send(server side).
const string buffer =
string("HTTP/1.1 200 OK\r\n") +
"Content-Length : " +
stringify(data.length()) + "\r\n" +
"\r\n" +
data;
AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer));
AWAIT_ASSERT_READY(response);
AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
ASSERT_EQ(data, response->body);
}
// Basic Https POST test.
TEST_F(SSLTest, HTTPSPost)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
ASSERT_SOME(server->address());
Try<std::string> serverHostname = server->address()->lookup_hostname();
ASSERT_SOME(serverHostname);
Future<Socket> socket = server->accept();
// Create URL from server hostname and port.
const http::URL url(
"https", serverHostname.get(), server->address()->port);
// Send POST request.
Future<http::Response> response =
http::post(url, None(), "payload", "text/plain");
AWAIT_ASSERT_READY(socket);
// Construct response and send(server side).
const string buffer =
string("HTTP/1.1 200 OK\r\n") +
"Content-Length : " +
stringify(data.length()) + "\r\n" +
"\r\n" +
data;
AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer));
AWAIT_ASSERT_READY(response);
AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
ASSERT_EQ(data, response->body);
}
// This test ensures that if an SSL connection never sends any
// data (i.e. no handshake or downgrade completes), it will not
// impact our ability to accept additional connections.
// This test was added due to MESOS-5340.
TEST_F(SSLTest, SilentSocket)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
ASSERT_SOME(server->address());
Try<std::string> serverHostname = server->address()->lookup_hostname();
ASSERT_SOME(serverHostname);
Future<Socket> socket = server->accept();
// We initiate a connection on which we will not send
// any data. This means the socket on the server will
// not complete the SSL handshake, nor be downgraded.
// As a result, we expect that the server will not see
// an accepted socket for this connection.
Try<Socket> connection = Socket::create(SocketImpl::Kind::POLL);
ASSERT_SOME(connection);
connection->connect(server->address().get());
// Note that settling libprocess is not sufficient
// for ensuring socket events are processed.
Clock::pause();
Clock::settle();
Clock::resume();
ASSERT_TRUE(socket.isPending());
// Now send an HTTP GET request, it should complete
// without getting blocked by the socket above
// undergoing the SSL handshake.
const http::URL url(
"https",
serverHostname.get(),
server->address()->port);
Future<http::Response> response = http::get(url);
AWAIT_READY(socket);
// Send the response from the server.
const string buffer = string() +
"HTTP/1.1 200 OK\r\n" +
"Content-Length: " + stringify(data.length()) + "\r\n" +
"\r\n" +
data;
AWAIT_READY(Socket(socket.get()).send(buffer));
AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
EXPECT_EQ(data, response->body);
}
// A copy of the SilentSocket test to ensure that the issue
// also is not present with downgrade support enabled. This
// was added due to MESOS-10114.
TEST_F(SSLTest, SilentSocketWithDowngrade)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
ASSERT_SOME(server->address());
Try<std::string> serverHostname = server->address()->lookup_hostname();
ASSERT_SOME(serverHostname);
Future<Socket> socket = server->accept();
// We initiate a connection on which we will not send
// any data. This means the socket on the server will
// not complete the SSL handshake, nor be downgraded.
// As a result, we expect that the server will not see
// an accepted socket for this connection.
Try<Socket> connection = Socket::create(SocketImpl::Kind::POLL);
ASSERT_SOME(connection);
connection->connect(server->address().get());
// Note that settling libprocess is not sufficient
// for ensuring socket events are processed.
Clock::pause();
Clock::settle();
Clock::resume();
ASSERT_TRUE(socket.isPending());
// Now send an HTTP GET request, it should complete
// without getting blocked by the socket above
// undergoing the SSL handshake.
const http::URL url(
"https",
serverHostname.get(),
server->address()->port);
Future<http::Response> response = http::get(url);
AWAIT_READY(socket);
// Send the response from the server.
const string buffer = string() +
"HTTP/1.1 200 OK\r\n" +
"Content-Length: " + stringify(data.length()) + "\r\n" +
"\r\n" +
data;
AWAIT_READY(Socket(socket.get()).send(buffer));
AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
EXPECT_EQ(data, response->body);
}
// This test was added due to an OOM issue: MESOS-7934.
TEST_F(SSLTest, ShutdownThenSend)
{
Clock::pause();
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}});
ASSERT_SOME(server);
ASSERT_SOME(server->address());
Future<Socket> socket = server->accept();
Clock::settle();
EXPECT_TRUE(socket.isPending());
Try<Socket> client = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client);
// Pass `None()` as hostname because this test is still
// using the 'legacy' hostname validation scheme.
AWAIT_ASSERT_READY(client->connect(
server->address().get(),
openssl::create_tls_client_config(None())));
AWAIT_ASSERT_READY(socket);
EXPECT_SOME(Socket(socket.get()).shutdown());
// This send should fail now that the socket is shut down.
AWAIT_FAILED(Socket(socket.get()).send("Hello World"));
}
#endif // USE_SSL_SOCKET
class SSLVerifyIPAddTest : public SSLTest,
public ::testing::WithParamInterface<const char*> {};
INSTANTIATE_TEST_CASE_P(SSLVerifyIPAdd,
SSLVerifyIPAddTest,
::testing::Values("false", "true"));
// Test a basic back-and-forth communication within the same OS
// process.
TEST_P(SSLVerifyIPAddTest, BasicSameProcess)
{
os::setenv("LIBPROCESS_SSL_ENABLED", "true");
os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string());
os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string());
os::setenv("LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true");
os::setenv("LIBPROCESS_SSL_CA_DIR", os::getcwd());
os::setenv("LIBPROCESS_SSL_CA_FILE", certificate_path().string());
os::setenv("LIBPROCESS_SSL_VERIFY_IPADD", GetParam());
os::setenv("LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "legacy");
openssl::reinitialize();
Try<Socket> server = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(server);
Try<Socket> client = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client);
// We need to explicitly bind to the address advertised by libprocess so the
// certificate we create in this test fixture can be verified.
ASSERT_SOME(server->bind(Address(net::IP(process::address().ip), 0)));
ASSERT_SOME(server->listen(BACKLOG));
Try<Address> address = server->address();
ASSERT_SOME(address);
Future<Socket> accept = server->accept();
// Pass `None()` as hostname because this test is still
// using the 'legacy' hostname validation scheme.
AWAIT_ASSERT_READY(client->connect(
address.get(),
openssl::create_tls_client_config(None())));
// Wait for the server to have accepted the client connection.
AWAIT_ASSERT_READY(accept);
Socket socket = accept.get();
// 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(data.size()));
// 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(data.size()));
}
#ifndef __WINDOWS__
TEST_P(SSLVerifyIPAddTest, BasicSameProcessUnix)
{
os::setenv("LIBPROCESS_SSL_ENABLED", "true");
os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string());
os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string());
// NOTE: we must set LIBPROCESS_SSL_REQUIRE_CLIENT_CERT to false because we
// don't have a hostname or IP to verify!
os::setenv("LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "false");
os::setenv("LIBPROCESS_SSL_CA_DIR", os::getcwd());
os::setenv("LIBPROCESS_SSL_CA_FILE", certificate_path().string());
os::setenv("LIBPROCESS_SSL_VERIFY_IPADD", GetParam());
openssl::reinitialize();
Try<unix::Socket> server = unix::Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(server);
Try<unix::Socket> client = unix::Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client);
// Use a path in the temporary directory so it gets cleaned up.
string path = path::join(sandbox.get(), "socket");
Try<unix::Address> address = unix::Address::create(path);
ASSERT_SOME(address);
ASSERT_SOME(server->bind(address.get()));
ASSERT_SOME(server->listen(BACKLOG));
Future<unix::Socket> accept = server->accept();
// Pass `None()` as hostname because this test is still
// using the 'legacy' hostname validation scheme.
AWAIT_ASSERT_READY(client->connect(
address.get(),
openssl::create_tls_client_config(None())));
// Wait for the server to have accepted the client connection.
AWAIT_ASSERT_READY(accept);
unix::Socket socket = accept.get();
// 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(data.size()));
// 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(data.size()));
}
#endif // __WINDOWS__
// Ensure that a certificate that WAS generated using the certificate
// authority IS allowed to communicate when the
// LIBPROCESS_SSL_REQUIRE_CLIENT_CERT flag is enabled.
TEST_P(SSLVerifyIPAddTest, RequireCertificate)
{
Try<Socket> server = setup_server({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"},
{"LIBPROCESS_SSL_VERIFY_IPADD", GetParam()}});
ASSERT_SOME(server);
Try<Subprocess> client = launch_client({
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_CA_FILE", certificate_path().string()},
{"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"},
{"LIBPROCESS_SSL_VERIFY_IPADD", GetParam()}},
server.get(),
true);
ASSERT_SOME(client);
Future<Socket> socket = server->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));
}
class SSLProtocolTest
: public SSLTest,
public ::testing::WithParamInterface<std::tuple<string, string>> {};
INSTANTIATE_TEST_CASE_P(
SSLProtocol,
SSLProtocolTest,
::testing::Combine(
::testing::ValuesIn(protocols), ::testing::ValuesIn(protocols)));
// Test all the combinations of protocols. Ensure that they can only
// communicate if the opposing end allows the given protocol, and not
// otherwise.
TEST_P(SSLProtocolTest, Mismatch)
{
const string& server_protocol = std::get<0>(GetParam());
const string& client_protocol = std::get<1>(GetParam());
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 = {
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}
};
// Set up the default client environment variables.
map<string, string> client_environment = {
{"LIBPROCESS_SSL_ENABLED", "true"},
{"LIBPROCESS_SSL_KEY_FILE", key_path().string()},
{"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()},
};
// 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->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->accept();
AWAIT_ASSERT_FAILED(socket);
// Pass `None()` as hostname because this test is still
// using the 'legacy' hostname validation scheme.
AWAIT_ASSERT_READY(await_subprocess(client.get(), None()));
}
}
// Verify that we can make a connection using a custom SSL context,
// and that the specified `verify` and `configure_socket` callbacks
// are called.
TEST_F(SSLTest, CustomSSLContext)
{
static bool verify_called;
static bool configure_socket_called;
os::setenv("LIBPROCESS_SSL_ENABLED", "true");
os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string());
os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string());
openssl::reinitialize();
verify_called = false;
configure_socket_called = false;
SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
openssl::TLSClientConfig config(
None(),
ctx,
[](SSL*, const network::Address&, const Option<std::string>&)
-> Try<Nothing>
{
configure_socket_called = true;
return Nothing();
},
[](const SSL* const, const Option<std::string>&, const Option<net::IP>&)
-> Try<Nothing>
{
verify_called = true;
return Nothing();
});
Try<Socket> client = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client);
Try<Socket> server = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(server);
server->listen(1);
Try<Address> address = server->address();
ASSERT_SOME(address);
Future<Socket> socket = server->accept();
Future<Nothing> connected = client->connect(*address, config);
AWAIT_READY(socket);
AWAIT_READY(connected);
EXPECT_TRUE(verify_called);
EXPECT_TRUE(configure_socket_called);
}
// Ensures that `connect()` fails if the passed
// `configure_socket` callback returns an error.
TEST_F(SSLTest, CustomSSLContextConfigureSocketFails)
{
os::setenv("LIBPROCESS_SSL_ENABLED", "true");
os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string());
os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string());
openssl::reinitialize();
SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
openssl::TLSClientConfig config(
None(),
ctx,
[](SSL*, const network::Address&, const Option<std::string>&)
-> Try<Nothing>
{
return Error("Configure socket.");
},
[](const SSL* const, const Option<std::string>&, const Option<net::IP>&)
-> Try<Nothing>
{
return Nothing();
});
Try<Socket> client = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client);
Try<Socket> server = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(server);
server->listen(1);
Try<Address> address = server->address();
ASSERT_SOME(address);
Future<Socket> socket = server->accept();
Future<Nothing> connected = client->connect(*address, config);
AWAIT_ASSERT_FAILED(connected);
}
// Ensures that `connect()` fails if the passed
// `verify` callback returns an error.
TEST_F(SSLTest, CustomSSLContextVerifyFails)
{
os::setenv("LIBPROCESS_SSL_ENABLED", "true");
os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string());
os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string());
openssl::reinitialize();
SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
openssl::TLSClientConfig config(
None(),
ctx,
[](SSL*, const network::Address&, const Option<std::string>&)
-> Try<Nothing>
{
return Nothing();
},
[](const SSL* const, const Option<std::string>&, const Option<net::IP>&)
-> Try<Nothing>
{
return Error("Verify failed.");
});
Try<Socket> client = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(client);
Try<Socket> server = Socket::create(SocketImpl::Kind::SSL);
ASSERT_SOME(server);
server->listen(1);
Try<Address> address = server->address();
ASSERT_SOME(address);
Future<Socket> socket = server->accept();
Future<Nothing> connected = client->connect(*address, config);
AWAIT_ASSERT_FAILED(connected);
}