Apache Mesos
gtest.hpp
Go to the documentation of this file.
1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements. See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership. The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #ifndef __PROCESS_SSL_TEST_HPP__
18 #define __PROCESS_SSL_TEST_HPP__
19 
20 #ifdef USE_SSL_SOCKET
21 #include <string>
22 
23 #ifdef __WINDOWS__
24 // NOTE: This must be included before the OpenSSL headers as it includes
25 // `WinSock2.h` and `Windows.h` in the correct order.
26 #include <stout/windows.hpp>
27 #endif // __WINDOWS__
28 
29 #include <openssl/rsa.h>
30 #include <openssl/bio.h>
31 #include <openssl/x509.h>
32 #include <openssl/x509v3.h>
33 
34 #include <process/io.hpp>
35 #include <process/process.hpp>
36 #include <process/socket.hpp>
37 #include <process/subprocess.hpp>
38 
40 
41 #include <stout/none.hpp>
42 #include <stout/option.hpp>
43 #include <stout/try.hpp>
44 #include <stout/result.hpp>
45 
46 #include <stout/os/realpath.hpp>
47 #endif // USE_SSL_SOCKET
48 
49 #include <stout/tests/utils.hpp>
50 
51 #ifdef USE_SSL_SOCKET
52 namespace process {
53 namespace network {
54 namespace openssl {
55 
56 // Forward declare the `reinitialize()` function since we want to
57 // programatically change SSL flags during tests.
58 void reinitialize();
59 
60 } // namespace openssl {
61 } // namespace network {
62 } // namespace process {
63 #endif // USE_SSL_SOCKET
64 
65 // When SSL is not compiled in, we want the `SSLTemporaryDirectoryTest` class
66 // to exist, so that other tests can inherit it; this class is equivalent
67 // to the `TemporaryDirectoryTest` under that condition.
68 #ifndef USE_SSL_SOCKET
70 #else
71 
76 {
77 public:
78  static void TearDownTestCase()
79  {
80  // Clear and reset any environment variables.
81  set_environment_variables({});
82  }
83 
84 protected:
88  Path key_path()
89  {
90  return Path(path::join(os::getcwd(), "key.pem"));
91  }
92 
96  Path certificate_path()
97  {
98  return Path(path::join(os::getcwd(), "cert.pem"));
99  }
100 
104  Path scrap_key_path()
105  {
106  return Path(path::join(os::getcwd(), "scrap_key.pem"));
107  }
108 
112  Path scrap_certificate_path()
113  {
114  return Path(path::join(os::getcwd(), "scrap_cert.pem"));
115  }
116 
121  static void set_environment_variables(
122  const std::map<std::string, std::string>& environment)
123  {
124  // This unsets all the SSL environment variables. Necessary for
125  // ensuring a clean starting slate between tests.
126  os::unsetenv("LIBPROCESS_SSL_ENABLED");
127  os::unsetenv("LIBPROCESS_SSL_SUPPORT_DOWNGRADE");
128  os::unsetenv("LIBPROCESS_SSL_CERT_FILE");
129  os::unsetenv("LIBPROCESS_SSL_KEY_FILE");
130  os::unsetenv("LIBPROCESS_SSL_VERIFY_CERT");
131  os::unsetenv("LIBPROCESS_SSL_VERIFY_SERVER_CERT");
132  os::unsetenv("LIBPROCESS_SSL_REQUIRE_CERT");
133  os::unsetenv("LIBPROCESS_SSL_REQUIRE_CLIENT_CERT");
134  os::unsetenv("LIBPROCESS_SSL_VERIFY_DEPTH");
135  os::unsetenv("LIBPROCESS_SSL_CA_DIR");
136  os::unsetenv("LIBPROCESS_SSL_CA_FILE");
137  os::unsetenv("LIBPROCESS_SSL_CIPHERS");
138  os::unsetenv("LIBPROCESS_SSL_ENABLE_SSL_V3");
139  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_0");
140  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_1");
141  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_2");
142  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_3");
143 
144  // Copy the given map into the clean slate.
145  foreachpair (
146  const std::string& name, const std::string& value, environment) {
147  os::setenv(name, value);
148  }
149 
150  // Make sure the library internally reflects the new environment variables.
151  process::network::openssl::reinitialize();
152  }
153 
161  void generate_keys_and_certs() {
162  // We store the allocated objects in these results so that we can
163  // have a consolidated 'cleanup()' function. This makes all the
164  // 'EXIT()' calls more readable and less error prone.
165  Result<EVP_PKEY*> private_key = None();
166  Result<X509*> certificate = None();
167  Result<EVP_PKEY*> scrap_key = None();
168  Result<X509*> scrap_certificate = None();
169 
170  auto cleanup = [&private_key, &certificate, &scrap_key, &scrap_certificate](
171  const Option<std::string> abort_message = None()) {
172  if (private_key.isSome()) { EVP_PKEY_free(private_key.get()); }
173  if (certificate.isSome()) { X509_free(certificate.get()); }
174  if (scrap_key.isSome()) { EVP_PKEY_free(scrap_key.get()); }
175  if (scrap_certificate.isSome()) { X509_free(scrap_certificate.get()); }
176 
177  // We abort here because failure during setup indicates that something
178  // is horribly and irrecoverably wrong.
179  if (abort_message.isSome()) {
180  ABORT(abort_message.get());
181  }
182  };
183 
184  // Generate the authority key.
185  private_key = process::network::openssl::generate_private_rsa_key();
186  if (private_key.isError()) {
187  cleanup("Could not generate private key: " + private_key.error());
188  }
189 
190  // Figure out the hostname that libprocess is advertising.
191  // Set the hostname of the certificate to this hostname so that
192  // hostname verification of the certificate will pass.
194  if (hostname.isError()) {
195  cleanup("Could not determine hostname of libprocess: " +
196  hostname.error());
197  }
198 
199  // Generate an authorized certificate.
200  certificate = process::network::openssl::generate_x509(
201  private_key.get(),
202  private_key.get(),
203  None(),
204  1,
205  365,
206  hostname.get(),
207  net::IP(process::address().ip));
208 
209  if (certificate.isError()) {
210  cleanup("Could not generate certificate: " + certificate.error());
211  }
212 
213  // Write the authority key to disk.
214  Try<Nothing> key_write =
215  process::network::openssl::write_key_file(private_key.get(), key_path());
216 
217  if (key_write.isError()) {
218  cleanup("Could not write private key to disk: " + key_write.error());
219  }
220 
221  // Write the authorized certificate to disk.
222  Try<Nothing> certificate_write =
223  process::network::openssl::write_certificate_file(
224  certificate.get(),
225  certificate_path());
226 
227  if (certificate_write.isError()) {
228  cleanup("Could not write certificate to disk: " +
229  certificate_write.error());
230  }
231 
232  // Generate a scrap key.
233  scrap_key = process::network::openssl::generate_private_rsa_key();
234  if (scrap_key.isError()) {
235  cleanup("Could not generate a scrap private key: " + scrap_key.error());
236  }
237 
238  // Write the scrap key to disk.
239  key_write = process::network::openssl::write_key_file(
240  scrap_key.get(),
241  scrap_key_path());
242 
243  if (key_write.isError()) {
244  cleanup("Could not write scrap key to disk: " + key_write.error());
245  }
246 
247  // Generate a scrap certificate.
248  scrap_certificate = process::network::openssl::generate_x509(
249  scrap_key.get(),
250  scrap_key.get());
251 
252  if (scrap_certificate.isError()) {
253  cleanup("Could not generate a scrap certificate: " +
254  scrap_certificate.error());
255  }
256 
257  // Write the scrap certificate to disk.
258  certificate_write = process::network::openssl::write_certificate_file(
259  scrap_certificate.get(),
260  scrap_certificate_path());
261 
262  if (certificate_write.isError()) {
263  cleanup("Could not write scrap certificate to disk: " +
264  certificate_write.error());
265  }
266 
267  // Since we successfully set up all our state, we call cleanup
268  // without an abort message (so as not to abort).
269  cleanup();
270  }
271 };
272 
273 
281 class SSLTest : public SSLTemporaryDirectoryTest
282 {
283 protected:
284  SSLTest() : data("Hello World!") {}
285 
286  void SetUp() override
287  {
289  generate_keys_and_certs();
290  }
291 
301  const std::map<std::string, std::string>& environment)
302  {
303  set_environment_variables(environment);
304 
307  process::network::internal::SocketImpl::Kind::SSL);
308 
309  if (create.isError()) {
310  return Error(create.error());
311  }
312 
313  process::network::inet::Socket server = create.get();
314 
315  // We need to explicitly bind to the address advertised by libprocess so the
316  // certificate we create in this test fixture can be verified.
318  server.bind(
320 
321  if (bind.isError()) {
322  return Error(bind.error());
323  }
324 
325  const Try<Nothing> listen = server.listen(BACKLOG);
326  if (listen.isError()) {
327  return Error(listen.error());
328  }
329 
330  return server;
331  }
332 
350  Try<process::Subprocess> launch_client(
351  const std::map<std::string, std::string>& environment,
353  const net::IP& ip,
354  uint16_t port,
355  bool use_ssl_socket)
356  {
357  // Set up arguments to be passed to the 'client-ssl' binary.
358  std::vector<std::string> argv = {
359  "ssl-client",
360  "--use_ssl=" + stringify(use_ssl_socket),
361  "--server=" + stringify(ip),
362  "--port=" + stringify(port),
363  "--data=" + data};
364 
365  if (hostname.isSome()) {
366  argv.push_back("--server_hostname=" + hostname.get());
367  }
368 
370  if (!path.isSome()) {
371  return Error("Could not establish build directory path");
372  }
373 
374  // Explicitly set `LIBPROCESS_IP` in the subprocess to the same IP that was
375  // used to generate the hostname for SSL certificates. This ensures that
376  // certificate verification can succeed.
377  std::map<std::string, std::string> full_environment(environment);
378  full_environment["LIBPROCESS_IP"] = stringify(process::address().ip);
379 
380  return process::subprocess(
381  path::join(path.get(), "ssl-client"),
382  argv,
386  nullptr,
387  full_environment);
388  }
389 
403  Try<process::Subprocess> launch_client(
404  const std::map<std::string, std::string>& environment,
405  const process::network::inet::Socket& server,
406  bool use_ssl_socket)
407  {
409  if (address.isError()) {
410  return Error(address.error());
411  }
412 
413  return launch_client(
414  environment,
415  None(),
416  address->ip,
417  address->port,
418  use_ssl_socket);
419  }
420 
421  static constexpr size_t BACKLOG = 5;
422 
423  const std::string data;
424 };
425 
426 #endif // USE_SSL_SOCKET
427 
428 #endif // __PROCESS_SSL_TEST_HPP__
Definition: path.hpp:29
Definition: errorbase.hpp:36
#define ABORT(...)
Definition: abort.hpp:40
T & get()&
Definition: try.hpp:80
Definition: check.hpp:33
Result< std::string > realpath(const std::string &path)
Definition: realpath.hpp:24
Try< Address > address(int_fd s)
Returns the Address with the assigned ip and assigned port.
Definition: network.hpp:79
static Result< T > error(const std::string &message)
Definition: result.hpp:54
process::Future< bool > cleanup(const std::string &hierarchy)
static Try< Socket > create(int_fd s, SocketImpl::Kind kind=SocketImpl::DEFAULT_KIND())
Returns an instance of a Socket using the specified kind of implementation.
Definition: socket.hpp:274
std::string getcwd()
Definition: getcwd.hpp:23
Try< std::string > getHostname(const IP &ip)
Definition: net.hpp:45
std::string join(const std::string &path1, const std::string &path2, const char _separator=os::PATH_SEPARATOR)
Definition: path.hpp:116
void setenv(const std::string &key, const std::string &value, bool overwrite=true)
Definition: os.hpp:157
#define STDERR_FILENO
Definition: windows.hpp:155
void unsetenv(const std::string &key)
Definition: os.hpp:167
Definition: check.hpp:30
constexpr char BACKLOG[]
Definition: statistics.hpp:29
process::Future< uint64_t > listen(const std::string &hierarchy, const std::string &cgroup, const std::string &control, const Option< std::string > &args=Option< std::string >::none())
bool isSome() const
Definition: option.hpp:116
Definition: utils.hpp:120
Definition: ip.hpp:73
Try< Subprocess > subprocess(const std::string &path, std::vector< std::string > argv, const Subprocess::IO &in=Subprocess::FD(STDIN_FILENO), const Subprocess::IO &out=Subprocess::FD(STDOUT_FILENO), const Subprocess::IO &err=Subprocess::FD(STDERR_FILENO), const flags::FlagsBase *flags=nullptr, const Option< std::map< std::string, std::string >> &environment=None(), const Option< lambda::function< pid_t(const lambda::function< int()> &)>> &clone=None(), const std::vector< Subprocess::ParentHook > &parent_hooks={}, const std::vector< Subprocess::ChildHook > &child_hooks={}, const std::vector< int_fd > &whitelist_fds={})
Forks a subprocess and execs the specified &#39;path&#39; with the specified &#39;argv&#39;, redirecting stdin...
Environment * environment
static IO FD(int_fd fd, IO::FDType type=IO::DUPLICATED)
void SetUp() override
Definition: utils.hpp:37
Represents a POSIX or Windows file system path and offers common path manipulations.
Definition: path.hpp:212
uint16_t port
Definition: address.hpp:141
const T & get() const &
Definition: option.hpp:119
#define foreachpair(KEY, VALUE, ELEMS)
Definition: foreach.hpp:51
Try< std::string > hostname()
Definition: net.hpp:154
static Try error(const E &e)
Definition: try.hpp:43
Definition: address.hpp:52
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:78
T & get()&
Definition: result.hpp:116
Definition: executor.hpp:48
network::inet::Address address()
Returns the socket address associated with this instance of the library.
Definition: gtest.hpp:69
bool isSome() const
Definition: result.hpp:112
bool isError() const
Definition: result.hpp:114
Try< Nothing > create(const std::string &hierarchy, const std::string &cgroup, bool recursive=false)
Try< Nothing > bind(int_fd s, const Address &address)
Definition: network.hpp:46
std::string stringify(int flags)
net::IP ip
Definition: address.hpp:140
Try< AddressType > address() const
Definition: socket.hpp:338
constexpr const char * name
Definition: shell.hpp:41