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.
This commit is contained in:
Tatsuhiro Tsujikawa 2016-03-23 22:56:18 +09:00
parent 5b58db39ff
commit 58b06f32a2
10 changed files with 99 additions and 51 deletions

View File

@ -1160,7 +1160,6 @@ void fill_default_config() {
downstreamconf.request_buffer_size = 16_k; downstreamconf.request_buffer_size = 16_k;
downstreamconf.response_buffer_size = 128_k; downstreamconf.response_buffer_size = 128_k;
downstreamconf.family = AF_UNSPEC; downstreamconf.family = AF_UNSPEC;
downstreamconf.no_tls = true;
} }
} }
@ -1194,7 +1193,8 @@ Options:
The options are categorized into several groups. The options are categorized into several groups.
Connections: Connections:
-b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][;proto=<PROTO>]] -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]]
[;proto=<PROTO>][;tls]]
Set backend host and port. The multiple backend Set backend host and port. The multiple backend
addresses are accepted by repeating this option. UNIX addresses are accepted by repeating this option. UNIX
domain socket can be specified by prefixing path name domain socket can be specified by prefixing path name
@ -1263,7 +1263,10 @@ Connections:
quotes: "h2", "http/1.1". The default value of <PROTO> quotes: "h2", "http/1.1". The default value of <PROTO>
is "http/1.1". Note that usually "h2" refers to HTTP/2 is "http/1.1". Note that usually "h2" refers to HTTP/2
over TLS. But in this option, it may mean HTTP/2 over 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, <PATTERN> must Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special not contain these characters. Since ";" has special
@ -1303,8 +1306,6 @@ Connections:
--backend-write-timeout options. --backend-write-timeout options.
--accept-proxy-protocol --accept-proxy-protocol
Accept PROXY protocol version 1 on frontend connection. Accept PROXY protocol version 1 on frontend connection.
--backend-tls
Enable SSL/TLS on backend connections.
Performance: Performance:
-n, --workers=<N> -n, --workers=<N>
@ -2132,7 +2133,7 @@ void process_options(
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern 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) { for (auto &addr : g.addrs) {
LOG(INFO) << "group " << i << " -> " << addr.host.c_str() LOG(INFO) << "group " << i << " -> " << addr.host.c_str()
<< (addr.host_unix ? "" : ":" + util::utos(addr.port)); << (addr.host_unix ? "" : ":" + util::utos(addr.port));

View File

@ -750,7 +750,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
} }
} }
auto session = make_unique<Http2Session>( auto session = make_unique<Http2Session>(
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()); http2_freelist.append(session.release());
} }

View File

