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.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=(<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
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 <PROTO>
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, <PATTERN> 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=<N>
@ -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));

View File

@ -750,7 +750,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
}
}
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());
}

View File

@ -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");

View File

@ -310,6 +310,7 @@ struct DownstreamAddrGroupConfig {
std::vector<DownstreamAddrConfig> 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;
};

View File

@ -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)) {

View File

@ -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),

View File

@ -1303,7 +1303,12 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &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

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()
// 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

View File

@ -66,7 +66,8 @@ namespace {
bool match_shared_downstream_addr(
const std::shared_ptr<SharedDownstreamAddr> &lhs,
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;
}
@ -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];

View File

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