nghttpx: Add TLS support for HTTP/1 backend
This commit is contained in:
parent
344cc1b5c3
commit
bb4e2f6a24
|
@ -112,7 +112,8 @@ OPTIONS = [
|
|||
"max-request-header-fields",
|
||||
"header-field-buffer",
|
||||
"max-header-fields",
|
||||
"no-http2-cipher-black-list"
|
||||
"no-http2-cipher-black-list",
|
||||
"backend-http1-tls"
|
||||
]
|
||||
|
||||
LOGVARS = [
|
||||
|
|
15
src/shrpx.cc
15
src/shrpx.cc
|
@ -1277,6 +1277,10 @@ Connections:
|
|||
--backend-write-timeout options.
|
||||
--accept-proxy-protocol
|
||||
Accept PROXY protocol version 1 on frontend connection.
|
||||
--backend-no-tls
|
||||
Disable SSL/TLS on backend connections.
|
||||
--backend-http1-tls
|
||||
Enable SSL/TLS on backend HTTP/1 connections.
|
||||
|
||||
Performance:
|
||||
-n, --workers=<N>
|
||||
|
@ -1603,8 +1607,6 @@ HTTP/2 and SPDY:
|
|||
connection to 2**<N>-1.
|
||||
Default: )"
|
||||
<< get_config()->http2.downstream.connection_window_bits << R"(
|
||||
--backend-no-tls
|
||||
Disable SSL/TLS on backend connections.
|
||||
--http2-no-cookie-crumbling
|
||||
Don't crumble cookie header field.
|
||||
--padding=<N>
|
||||
|
@ -2029,6 +2031,10 @@ void process_options(
|
|||
downstreamconf.proto = PROTO_HTTP;
|
||||
}
|
||||
|
||||
if (downstreamconf.proto == PROTO_HTTP && !downstreamconf.http1_tls) {
|
||||
downstreamconf.no_tls = true;
|
||||
}
|
||||
|
||||
if (!upstreamconf.no_tls &&
|
||||
(!tlsconf.private_key_file || !tlsconf.cert_file)) {
|
||||
print_usage(std::cerr);
|
||||
|
@ -2377,6 +2383,7 @@ int main(int argc, char **argv) {
|
|||
{SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, no_argument, &flag, 103},
|
||||
{SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER, required_argument, &flag, 104},
|
||||
{SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, required_argument, &flag, 105},
|
||||
{SHRPX_OPT_BACKEND_HTTP1_TLS, no_argument, &flag, 106},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
int option_index = 0;
|
||||
|
@ -2826,6 +2833,10 @@ int main(int argc, char **argv) {
|
|||
// --max-request-header-fields
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, optarg);
|
||||
break;
|
||||
case 106:
|
||||
// --backend-http1-tls
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS, "yes");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -722,8 +722,9 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
}
|
||||
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session);
|
||||
} else {
|
||||
dconn =
|
||||
make_unique<HttpDownstreamConnection>(dconn_pool, group, conn_.loop);
|
||||
dconn = make_unique<HttpDownstreamConnection>(
|
||||
dconn_pool, group, conn_.loop, worker_->get_cl_ssl_ctx(),
|
||||
worker_->get_mcpool());
|
||||
}
|
||||
dconn->set_client_handler(this);
|
||||
return dconn;
|
||||
|
|
|
@ -676,6 +676,7 @@ enum {
|
|||
SHRPX_OPTID_BACKEND_HTTP_PROXY_URI,
|
||||
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
|
||||
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
|
||||
SHRPX_OPTID_BACKEND_HTTP1_TLS,
|
||||
SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
|
||||
SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
|
||||
SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
|
||||
|
@ -1077,6 +1078,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
}
|
||||
break;
|
||||
case 's':
|
||||
if (util::strieq_l("backend-http1-tl", name, 16)) {
|
||||
return SHRPX_OPTID_BACKEND_HTTP1_TLS;
|
||||
}
|
||||
if (util::strieq_l("max-header-field", name, 16)) {
|
||||
return SHRPX_OPTID_MAX_HEADER_FIELDS;
|
||||
}
|
||||
|
@ -2213,6 +2217,10 @@ int parse_config(const char *opt, const char *optarg,
|
|||
case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
|
||||
mod_config()->tls.no_http2_cipher_black_list = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_HTTP1_TLS:
|
||||
mod_config()->conn.downstream.http1_tls = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_CONF:
|
||||
LOG(WARN) << "conf: ignored";
|
||||
|
|
|
@ -206,6 +206,7 @@ constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] =
|
|||
"max-response-header-fields";
|
||||
constexpr char SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST[] =
|
||||
"no-http2-cipher-black-list";
|
||||
constexpr char SHRPX_OPT_BACKEND_HTTP1_TLS[] = "backend-http1-tls";
|
||||
|
||||
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||
|
||||
|
@ -534,6 +535,7 @@ struct ConnectionConfig {
|
|||
// downstream protocol; this will be determined by given options.
|
||||
shrpx_proto proto;
|
||||
bool no_tls;
|
||||
bool http1_tls;
|
||||
// true if IPv4 only; ipv4 and ipv6 are mutually exclusive; and
|
||||
// (ipv4 && ipv6) must be false.
|
||||
bool ipv4;
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "shrpx_downstream_connection_pool.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_ssl.h"
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
|
||||
|
@ -100,7 +101,7 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = downstream->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
if (dconn->on_connect() != 0) {
|
||||
if (dconn->connected() != 0) {
|
||||
if (upstream->on_downstream_abort_request(downstream, 503) != 0) {
|
||||
delete handler;
|
||||
}
|
||||
|
@ -111,13 +112,17 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
} // namespace
|
||||
|
||||
HttpDownstreamConnection::HttpDownstreamConnection(
|
||||
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop)
|
||||
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop,
|
||||
SSL_CTX *ssl_ctx, MemchunkPool *mcpool)
|
||||
: DownstreamConnection(dconn_pool),
|
||||
conn_(loop, -1, nullptr, nullptr,
|
||||
conn_(loop, -1, nullptr, mcpool,
|
||||
get_config()->conn.downstream.timeout.write,
|
||||
get_config()->conn.downstream.timeout.read, {}, {}, connectcb,
|
||||
readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
|
||||
get_config()->tls.dyn_rec.idle_timeout),
|
||||
do_read_(&HttpDownstreamConnection::noop),
|
||||
do_write_(&HttpDownstreamConnection::noop),
|
||||
ssl_ctx_(ssl_ctx),
|
||||
ioctrl_(&conn_.rlimit),
|
||||
response_htp_{0},
|
||||
group_(group),
|
||||
|
@ -144,6 +149,20 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// TODO This does not do any good, since once connection is
|
||||
// dropped, HttpDownstreamConnection is destroyed. We need to
|
||||
// cache SSL session somewhere, possibly worker scope cache.
|
||||
if (ssl_ctx_) {
|
||||
if (!conn_.tls.ssl) {
|
||||
auto ssl = ssl::create_ssl(ssl_ctx_);
|
||||
if (!ssl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
conn_.set_ssl(ssl);
|
||||
}
|
||||
}
|
||||
|
||||
auto worker = client_handler_->get_worker();
|
||||
auto &next_downstream = worker->get_dgrp(group_)->next;
|
||||
auto end = next_downstream;
|
||||
|
@ -190,6 +209,17 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
|
||||
addr_idx_ = i;
|
||||
|
||||
if (ssl_ctx_) {
|
||||
auto sni_name = !get_config()->tls.backend_sni_name.empty()
|
||||
? StringRef(get_config()->tls.backend_sni_name)
|
||||
: StringRef(addrs[i].host);
|
||||
if (!util::numeric_host(sni_name.c_str())) {
|
||||
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
|
||||
}
|
||||
|
||||
conn_.prepare_client_handshake();
|
||||
}
|
||||
|
||||
ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
|
||||
ev_io_set(&conn_.rev, conn_.fd, EV_READ);
|
||||
|
||||
|
@ -748,11 +778,69 @@ http_parser_settings htp_hooks = {
|
|||
};
|
||||
} // namespace
|
||||
|
||||
int HttpDownstreamConnection::on_read() {
|
||||
ssize_t HttpDownstreamConnection::read_clear(uint8_t *buf, size_t len) {
|
||||
return conn_.read_clear(buf, len);
|
||||
}
|
||||
|
||||
ssize_t HttpDownstreamConnection::write_clear(struct iovec *iov,
|
||||
size_t iovlen) {
|
||||
return conn_.writev_clear(iov, iovlen);
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::tls_handshake() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
auto rv = conn_.tls_handshake();
|
||||
if (rv == SHRPX_ERR_INPROGRESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rv < 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "SSL/TLS handshake completed";
|
||||
}
|
||||
|
||||
if (!get_config()->tls.insecure &&
|
||||
ssl::check_cert(conn_.tls.ssl, &get_config()
|
||||
->conn.downstream.addr_groups[group_]
|
||||
.addrs[addr_idx_]) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
read_ = &HttpDownstreamConnection::read_tls;
|
||||
write_ = &HttpDownstreamConnection::write_tls;
|
||||
|
||||
do_read_ = &HttpDownstreamConnection::do_read;
|
||||
do_write_ = &HttpDownstreamConnection::do_write;
|
||||
|
||||
// TODO Check negotiated ALPN
|
||||
|
||||
return on_write();
|
||||
}
|
||||
|
||||
ssize_t HttpDownstreamConnection::read_tls(uint8_t *buf, size_t buflen) {
|
||||
return conn_.read_tls(buf, buflen);
|
||||
}
|
||||
|
||||
ssize_t HttpDownstreamConnection::write_tls(struct iovec *iov, size_t iovlen) {
|
||||
assert(iovlen > 0);
|
||||
return conn_.write_tls(iov[0].iov_base, iov[0].iov_len);
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::do_read() {
|
||||
if (!connected_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (conn_.tls.ssl) {
|
||||
ERR_clear_error();
|
||||
}
|
||||
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
std::array<uint8_t, 8_k> buf;
|
||||
int rv;
|
||||
|
@ -760,7 +848,7 @@ int HttpDownstreamConnection::on_read() {
|
|||
if (downstream_->get_upgraded()) {
|
||||
// For upgraded connection, just pass data to the upstream.
|
||||
for (;;) {
|
||||
auto nread = conn_.read_clear(buf.data(), buf.size());
|
||||
auto nread = read_(*this, buf.data(), buf.size());
|
||||
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
|
@ -784,7 +872,7 @@ int HttpDownstreamConnection::on_read() {
|
|||
}
|
||||
|
||||
for (;;) {
|
||||
auto nread = conn_.read_clear(buf.data(), buf.size());
|
||||
auto nread = read_(*this, buf.data(), buf.size());
|
||||
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
|
@ -842,11 +930,15 @@ int HttpDownstreamConnection::on_read() {
|
|||
}
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::on_write() {
|
||||
int HttpDownstreamConnection::do_write() {
|
||||
if (!connected_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (conn_.tls.ssl) {
|
||||
ERR_clear_error();
|
||||
}
|
||||
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
auto upstream = downstream_->get_upstream();
|
||||
|
@ -857,7 +949,7 @@ int HttpDownstreamConnection::on_write() {
|
|||
while (input->rleft() > 0) {
|
||||
auto iovcnt = input->riovec(iov.data(), iov.size());
|
||||
|
||||
auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
|
||||
auto nwrite = write_(*this, iov.data(), iovcnt);
|
||||
|
||||
if (nwrite == 0) {
|
||||
return 0;
|
||||
|
@ -883,7 +975,7 @@ int HttpDownstreamConnection::on_write() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::on_connect() {
|
||||
int HttpDownstreamConnection::connected() {
|
||||
auto connect_blocker = client_handler_->get_connect_blocker();
|
||||
|
||||
if (!util::check_socket_connected(conn_.fd)) {
|
||||
|
@ -898,18 +990,38 @@ int HttpDownstreamConnection::on_connect() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, this) << "Connected to downstream host";
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
connect_blocker->on_success();
|
||||
|
||||
conn_.rlimit.startw();
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
ev_set_cb(&conn_.wev, writecb);
|
||||
|
||||
if (conn_.tls.ssl) {
|
||||
do_read_ = &HttpDownstreamConnection::tls_handshake;
|
||||
do_write_ = &HttpDownstreamConnection::tls_handshake;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
do_read_ = &HttpDownstreamConnection::do_read;
|
||||
do_write_ = &HttpDownstreamConnection::do_write;
|
||||
|
||||
read_ = &HttpDownstreamConnection::read_clear;
|
||||
write_ = &HttpDownstreamConnection::write_clear;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::on_read() { return do_read_(*this); }
|
||||
|
||||
int HttpDownstreamConnection::on_write() { return do_write_(*this); }
|
||||
|
||||
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
|
||||
|
||||
void HttpDownstreamConnection::signal_write() {
|
||||
|
@ -918,4 +1030,6 @@ void HttpDownstreamConnection::signal_write() {
|
|||
|
||||
size_t HttpDownstreamConnection::get_group() const { return group_; }
|
||||
|
||||
int HttpDownstreamConnection::noop() { return 0; }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -40,7 +40,8 @@ class DownstreamConnectionPool;
|
|||
class HttpDownstreamConnection : public DownstreamConnection {
|
||||
public:
|
||||
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group,
|
||||
struct ev_loop *loop);
|
||||
struct ev_loop *loop, SSL_CTX *ssl_ctx,
|
||||
MemchunkPool *mcpool);
|
||||
virtual ~HttpDownstreamConnection();
|
||||
virtual int attach_downstream(Downstream *downstream);
|
||||
virtual void detach_downstream(Downstream *downstream);
|
||||
|
@ -61,11 +62,29 @@ public:
|
|||
|
||||
virtual bool poolable() const { return true; }
|
||||
|
||||
int on_connect();
|
||||
ssize_t read_clear(uint8_t *buf, size_t buflen);
|
||||
ssize_t write_clear(struct iovec *iov, size_t iovlen);
|
||||
ssize_t read_tls(uint8_t *buf, size_t buflen);
|
||||
ssize_t write_tls(struct iovec *iov, size_t iovlen);
|
||||
|
||||
int do_read();
|
||||
int do_write();
|
||||
int tls_handshake();
|
||||
|
||||
int connected();
|
||||
void signal_write();
|
||||
|
||||
int noop();
|
||||
|
||||
private:
|
||||
Connection conn_;
|
||||
std::function<int(HttpDownstreamConnection &)> do_read_, do_write_;
|
||||
std::function<ssize_t(HttpDownstreamConnection &, uint8_t *buf,
|
||||
size_t buflen)> read_;
|
||||
std::function<ssize_t(HttpDownstreamConnection &, struct iovec *iov,
|
||||
size_t iovlen)> write_;
|
||||
// nullptr if TLS is not used.
|
||||
SSL_CTX *ssl_ctx_;
|
||||
IOControl ioctrl_;
|
||||
http_parser response_htp_;
|
||||
size_t group_;
|
||||
|
|
|
@ -628,9 +628,9 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
}
|
||||
|
||||
namespace {
|
||||
int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
|
||||
const unsigned char *in, unsigned int inlen,
|
||||
void *arg) {
|
||||
int select_h2_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg) {
|
||||
if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
|
||||
inlen)) {
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
@ -640,6 +640,22 @@ int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int select_h1_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg) {
|
||||
auto end = in + inlen;
|
||||
for (; in < end;) {
|
||||
if (util::streq_l(NGHTTP2_H1_1_ALPN, in, end - in)) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
in += in[0];
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SSL_CTX *create_ssl_client_context(
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
neverbleed_t *nb
|
||||
|
@ -722,15 +738,27 @@ SSL_CTX *create_ssl_client_context(
|
|||
DIE();
|
||||
}
|
||||
}
|
||||
// NPN selection callback
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, nullptr);
|
||||
|
||||
if (get_config()->conn.downstream.proto == PROTO_HTTP2) {
|
||||
// NPN selection callback
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h2_next_proto_cb, nullptr);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
// ALPN advertisement; We only advertise HTTP/2
|
||||
auto proto_list = util::get_default_alpn();
|
||||
// ALPN advertisement; We only advertise HTTP/2
|
||||
auto proto_list = util::get_default_alpn();
|
||||
|
||||
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
||||
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
} else {
|
||||
// NPN selection callback
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h1_next_proto_cb, nullptr);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
SSL_CTX_set_alpn_protos(
|
||||
ssl_ctx, reinterpret_cast<const unsigned char *>(NGHTTP2_H1_1_ALPN),
|
||||
str_size(NGHTTP2_H1_1_ALPN));
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
}
|
||||
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
@ -1270,15 +1298,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
return ssl_ctx;
|
||||
}
|
||||
|
||||
bool downstream_tls_enabled() {
|
||||
auto no_tls = get_config()->conn.downstream.no_tls;
|
||||
|
||||
if (get_config()->client_mode) {
|
||||
return !no_tls;
|
||||
}
|
||||
|
||||
return get_config()->http2_bridge && !no_tls;
|
||||
}
|
||||
bool downstream_tls_enabled() { return !get_config()->conn.downstream.no_tls; }
|
||||
|
||||
SSL_CTX *setup_client_ssl_context(
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
|
|
Loading…
Reference in New Issue