From 58b06f32a260d5844d55fce42d1dbed63a923599 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 23 Mar 2016 22:56:18 +0900 Subject: [PATCH] nghttpx: Configure TLS per backend routing pattern We added "tls" parameter to --backend option to enable TLS on that backend connection. --backend-tls options was deprecated, now is noop. --- src/shrpx.cc | 13 +-- src/shrpx_client_handler.cc | 3 +- src/shrpx_config.cc | 108 ++++++++++++++++-------- src/shrpx_config.h | 3 +- src/shrpx_http2_session.cc | 5 +- src/shrpx_http_downstream_connection.cc | 2 +- src/shrpx_ssl.cc | 7 +- src/shrpx_ssl.h | 4 +- src/shrpx_worker.cc | 4 +- src/shrpx_worker.h | 1 + 10 files changed, 99 insertions(+), 51 deletions(-) diff --git a/src/shrpx.cc b/src/shrpx.cc index 40298537..c30746d2 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1160,7 +1160,6 @@ void fill_default_config() { downstreamconf.request_buffer_size = 16_k; downstreamconf.response_buffer_size = 128_k; downstreamconf.family = AF_UNSPEC; - downstreamconf.no_tls = true; } } @@ -1194,7 +1193,8 @@ Options: The options are categorized into several groups. Connections: - -b, --backend=(,|unix:)[;[[:...]][;proto=]] + -b, --backend=(,|unix:)[;[[:...]] + [;proto=][;tls]] Set backend host and port. The multiple backend addresses are accepted by repeating this option. UNIX domain socket can be specified by prefixing path name @@ -1263,7 +1263,10 @@ Connections: quotes: "h2", "http/1.1". The default value of is "http/1.1". Note that usually "h2" refers to HTTP/2 over TLS. But in this option, it may mean HTTP/2 over - cleartext TCP unless --backend-tls is used. + cleartext TCP unless "tls" keyword is used (see below). + + Optionally, TLS can be enabled by specifying "tls" + keyword. TLS is not enabled by default. Since ";" and ":" are used as delimiter, must not contain these characters. Since ";" has special @@ -1303,8 +1306,6 @@ Connections: --backend-write-timeout options. --accept-proxy-protocol Accept PROXY protocol version 1 on frontend connection. - --backend-tls - Enable SSL/TLS on backend connections. Performance: -n, --workers= @@ -2132,7 +2133,7 @@ void process_options( } if (LOG_ENABLED(INFO)) { LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern - << "', proto=" << strproto(g.proto); + << "', proto=" << strproto(g.proto) << (g.tls ? ", tls" : ""); for (auto &addr : g.addrs) { LOG(INFO) << "group " << i << " -> " << addr.host.c_str() << (addr.host_unix ? "" : ":" + util::utos(addr.port)); diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 180dbb3a..63dbf104 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -750,7 +750,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) { } } auto session = make_unique( - conn_.loop, worker_->get_cl_ssl_ctx(), worker_, &group); + conn_.loop, shared_addr->tls ? worker_->get_cl_ssl_ctx() : nullptr, + worker_, &group); http2_freelist.append(session.release()); } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 985b8d4b..51573b23 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -568,6 +568,58 @@ int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) { } } // namespace +struct DownstreamParams { + shrpx_proto proto; + bool tls; +}; + +namespace { +// Parses downstream configuration parameter |src_params|, and stores +// parsed results into |out|. This function returns 0 if it succeeds, +// or -1. +int parse_downstream_params(DownstreamParams &out, + const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::istarts_with_l(param, "proto=")) { + auto protostr = StringRef{first + str_size("proto="), end}; + if (protostr.empty()) { + LOG(ERROR) << "backend: proto: protocol is empty"; + return -1; + } + + if (util::streq_l("h2", std::begin(protostr), protostr.size())) { + out.proto = PROTO_HTTP2; + } else if (util::streq_l("http/1.1", std::begin(protostr), + protostr.size())) { + out.proto = PROTO_HTTP1; + } else { + LOG(ERROR) << "backend: proto: unknown protocol " << protostr; + return -1; + } + } else if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (!param.empty()) { + LOG(ERROR) << "backend: " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + namespace { // Parses host-path mapping patterns in |src_pattern|, and stores // mappings in config. We will store each host-path pattern found in @@ -577,37 +629,18 @@ namespace { // // This function returns 0 if it succeeds, or -1. int parse_mapping(const DownstreamAddrConfig &addr, - const StringRef &src_pattern, const StringRef &src_proto) { + const StringRef &src_pattern, const StringRef &src_params) { // This returns at least 1 element (it could be empty string). We // will append '/' to all patterns, so it becomes catch-all pattern. auto mapping = util::split_str(src_pattern, ':'); assert(!mapping.empty()); auto &addr_groups = mod_config()->conn.downstream.addr_groups; - auto proto = PROTO_HTTP1; + DownstreamParams params{}; + params.proto = PROTO_HTTP1; - if (!src_proto.empty()) { - if (!util::istarts_with_l(src_proto, "proto=")) { - LOG(ERROR) << "backend: proto keyword not found"; - return -1; - } - - auto protostr = StringRef{std::begin(src_proto) + str_size("proto="), - std::end(src_proto)}; - if (protostr.empty()) { - LOG(ERROR) << "backend: protocol is empty"; - return -1; - } - - if (util::streq_l("h2", std::begin(protostr), protostr.size())) { - proto = PROTO_HTTP2; - } else if (util::streq_l("http/1.1", std::begin(protostr), - protostr.size())) { - proto = PROTO_HTTP1; - } else { - LOG(ERROR) << "backend: unknown protocol " << protostr; - return -1; - } + if (parse_downstream_params(params, src_params) != 0) { + return -1; } for (const auto &raw_pattern : mapping) { @@ -627,10 +660,18 @@ int parse_mapping(const DownstreamAddrConfig &addr, } for (auto &g : addr_groups) { if (g.pattern == pattern) { - if (g.proto != proto) { + if (g.proto != params.proto) { LOG(ERROR) << "backend: protocol mismatch. We saw protocol " << strproto(g.proto) << " for pattern " << g.pattern - << ", but another protocol " << strproto(proto); + << ", but another protocol " << strproto(params.proto); + return -1; + } + + if (g.tls != params.tls) { + LOG(ERROR) << "backend: TLS mismatch. We saw TLS was " + << (g.tls ? "enabled" : "disabled") << " for pattern " + << g.pattern << ", but we now got TLS was " + << (params.tls ? "enabled" : "disabled"); return -1; } @@ -644,7 +685,8 @@ int parse_mapping(const DownstreamAddrConfig &addr, } DownstreamAddrGroupConfig g(StringRef{pattern}); g.addrs.push_back(addr); - g.proto = proto; + g.proto = params.proto; + g.tls = params.tls; if (pattern[0] == '*') { // wildcard pattern @@ -1653,11 +1695,10 @@ int parse_config(const char *opt, const char *optarg, auto mapping = addr_end == std::end(src) ? addr_end : addr_end + 1; auto mapping_end = std::find(mapping, std::end(src), ';'); - auto proto = mapping_end == std::end(src) ? mapping_end : mapping_end + 1; - auto proto_end = std::find(proto, std::end(src), ';'); + auto params = mapping_end == std::end(src) ? mapping_end : mapping_end + 1; if (parse_mapping(addr, StringRef{mapping, mapping_end}, - StringRef{proto, proto_end}) != 0) { + StringRef{params, std::end(src)}) != 0) { return -1; } @@ -2453,12 +2494,9 @@ int parse_config(const char *opt, const char *optarg, return 0; case SHRPX_OPTID_BACKEND_HTTP1_TLS: - LOG(WARN) << opt << ": deprecated. Use " << SHRPX_OPT_BACKEND_TLS - << " instead."; - // fall through case SHRPX_OPTID_BACKEND_TLS: - mod_config()->conn.downstream.no_tls = !util::strieq(optarg, "yes"); - + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_BACKEND << " instead."; return 0; case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS: mod_config()->tls.session_cache.memcached.tls = util::strieq(optarg, "yes"); diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 0d0b6c8e..1f3bcc58 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -310,6 +310,7 @@ struct DownstreamAddrGroupConfig { std::vector addrs; // Application protocol used in this group shrpx_proto proto; + bool tls; }; struct TicketKey { @@ -578,8 +579,6 @@ struct ConnectionConfig { // AF_INET6 or AF_UNSPEC. This is ignored if backend connection // is made via Unix domain socket. int family; - bool no_tls; - bool http1_tls; } downstream; }; diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index b23a9be0..bbad8f64 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -1509,8 +1509,9 @@ int Http2Session::connection_made() { } } - auto must_terminate = !get_config()->conn.downstream.no_tls && - !nghttp2::ssl::check_http2_requirement(conn_.tls.ssl); + auto &shared_addr = group_->shared_addr; + auto must_terminate = + shared_addr->tls && !nghttp2::ssl::check_http2_requirement(conn_.tls.ssl); if (must_terminate) { if (LOG_ENABLED(INFO)) { diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 55620e3e..4474b33c 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -122,7 +122,7 @@ HttpDownstreamConnection::HttpDownstreamConnection(DownstreamAddrGroup *group, do_read_(&HttpDownstreamConnection::noop), do_write_(&HttpDownstreamConnection::noop), worker_(worker), - ssl_ctx_(worker->get_cl_ssl_ctx()), + ssl_ctx_(group->shared_addr->tls ? worker->get_cl_ssl_ctx() : nullptr), group_(group), addr_(nullptr), ioctrl_(&conn_.rlimit), diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index a36b20be..0e7c4c3f 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -1303,7 +1303,12 @@ SSL_CTX *setup_server_ssl_context(std::vector &all_ssl_ctx, return ssl_ctx; } -bool downstream_tls_enabled() { return !get_config()->conn.downstream.no_tls; } +bool downstream_tls_enabled() { + const auto &groups = get_config()->conn.downstream.addr_groups; + + return std::any_of(std::begin(groups), std::end(groups), + [](const DownstreamAddrGroupConfig &g) { return g.tls; }); +} SSL_CTX *setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index 900d9516..46407324 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -194,8 +194,8 @@ SSL_CTX *setup_server_ssl_context(std::vector &all_ssl_ctx, ); // Setups client side SSL_CTX. This function inspects get_config() -// and if downstream_no_tls is true, returns nullptr. Otherwise, only -// construct SSL_CTX if either client_mode or http2_bridge is true. +// and if TLS is disabled in all downstreams, returns nullptr. +// Otherwise, only construct SSL_CTX. SSL_CTX *setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED neverbleed_t *nb diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 75b862ab..4ce6d29b 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -66,7 +66,8 @@ namespace { bool match_shared_downstream_addr( const std::shared_ptr &lhs, const std::shared_ptr &rhs) { - if (lhs->addrs.size() != rhs->addrs.size() || lhs->proto != rhs->proto) { + if (lhs->addrs.size() != rhs->addrs.size() || lhs->proto != rhs->proto || + lhs->tls != rhs->tls) { return false; } @@ -146,6 +147,7 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, shared_addr->addrs.resize(src.addrs.size()); shared_addr->proto = src.proto; + shared_addr->tls = src.tls; for (size_t j = 0; j < src.addrs.size(); ++j) { auto &src_addr = src.addrs[j]; diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index bd7be800..9e3f9915 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -98,6 +98,7 @@ struct SharedDownstreamAddr { DownstreamConnectionPool dconn_pool; // Next downstream address index in addrs. size_t next; + bool tls; }; struct DownstreamAddrGroup {