From 9282d96d00fad24b77930073a8292f8461d6b5d5 Mon Sep 17 00:00:00 2001 From: Roman Gershman Date: Fri, 19 Nov 2021 18:00:14 +0200 Subject: [PATCH] Add tls support and config flags simulating redis configuration params --- server/CMakeLists.txt | 4 +- server/config_flags.cc | 19 +++++++ server/config_flags.h | 92 ++++++++++++++++++++++++++++++++++ server/dragonfly_connection.cc | 24 ++++++--- server/dragonfly_connection.h | 11 ++-- server/dragonfly_listener.cc | 73 ++++++++++++++++++++++++++- server/dragonfly_listener.h | 3 ++ 7 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 server/config_flags.cc create mode 100644 server/config_flags.h diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index bff4ebff4..f2697b3c7 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,13 +1,13 @@ add_executable(dragonfly dfly_main.cc) cxx_link(dragonfly base dragonfly_lib) -add_library(dragonfly_lib command_registry.cc db_slice.cc dragonfly_listener.cc +add_library(dragonfly_lib command_registry.cc config_flags.cc db_slice.cc dragonfly_listener.cc dragonfly_connection.cc main_service.cc engine_shard_set.cc redis_parser.cc resp_expr.cc reply_builder.cc) cxx_link(dragonfly_lib uring_fiber_lib - fibers_ext strings_lib http_server_lib) + fibers_ext strings_lib http_server_lib tls_lib) add_library(dfly_test_lib test_utils.cc) cxx_link(dfly_test_lib dragonfly_lib gtest_main_ext) diff --git a/server/config_flags.cc b/server/config_flags.cc new file mode 100644 index 000000000..1b6d46a89 --- /dev/null +++ b/server/config_flags.cc @@ -0,0 +1,19 @@ +// Copyright 2021, Beeri 15. All rights reserved. +// Author: Roman Gershman (romange@gmail.com) +// +#include "server/config_flags.h" + +namespace dfly { + +bool ValidateConfigEnum(const char* nm, const std::string& val, const ConfigEnum* ptr, unsigned len, + int* dest) { + for (unsigned i = 0; i < len; ++i) { + if (val == ptr[i].first) { + *dest = ptr[i].second; + return true; + } + } + return false; +} + +} // namespace dfly diff --git a/server/config_flags.h b/server/config_flags.h new file mode 100644 index 000000000..02061a7f5 --- /dev/null +++ b/server/config_flags.h @@ -0,0 +1,92 @@ +// Copyright 2021, Beeri 15. All rights reserved. +// Author: Roman Gershman (romange@gmail.com) +// + +#pragma once + +#include +#include + +#include "base/flags.h" + +namespace dfly { + +// DashStr - replaces all underscores to dash characters and keeps the rest as is. +template class DashStr { + public: + DashStr(const char* s) { + memcpy(str_, s, N); + for (unsigned i = 0; i < N; ++i) { + if (str_[i] == '_') + str_[i] = '-'; + } + } + + const char* str() const { + return str_; + } + + private: + char str_[N]; +}; + +using ConfigEnum = std::pair; + +bool ValidateConfigEnum(const char* nm, const std::string& val, const ConfigEnum* ptr, unsigned len, + int* dest); + +} // namespace dfly + +inline bool TrueValidator(const char* nm, const std::string& val) { + return true; +} + +#define DEFINE_CONFIG_VAR(type, shorttype, name, value, help, validator) \ + namespace fL##shorttype { \ + type FLAGS_##name = value; \ + static type FLAGS_no##name = value; \ + static ::dfly::DashStr _dash_##name(#name); \ + static GFLAGS_NAMESPACE::FlagRegisterer o_##name( \ + _dash_##name.str(), MAYBE_STRIPPED_HELP(help), __FILE__, &FLAGS_##name, &FLAGS_no##name); \ + static const bool name##_val_reg = \ + GFLAGS_NAMESPACE::RegisterFlagValidator(&FLAGS_##name, validator); \ + } \ + using fL##shorttype::FLAGS_##name + +#define BIND_CONFIG(var) [](const char* nm, auto val) { \ + var = val; \ + return true;} + + +#define BIND_ENUM_CONFIG(enum_arr, dest_var) [](const char* nm, const std::string& val) { \ + return ::dfly::ValidateConfigEnum(nm, val, enum_arr, ABSL_ARRAYSIZE(enum_arr), \ + &(dest_var));} + +#define CONFIG_uint64(name,val, txt, validator) \ + DEFINE_CONFIG_VAR(GFLAGS_NAMESPACE::uint64, U64, name, val, txt, validator) + + +#define CONFIG_string(name, val, txt, validator) \ + namespace fLS { \ + using ::fLS::clstring; \ + using ::fLS::StringFlagDestructor; \ + static union { void* align; char s[sizeof(clstring)]; } s_##name[2]; \ + clstring* const FLAGS_no##name = ::fLS:: \ + dont_pass0toDEFINE_string(s_##name[0].s, \ + val); \ + static ::dfly::DashStr _dash_##name(#name); \ + static GFLAGS_NAMESPACE::FlagRegisterer o_##name( \ + _dash_##name.str(), MAYBE_STRIPPED_HELP(txt), __FILE__, \ + FLAGS_no##name, new (s_##name[1].s) clstring(*FLAGS_no##name)); \ + static StringFlagDestructor d_##name(s_##name[0].s, s_##name[1].s); \ + extern GFLAGS_DLL_DEFINE_FLAG clstring& FLAGS_##name; \ + using fLS::FLAGS_##name; \ + clstring& FLAGS_##name = *FLAGS_no##name; \ + static const bool name##_val_reg = \ + GFLAGS_NAMESPACE::RegisterFlagValidator(&FLAGS_##name, validator); \ + } \ + using fLS::FLAGS_##name + +#define CONFIG_enum(name, val, txt, enum_arr, dest_var) \ + CONFIG_string(name, val, txt, BIND_ENUM_CONFIG(enum_arr, dest_var)) + diff --git a/server/dragonfly_connection.cc b/server/dragonfly_connection.cc index 5ebc9225a..93cd3d817 100644 --- a/server/dragonfly_connection.cc +++ b/server/dragonfly_connection.cc @@ -8,11 +8,12 @@ #include "base/io_buf.h" #include "base/logging.h" +#include "server/command_registry.h" +#include "server/conn_context.h" #include "server/main_service.h" #include "server/redis_parser.h" -#include "server/conn_context.h" -#include "server/command_registry.h" #include "util/fiber_sched_algo.h" +#include "util/tls/tls_socket.h" using namespace util; using namespace std; @@ -68,8 +69,7 @@ struct Connection::Shutdown { } }; -Connection::Connection(Service* service) - : service_(service) { +Connection::Connection(Service* service, SSL_CTX* ctx) : service_(service), ctx_(ctx) { redis_parser_.reset(new RedisParser); } @@ -106,7 +106,19 @@ void Connection::HandleRequests() { int val = 1; CHECK_EQ(0, setsockopt(socket_->native_handle(), SOL_TCP, TCP_NODELAY, &val, sizeof(val))); - FiberSocketBase* peer = socket_.get(); + std::unique_ptr tls_sock; + if (ctx_) { + tls_sock.reset(new tls::TlsSocket(socket_.get())); + tls_sock->InitSSL(ctx_); + + FiberSocketBase::accept_result aresult = tls_sock->Accept(); + if (!aresult) { + LOG(WARNING) << "Error handshaking " << aresult.error().message(); + return; + } + VLOG(1) << "TLS handshake succeeded"; + } + FiberSocketBase* peer = tls_sock ? (FiberSocketBase*)tls_sock.get() : socket_.get(); cc_.reset(new ConnectionContext(peer, this)); cc_->shard_set = &service_->shard_set(); @@ -132,7 +144,7 @@ void Connection::InputLoop(FiberSocketBase* peer) { io_buf.CommitWrite(*recv_sz); status = ParseRedis(&io_buf); - if (status == NEED_MORE) { + if (status == NEED_MORE) { status = OK; } else if (status != OK) { break; diff --git a/server/dragonfly_connection.h b/server/dragonfly_connection.h index 7fff4af14..b4b9b8d07 100644 --- a/server/dragonfly_connection.h +++ b/server/dragonfly_connection.h @@ -8,15 +8,17 @@ #include "base/io_buf.h" +typedef struct ssl_ctx_st SSL_CTX; + namespace dfly { -class Service; -class RedisParser; class ConnectionContext; +class RedisParser; +class Service; class Connection : public util::Connection { public: - Connection(Service* service); + Connection(Service* service, SSL_CTX* ctx); ~Connection(); using error_code = std::error_code; @@ -39,9 +41,10 @@ class Connection : public util::Connection { ParserStatus ParseRedis(base::IoBuf* buf); std::unique_ptr redis_parser_; + Service* service_; + SSL_CTX* ctx_; std::unique_ptr cc_; - Service* service_; unsigned parser_error_ = 0; struct Shutdown; diff --git a/server/dragonfly_listener.cc b/server/dragonfly_listener.cc index 449c8258e..01f756883 100644 --- a/server/dragonfly_listener.cc +++ b/server/dragonfly_listener.cc @@ -4,25 +4,96 @@ #include "server/dragonfly_listener.h" +#include + #include "base/logging.h" +#include "server/config_flags.h" #include "server/dragonfly_connection.h" #include "util/proactor_pool.h" using namespace util; DEFINE_uint32(conn_threads, 0, "Number of threads used for handing server connections"); +DEFINE_bool(tls, false, ""); +CONFIG_string(tls_client_cert_file, "", "", TrueValidator); +CONFIG_string(tls_client_key_file, "", "", TrueValidator); + +enum TlsClientAuth { + CL_AUTH_NO = 0, + CL_AUTH_YES = 1, + CL_AUTH_OPTIONAL = 2, +}; + +dfly::ConfigEnum tls_auth_clients_enum[] = { + {"no", CL_AUTH_NO}, + {"yes", CL_AUTH_YES}, + {"optional", CL_AUTH_OPTIONAL}, +}; + +static int tls_auth_clients_opt = CL_AUTH_YES; + +CONFIG_enum(tls_auth_clients, "yes", "", tls_auth_clients_enum, tls_auth_clients_opt); namespace dfly { +// To connect: openssl s_client -cipher "ADH:@SECLEVEL=0" -state -crlf -connect 127.0.0.1:6380 +static SSL_CTX* CreateSslCntx() { + SSL_CTX* ctx = SSL_CTX_new(TLS_server_method()); + + if (FLAGS_tls_client_key_file.empty()) { + // To connect - use openssl s_client -cipher with either: + // "AECDH:@SECLEVEL=0" or "ADH:@SECLEVEL=0" setting. + CHECK_EQ(1, SSL_CTX_set_cipher_list(ctx, "aNULL")); + + // To allow anonymous ciphers. + SSL_CTX_set_security_level(ctx, 0); + + // you can still connect with redis-cli with : + // redis-cli --tls --insecure --tls-ciphers "ADH:@SECLEVEL=0" + LOG(WARNING) + << "tls-client-key-file not set, no keys are loaded and anonymous ciphers are enabled. " + << "Do not use in production!"; + } else { // tls_client_key_file is set. + CHECK_EQ(1, + SSL_CTX_use_PrivateKey_file(ctx, FLAGS_tls_client_key_file.c_str(), SSL_FILETYPE_PEM)); + + if (!FLAGS_tls_client_cert_file.empty()) { + // TO connect with redis-cli you need both tls-client-key-file and tls-client-cert-file + // loaded. Use `redis-cli --tls -p 6380 --insecure PING` to test + + CHECK_EQ(1, SSL_CTX_use_certificate_chain_file(ctx, FLAGS_tls_client_cert_file.c_str())); + } + CHECK_EQ(1, SSL_CTX_set_cipher_list(ctx, "DEFAULT")); + } + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + + SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); + + unsigned mask = SSL_VERIFY_NONE; + + // if (tls_auth_clients_opt) + // mask |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_CTX_set_verify(ctx, mask, NULL); + + CHECK_EQ(1, SSL_CTX_set_dh_auto(ctx, 1)); + + return ctx; +} + Listener::Listener(Service* e) : engine_(e) { + if (FLAGS_tls) { + OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL); + ctx_ = CreateSslCntx(); + } } Listener::~Listener() { + SSL_CTX_free(ctx_); } util::Connection* Listener::NewConnection(ProactorBase* proactor) { - return new Connection{engine_}; + return new Connection{engine_, ctx_}; } void Listener::PreShutdown() { diff --git a/server/dragonfly_listener.h b/server/dragonfly_listener.h index f1e909814..15aa5d5c3 100644 --- a/server/dragonfly_listener.h +++ b/server/dragonfly_listener.h @@ -6,6 +6,8 @@ #include "util/listener_interface.h" +typedef struct ssl_ctx_st SSL_CTX; + namespace dfly { class Service; @@ -26,6 +28,7 @@ class Listener : public util::ListenerInterface { Service* engine_; std::atomic_uint32_t next_id_{0}; + SSL_CTX* ctx_ = nullptr; }; } // namespace dfly