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 #include <openssl/rsa.h>
24 #include <openssl/bio.h>
25 #include <openssl/x509.h>
26 #include <openssl/x509v3.h>
27 
28 #include <process/io.hpp>
29 #include <process/process.hpp>
30 #include <process/socket.hpp>
31 #include <process/subprocess.hpp>
32 
34 
35 #include <stout/none.hpp>
36 #include <stout/option.hpp>
37 #include <stout/try.hpp>
38 #include <stout/result.hpp>
39 
40 #include <stout/os/realpath.hpp>
41 #endif // USE_SSL_SOCKET
42 
43 #include <stout/tests/utils.hpp>
44 
45 #ifdef USE_SSL_SOCKET
46 namespace process {
47 namespace network {
48 namespace openssl {
49 
50 // Forward declare the `reinitialize()` function since we want to
51 // programatically change SSL flags during tests.
52 void reinitialize();
53 
54 } // namespace openssl {
55 } // namespace network {
56 } // namespace process {
57 #endif // USE_SSL_SOCKET
58 
59 // When SSL is not compiled in, we want the `SSLTemporaryDirectoryTest` class
60 // to exist, so that other tests can inherit it; this class is equivalent
61 // to the `TemporaryDirectoryTest` under that condition.
62 #ifndef USE_SSL_SOCKET
64 #else
65 
70 {
71 public:
72  static void TearDownTestCase()
73  {
74  // Clear and reset any environment variables.
75  set_environment_variables({});
76  }
77 
78 protected:
82  Path key_path()
83  {
84  return Path(path::join(os::getcwd(), "key.pem"));
85  }
86 
90  Path certificate_path()
91  {
92  return Path(path::join(os::getcwd(), "cert.pem"));
93  }
94 
98  Path scrap_key_path()
99  {
100  return Path(path::join(os::getcwd(), "scrap_key.pem"));
101  }
102 
106  Path scrap_certificate_path()
107  {
108  return Path(path::join(os::getcwd(), "scrap_cert.pem"));
109  }
110 
115  static void set_environment_variables(
116  const std::map<std::string, std::string>& environment)
117  {
118  // This unsets all the SSL environment variables. Necessary for
119  // ensuring a clean starting slate between tests.
120  os::unsetenv("LIBPROCESS_SSL_ENABLED");
121  os::unsetenv("LIBPROCESS_SSL_SUPPORT_DOWNGRADE");
122  os::unsetenv("LIBPROCESS_SSL_CERT_FILE");
123  os::unsetenv("LIBPROCESS_SSL_KEY_FILE");
124  os::unsetenv("LIBPROCESS_SSL_VERIFY_CERT");
125  os::unsetenv("LIBPROCESS_SSL_REQUIRE_CERT");
126  os::unsetenv("LIBPROCESS_SSL_VERIFY_DEPTH");
127  os::unsetenv("LIBPROCESS_SSL_CA_DIR");
128  os::unsetenv("LIBPROCESS_SSL_CA_FILE");
129  os::unsetenv("LIBPROCESS_SSL_CIPHERS");
130  os::unsetenv("LIBPROCESS_SSL_ENABLE_SSL_V3");
131  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_0");
132  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_1");
133  os::unsetenv("LIBPROCESS_SSL_ENABLE_TLS_V1_2");
134 
135  // Copy the given map into the clean slate.
136  foreachpair (
137  const std::string& name, const std::string& value, environment) {
138  os::setenv(name, value);
139  }
140 
141  // Make sure the library internally reflects the new environment variables.
142  process::network::openssl::reinitialize();
143  }
144 
152  void generate_keys_and_certs() {
153  // We store the allocated objects in these results so that we can
154  // have a consolidated 'cleanup()' function. This makes all the
155  // 'EXIT()' calls more readable and less error prone.
156  Result<EVP_PKEY*> private_key = None();
157  Result<X509*> certificate = None();
158  Result<EVP_PKEY*> scrap_key = None();
159  Result<X509*> scrap_certificate = None();
160 
161  auto cleanup = [&private_key, &certificate, &scrap_key, &scrap_certificate](
162  const Option<std::string> abort_message = None()) {
163  if (private_key.isSome()) { EVP_PKEY_free(private_key.get()); }
164  if (certificate.isSome()) { X509_free(certificate.get()); }
165  if (scrap_key.isSome()) { EVP_PKEY_free(scrap_key.get()); }
166  if (scrap_certificate.isSome()) { X509_free(scrap_certificate.get()); }
167 
168  // We abort here because failure during setup indicates that something
169  // is horribly and irrecoverably wrong.
170  if (abort_message.isSome()) {
171  ABORT(abort_message.get());
172  }
173  };
174 
175  // Generate the authority key.
176  private_key = process::network::openssl::generate_private_rsa_key();
177  if (private_key.isError()) {
178  cleanup("Could not generate private key: " + private_key.error());
179  }
180 
181  // Figure out the hostname that libprocess is advertising.
182  // Set the hostname of the certificate to this hostname so that
183  // hostname verification of the certificate will pass.
185  if (hostname.isError()) {
186  cleanup("Could not determine hostname of libprocess: " +
187  hostname.error());
188  }
189 
190  // Generate an authorized certificate.
191  certificate = process::network::openssl::generate_x509(
192  private_key.get(),
193  private_key.get(),
194  None(),
195  1,
196  365,
197  hostname.get(),
198  net::IP(process::address().ip));
199 
200  if (certificate.isError()) {
201  cleanup("Could not generate certificate: " + certificate.error());
202  }
203 
204  // Write the authority key to disk.
205  Try<Nothing> key_write =
206  process::network::openssl::write_key_file(private_key.get(), key_path());
207 
208  if (key_write.isError()) {
209  cleanup("Could not write private key to disk: " + key_write.error());
210  }
211 
212  // Write the authorized certificate to disk.
213  Try<Nothing> certificate_write =
214  process::network::openssl::write_certificate_file(
215  certificate.get(),
216  certificate_path());
217 
218  if (certificate_write.isError()) {
219  cleanup("Could not write certificate to disk: " +
220  certificate_write.error());
221  }
222 
223  // Generate a scrap key.
224  scrap_key = process::network::openssl::generate_private_rsa_key();
225  if (scrap_key.isError()) {
226  cleanup("Could not generate a scrap private key: " + scrap_key.error());
227  }
228 
229  // Write the scrap key to disk.
230  key_write = process::network::openssl::write_key_file(
231  scrap_key.get(),
232  scrap_key_path());
233 
234  if (key_write.isError()) {
235  cleanup("Could not write scrap key to disk: " + key_write.error());
236  }
237 
238  // Generate a scrap certificate.
239  scrap_certificate = process::network::openssl::generate_x509(
240  scrap_key.get(),
241  scrap_key.get());
242 
243  if (scrap_certificate.isError()) {
244  cleanup("Could not generate a scrap certificate: " +
245  scrap_certificate.error());
246  }
247 
248  // Write the scrap certificate to disk.
249  certificate_write = process::network::openssl::write_certificate_file(
250  scrap_certificate.get(),
251  scrap_certificate_path());
252 
253  if (certificate_write.isError()) {
254  cleanup("Could not write scrap certificate to disk: " +
255  certificate_write.error());
256  }
257 
258  // Since we successfully set up all our state, we call cleanup
259  // without an abort message (so as not to abort).
260  cleanup();
261  }
262 };
263 
264 
272 class SSLTest : public SSLTemporaryDirectoryTest,
273  public ::testing::WithParamInterface<const char*>
274 {
275 protected:
276  SSLTest() : data("Hello World!") {}
277 
278  virtual void SetUp()
279  {
281  generate_keys_and_certs();
282  }
283 
293  const std::map<std::string, std::string>& environment)
294  {
295  set_environment_variables(environment);
296 
299  process::network::internal::SocketImpl::Kind::SSL);
300 
301  if (create.isError()) {
302  return Error(create.error());
303  }
304 
305  process::network::inet::Socket server = create.get();
306 
307  // We need to explicitly bind to the address advertised by libprocess so the
308  // certificate we create in this test fixture can be verified.
310  server.bind(
312 
313  if (bind.isError()) {
314  return Error(bind.error());
315  }
316 
317  const Try<Nothing> listen = server.listen(BACKLOG);
318  if (listen.isError()) {
319  return Error(listen.error());
320  }
321 
322  return server;
323  }
324 
339  Try<process::Subprocess> launch_client(
340  const std::map<std::string, std::string>& environment,
341  const process::network::inet::Socket& server,
342  bool use_ssl_socket)
343  {
345  if (address.isError()) {
346  return Error(address.error());
347  }
348 
349  // Set up arguments to be passed to the 'client-ssl' binary.
350  const std::vector<std::string> argv = {
351  "ssl-client",
352  "--use_ssl=" + stringify(use_ssl_socket),
353  "--server=" + stringify(address->ip),
354  "--port=" + stringify(address->port),
355  "--data=" + data};
356 
358  if (!path.isSome()) {
359  return Error("Could not establish build directory path");
360  }
361 
362  // Explicitly set `LIBPROCESS_IP` in the subprocess to the same IP that was
363  // used to generate the hostname for SSL certificates. This ensures that
364  // certificate verification can succeed.
365  std::map<std::string, std::string> full_environment(environment);
366  full_environment["LIBPROCESS_IP"] = stringify(process::address().ip);
367 
368  return process::subprocess(
369  path::join(path.get(), "ssl-client"),
370  argv,
374  nullptr,
375  full_environment);
376  }
377 
378  static constexpr size_t BACKLOG = 5;
379 
380  const std::string data;
381 };
382 
383 #endif // USE_SSL_SOCKET
384 
385 #endif // __PROCESS_SSL_TEST_HPP__
Definition: path.hpp:26
Definition: errorbase.hpp:36
#define ABORT(...)
Definition: abort.hpp:40
T & get()&
Definition: try.hpp:73
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:53
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:258
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:56
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())
Definition: utils.hpp:33
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)
Represents a POSIX or Windows file system path and offers common path manipulations.
Definition: path.hpp:145
uint16_t port
Definition: address.hpp:134
#define foreachpair(KEY, VALUE, ELEMS)
Definition: foreach.hpp:51
const T & get() const
Definition: result.hpp:115
Try< std::string > hostname()
Definition: net.hpp:154
static Try error(const E &e)
Definition: try.hpp:42
Definition: address.hpp:51
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:71
Definition: executor.hpp:48
network::inet::Address address()
Returns the socket address associated with this instance of the library.
Definition: gtest.hpp:63
virtual void SetUp()
Definition: utils.hpp:36
bool isSome() const
Definition: result.hpp:111
bool isError() const
Definition: result.hpp:113
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:133
Try< AddressType > address() const
Definition: socket.hpp:322
constexpr const char * name
Definition: shell.hpp:43