@ -568,6 +568,58 @@ int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) {
} }
} // namespace } // 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 { namespace {
// Parses host-path mapping patterns in |src_pattern|, and stores // Parses host-path mapping patterns in |src_pattern|, and stores
// mappings in config. We will store each host-path pattern found in // mappings in config. We will store each host-path pattern found in
@ -577,39 +629,20 @@ namespace {
// //
// This function returns 0 if it succeeds, or -1. // This function returns 0 if it succeeds, or -1.
int parse_mapping(const DownstreamAddrConfig &addr, 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 // This returns at least 1 element (it could be empty string). We
// will append '/' to all patterns, so it becomes catch-all pattern. // will append '/' to all patterns, so it becomes catch-all pattern.
auto mapping = util::split_str(src_pattern, ':'); auto mapping = util::split_str(src_pattern, ':');
assert(!mapping.empty()); assert(!mapping.empty());
auto &addr_groups = mod_config()->conn.downstream.addr_groups; auto &addr_groups = mod_config()->conn.downstream.addr_groups;
auto proto = PROTO_HTTP1; DownstreamParams params{};
params.proto = PROTO_HTTP1;
if (!src_proto.empty()) { if (parse_downstream_params(params, src_params) != 0) {
if (!util::istarts_with_l(src_proto, "proto=")) {
LOG(ERROR) << "backend: proto keyword not found";
return -1; 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;
}
}
for (const auto &raw_pattern : mapping) { for (const auto &raw_pattern : mapping) {
auto done = false; auto done = false;
std::string pattern; std::string pattern;
@ -627,10 +660,18 @@ int parse_mapping(const DownstreamAddrConfig &addr,
} }
for (auto &g : addr_groups) { for (auto &g : addr_groups) {
if (g.pattern == pattern) { if (g.pattern == pattern) {
if (g.proto != proto) { if (g.proto != params.proto) {
LOG(ERROR) << "backend: protocol mismatch. We saw protocol " LOG(ERROR) << "backend: protocol mismatch. We saw protocol "
<< strproto(g.proto) << " for pattern " << g.pattern << 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; return -1;
} }
@ -644,7 +685,8 @@ int parse_mapping(const DownstreamAddrConfig &addr,
} }
DownstreamAddrGroupConfig g(StringRef{pattern}); DownstreamAddrGroupConfig g(StringRef{pattern});
g.addrs.push_back(addr); g.addrs.push_back(addr);
g.proto = proto; g.proto = params.proto;
g.tls = params.tls;
if (pattern[0] == '*') { if (pattern[0] == '*') {
// wildcard pattern // 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 = addr_end == std::end(src) ? addr_end : addr_end + 1;
auto mapping_end = std::find(mapping, std::end(src), ';'); auto mapping_end = std::find(mapping, std::end(src), ';');
auto proto = mapping_end == std::end(src) ? mapping_end : mapping_end + 1; auto params = mapping_end == std::end(src) ? mapping_end : mapping_end + 1;
auto proto_end = std::find(proto, std::end(src), ';');
if (parse_mapping(addr, StringRef{mapping, mapping_end}, if (parse_mapping(addr, StringRef{mapping, mapping_end},
StringRef{proto, proto_end}) != 0) { StringRef{params, std::end(src)}) != 0) {
return -1; return -1;
} }
@ -2453,12 +2494,9 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
case SHRPX_OPTID_BACKEND_HTTP1_TLS: case SHRPX_OPTID_BACKEND_HTTP1_TLS:
LOG(WARN) << opt << ": deprecated. Use " << SHRPX_OPT_BACKEND_TLS
<< " instead.";
// fall through
case SHRPX_OPTID_BACKEND_TLS: 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; return 0;
case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS: case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS:
mod_config()->tls.session_cache.memcached.tls = util::strieq(optarg, "yes"); mod_config()->tls.session_cache.memcached.tls = util::strieq(optarg, "yes");

View File

@ -310,6 +310,7 @@ struct DownstreamAddrGroupConfig {
std::vector<DownstreamAddrConfig> addrs; std::vector<DownstreamAddrConfig> addrs;
// Application protocol used in this group // Application protocol used in this group
shrpx_proto proto; shrpx_proto proto;
bool tls;
}; };
struct TicketKey { struct TicketKey {
@ -578,8 +579,6 @@ struct ConnectionConfig {
// AF_INET6 or AF_UNSPEC. This is ignored if backend connection // AF_INET6 or AF_UNSPEC. This is ignored if backend connection
// is made via Unix domain socket. // is made via Unix domain socket.
int family; int family;
bool no_tls;
bool http1_tls;
} downstream; } downstream;
}; };

View File

@ -1509,8 +1509,9 @@ int Http2Session::connection_made() {
} }
} }
auto must_terminate = !get_config()->conn.downstream.no_tls && auto &shared_addr = group_->shared_addr;
!nghttp2::ssl::check_http2_requirement(conn_.tls.ssl); auto must_terminate =
shared_addr->tls && !nghttp2::ssl::check_http2_requirement(conn_.tls.ssl);
if (must_terminate) { if (must_terminate) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {

View File

@ -122,7 +122,7 @@ HttpDownstreamConnection::HttpDownstreamConnection(DownstreamAddrGroup *group,
do_read_(&HttpDownstreamConnection::noop), do_read_(&HttpDownstreamConnection::noop),
do_write_(&HttpDownstreamConnection::noop), do_write_(&HttpDownstreamConnection::noop),
worker_(worker), worker_(worker),
ssl_ctx_(worker->get_cl_ssl_ctx()), ssl_ctx_(group->shared_addr->tls ? worker->get_cl_ssl_ctx() : nullptr),
group_(group), group_(group),
addr_(nullptr), addr_(nullptr),
ioctrl_(&conn_.rlimit), ioctrl_(&conn_.rlimit),

View File

@ -1303,7 +1303,12 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
return 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( SSL_CTX *setup_downstream_client_ssl_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED

View File

@ -194,8 +194,8 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
); );
// Setups client side SSL_CTX. This function inspects get_config() // Setups client side SSL_CTX. This function inspects get_config()
// and if downstream_no_tls is true, returns nullptr. Otherwise, only // and if TLS is disabled in all downstreams, returns nullptr.
// construct SSL_CTX if either client_mode or http2_bridge is true. // Otherwise, only construct SSL_CTX.
SSL_CTX *setup_downstream_client_ssl_context( SSL_CTX *setup_downstream_client_ssl_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
neverbleed_t *nb neverbleed_t *nb

View File

@ -66,7 +66,8 @@ namespace {
bool match_shared_downstream_addr( bool match_shared_downstream_addr(
const std::shared_ptr<SharedDownstreamAddr> &lhs, const std::shared_ptr<SharedDownstreamAddr> &lhs,
const std::shared_ptr<SharedDownstreamAddr> &rhs) { const std::shared_ptr<SharedDownstreamAddr> &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; 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->addrs.resize(src.addrs.size());
shared_addr->proto = src.proto; shared_addr->proto = src.proto;
shared_addr->tls = src.tls;
for (size_t j = 0; j < src.addrs.size(); ++j) { for (size_t j = 0; j < src.addrs.size(); ++j) {
auto &src_addr = src.addrs[j]; auto &src_addr = src.addrs[j];

View File

@ -98,6 +98,7 @@ struct SharedDownstreamAddr {
DownstreamConnectionPool dconn_pool; DownstreamConnectionPool dconn_pool;
// Next downstream address index in addrs. // Next downstream address index in addrs.
size_t next; size_t next;
bool tls;
}; };
struct DownstreamAddrGroup { struct DownstreamAddrGroup {