nghttpx: Redirect to HTTPS URI with redirect-if-not-tls param

This commit removes frontend-tls parameter, and adds
redirect-if-not-tls parameter parameter to --backend option.  nghttpx
now responds to the request with 308 status code to redirect the
request to https URI if frontend connection is not TLS encrypted, and
redirect-if-no-tls parameter is used in --backend option.  The port
number in Location header field is 443 by default (thus omitted), but
it can be configurable using --redirect-https-port option.
This commit is contained in:
Tatsuhiro Tsujikawa 2017-02-18 18:23:06 +09:00
parent 1bd6893084
commit a7c780a732
22 changed files with 310 additions and 61 deletions

View File

@ -160,6 +160,7 @@ OPTIONS = [
"accesslog-write-early", "accesslog-write-early",
"tls-min-proto-version", "tls-min-proto-version",
"tls-max-proto-version", "tls-max-proto-version",
"redirect-https-port",
] ]
LOGVARS = [ LOGVARS = [

View File

@ -185,6 +185,8 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "util_is_hex_string", !CU_add_test(pSuite, "util_is_hex_string",
shrpx::test_util_is_hex_string) || shrpx::test_util_is_hex_string) ||
!CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) || !CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) ||
!CU_add_test(pSuite, "util_extract_host",
shrpx::test_util_extract_host) ||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) || !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) || !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||

View File

@ -1447,6 +1447,7 @@ void fill_default_config(Config *config) {
httpconf.max_request_header_fields = 100; httpconf.max_request_header_fields = 100;
httpconf.response_header_field_buffer = 64_k; httpconf.response_header_field_buffer = 64_k;
httpconf.max_response_header_fields = 500; httpconf.max_response_header_fields = 500;
httpconf.redirect_https_port = StringRef::from_lit("443");
auto &http2conf = config->http2; auto &http2conf = config->http2;
{ {
@ -1678,12 +1679,12 @@ Connections:
The parameters are delimited by ";". The available The parameters are delimited by ";". The available
parameters are: "proto=<PROTO>", "tls", parameters are: "proto=<PROTO>", "tls",
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>", "sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
"affinity=<METHOD>", "dns", and "frontend-tls". The "affinity=<METHOD>", "dns", and "redirect-if-not-tls".
parameter consists of keyword, and optionally followed The parameter consists of keyword, and optionally
by "=" and value. For example, the parameter "proto=h2" followed by "=" and value. For example, the parameter
consists of the keyword "proto" and value "h2". The "proto=h2" consists of the keyword "proto" and value
parameter "tls" consists of the keyword "tls" without "h2". The parameter "tls" consists of the keyword "tls"
value. Each parameter is described as follows. without value. Each parameter is described as follows.
The backend application protocol can be specified using The backend application protocol can be specified using
optional "proto" parameter, and in the form of optional "proto" parameter, and in the form of
@ -1740,16 +1741,18 @@ Connections:
backend host name at start up, or reloading backend host name at start up, or reloading
configuration is skipped. configuration is skipped.
If "frontend-tls" parameter is used, the matched backend If "redirect-if-not-tls" parameter is used, the matched
requires frontend TLS connection. In other words, even backend requires that frontend connection is TLS
if pattern is matched, frontend connection is not TLS encrypted. If it isn't, nghttpx responds to the request
protected, the request is forwarded to one of catch-all with 308 status code, and https URI the client should
backends. For this reason, catch-all backend cannot use instead is included in Location header field. The
have "frontend-tls" parameter. If at least one backend port number in redirect URI is 443 by default, and can
has "frontend-tls" parameter, this feature is enabled be changed using --redirect-https-port option. If at
for all backend servers sharing the same <PATTERN>. It least one backend has "redirect-if-not-tls" parameter,
is advised to set "frontend-tls" parameter to all this feature is enabled for all backend servers sharing
backends explicitly if this feature is desired. the same <PATTERN>. It is advised to set
"redirect-if-no-tls" parameter to all backends
explicitly if this feature is desired.
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
@ -2545,6 +2548,12 @@ HTTP:
Don't rewrite server header field in default mode. When Don't rewrite server header field in default mode. When
--http2-proxy is used, these headers will not be altered --http2-proxy is used, these headers will not be altered
regardless of this option. regardless of this option.
--redirect-https-port=<PORT>
Specify the port number which appears in Location header
field when redirect to HTTPS URI is made due to
"redirect-if-not-tls" parameter in --backend option.
Default: )"
<< config->http.redirect_https_port << R"(
API: API:
--api-max-request-body=<SIZE> --api-max-request-body=<SIZE>
@ -3253,6 +3262,7 @@ int main(int argc, char **argv) {
152}, 152},
{SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag, {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag,
153}, 153},
{SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -3975,6 +3985,10 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION, cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION,
StringRef{optarg}); StringRef{optarg});
break; break;
case 154:
// --redirect-https-port
cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg});
break;
default: default:
break; break;
} }

View File

@ -961,7 +961,7 @@ uint32_t next_cycle(const WeightedPri &pri) {
} // namespace } // namespace
std::unique_ptr<DownstreamConnection> std::unique_ptr<DownstreamConnection>
ClientHandler::get_downstream_connection(Downstream *downstream) { ClientHandler::get_downstream_connection(int &err, Downstream *downstream) {
size_t group_idx; size_t group_idx;
auto &downstreamconf = *worker_->get_downstream_config(); auto &downstreamconf = *worker_->get_downstream_config();
auto &routerconf = downstreamconf.router; auto &routerconf = downstreamconf.router;
@ -971,6 +971,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
const auto &req = downstream->request(); const auto &req = downstream->request();
err = 0;
switch (faddr_->alt_mode) { switch (faddr_->alt_mode) {
case ALTMODE_API: case ALTMODE_API:
return make_unique<APIDownstreamConnection>(worker_); return make_unique<APIDownstreamConnection>(worker_);
@ -1009,11 +1011,13 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
CLOG(INFO, this) << "Downstream address group_idx: " << group_idx; CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
} }
if (groups[group_idx]->shared_addr->require_upstream_tls && !conn_.tls.ssl) { if (groups[group_idx]->shared_addr->redirect_if_not_tls && !conn_.tls.ssl) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Downstream address group " << group_idx CLOG(INFO, this) << "Downstream address group " << group_idx
<< " requires frontend TLS connection. Send request to " << " requires frontend TLS connection.";
"catch-all group."; }
group_idx = catch_all; err = SHRPX_ERR_TLS_REQUIRED;
return nullptr;
} }
auto &group = groups[group_idx]; auto &group = groups[group_idx];
@ -1087,6 +1091,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
CLOG(INFO, this) << "No working downstream address found"; CLOG(INFO, this) << "No working downstream address found";
} }
err = -1;
return nullptr; return nullptr;
} }
@ -1099,6 +1104,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
auto http2session = select_http2_session(group); auto http2session = select_http2_session(group);
if (http2session == nullptr) { if (http2session == nullptr) {
err = -1;
return nullptr; return nullptr;
} }

View File

@ -99,8 +99,12 @@ public:
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn); void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
void remove_downstream_connection(DownstreamConnection *dconn); void remove_downstream_connection(DownstreamConnection *dconn);
// Returns DownstreamConnection object based on request path. This
// function returns non-null DownstreamConnection, and assigns 0 to
// |err| if it succeeds, or returns nullptr, and assigns negative
// error code to |err|.
std::unique_ptr<DownstreamConnection> std::unique_ptr<DownstreamConnection>
get_downstream_connection(Downstream *downstream); get_downstream_connection(int &err, Downstream *downstream);
MemchunkPool *get_mcpool(); MemchunkPool *get_mcpool();
SSL *get_ssl() const; SSL *get_ssl() const;
// Call this function when HTTP/2 connection header is received at // Call this function when HTTP/2 connection header is received at

View File

@ -747,7 +747,7 @@ struct DownstreamParams {
shrpx_session_affinity affinity; shrpx_session_affinity affinity;
bool tls; bool tls;
bool dns; bool dns;
bool frontend_tls; bool redirect_if_not_tls;
}; };
namespace { namespace {
@ -823,8 +823,8 @@ int parse_downstream_params(DownstreamParams &out,
} }
} else if (util::strieq_l("dns", param)) { } else if (util::strieq_l("dns", param)) {
out.dns = true; out.dns = true;
} else if (util::strieq_l("frontend-tls", param)) { } else if (util::strieq_l("redirect-if-not-tls", param)) {
out.frontend_tls = true; out.redirect_if_not_tls = true;
} else if (!param.empty()) { } else if (!param.empty()) {
LOG(ERROR) << "backend: " << param << ": unknown keyword"; LOG(ERROR) << "backend: " << param << ": unknown keyword";
return -1; return -1;
@ -919,8 +919,8 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
} }
// If at least one backend requires frontend TLS connection, // If at least one backend requires frontend TLS connection,
// enable it for all backends sharing the same pattern. // enable it for all backends sharing the same pattern.
if (params.frontend_tls) { if (params.redirect_if_not_tls) {
g.require_upstream_tls = true; g.redirect_if_not_tls = true;
} }
g.addrs.push_back(addr); g.addrs.push_back(addr);
done = true; done = true;
@ -936,7 +936,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
auto &g = addr_groups.back(); auto &g = addr_groups.back();
g.addrs.push_back(addr); g.addrs.push_back(addr);
g.affinity = params.affinity; g.affinity = params.affinity;
g.require_upstream_tls = params.frontend_tls; g.redirect_if_not_tls = params.redirect_if_not_tls;
if (pattern[0] == '*') { if (pattern[0] == '*') {
// wildcard pattern // wildcard pattern
@ -1753,6 +1753,9 @@ int option_lookup_token(const char *name, size_t namelen) {
} }
break; break;
case 't': case 't':
if (util::strieq_l("redirect-https-por", name, 18)) {
return SHRPX_OPTID_REDIRECT_HTTPS_PORT;
}
if (util::strieq_l("stream-read-timeou", name, 18)) { if (util::strieq_l("stream-read-timeou", name, 18)) {
return SHRPX_OPTID_STREAM_READ_TIMEOUT; return SHRPX_OPTID_STREAM_READ_TIMEOUT;
} }
@ -3356,6 +3359,16 @@ int parse_config(Config *config, int optid, const StringRef &opt,
return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg); return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg);
case SHRPX_OPTID_TLS_MAX_PROTO_VERSION: case SHRPX_OPTID_TLS_MAX_PROTO_VERSION:
return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg); return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg);
case SHRPX_OPTID_REDIRECT_HTTPS_PORT: {
auto n = util::parse_uint(optarg);
if (n == -1 || n < 0 || n > 65535) {
LOG(ERROR) << opt << ": bad value. Specify an integer in the range [0, "
"65535], inclusive";
return -1;
}
config->http.redirect_https_port = optarg;
return 0;
}
case SHRPX_OPTID_CONF: case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";
@ -3663,12 +3676,6 @@ int configure_downstream_group(Config *config, bool http2_proxy,
return -1; return -1;
} }
if (addr_groups[catch_all_group].require_upstream_tls) {
LOG(FATAL)
<< "backend: Catch-all backend cannot have frontend-tls parameter";
return -1;
}
downstreamconf.addr_group_catch_all = catch_all_group; downstreamconf.addr_group_catch_all = catch_all_group;
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {

View File

@ -331,6 +331,8 @@ constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION =
StringRef::from_lit("tls-min-proto-version"); StringRef::from_lit("tls-min-proto-version");
constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION = constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION =
StringRef::from_lit("tls-max-proto-version"); StringRef::from_lit("tls-max-proto-version");
constexpr auto SHRPX_OPT_REDIRECT_HTTPS_PORT =
StringRef::from_lit("redirect-https-port");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -434,9 +436,7 @@ struct AffinityHash {
struct DownstreamAddrGroupConfig { struct DownstreamAddrGroupConfig {
DownstreamAddrGroupConfig(const StringRef &pattern) DownstreamAddrGroupConfig(const StringRef &pattern)
: pattern(pattern), : pattern(pattern), affinity(AFFINITY_NONE), redirect_if_not_tls(false) {}
affinity(AFFINITY_NONE),
require_upstream_tls(false) {}
StringRef pattern; StringRef pattern;
std::vector<DownstreamAddrConfig> addrs; std::vector<DownstreamAddrConfig> addrs;
@ -445,8 +445,9 @@ struct DownstreamAddrGroupConfig {
std::vector<AffinityHash> affinity_hash; std::vector<AffinityHash> affinity_hash;
// Session affinity // Session affinity
shrpx_session_affinity affinity; shrpx_session_affinity affinity;
// true if this group requires that client connection must be TLS. // true if this group requires that client connection must be TLS,
bool require_upstream_tls; // and the request must be redirected to https URI.
bool redirect_if_not_tls;
}; };
struct TicketKey { struct TicketKey {
@ -639,6 +640,9 @@ struct HttpConfig {
HeaderRefs add_request_headers; HeaderRefs add_request_headers;
HeaderRefs add_response_headers; HeaderRefs add_response_headers;
StringRef server_name; StringRef server_name;
// Port number which appears in Location header field when https
// redirect is made.
StringRef redirect_https_port;
size_t request_header_field_buffer; size_t request_header_field_buffer;
size_t max_request_header_fields; size_t max_request_header_fields;
size_t response_header_field_buffer; size_t response_header_field_buffer;
@ -1016,6 +1020,7 @@ enum {
SHRPX_OPTID_PSK_SECRETS, SHRPX_OPTID_PSK_SECRETS,
SHRPX_OPTID_READ_BURST, SHRPX_OPTID_READ_BURST,
SHRPX_OPTID_READ_RATE, SHRPX_OPTID_READ_RATE,
SHRPX_OPTID_REDIRECT_HTTPS_PORT,
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER, SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER, SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RLIMIT_NOFILE, SHRPX_OPTID_RLIMIT_NOFILE,

View File

@ -38,6 +38,7 @@ enum ErrorCode {
SHRPX_ERR_INPROGRESS = -102, SHRPX_ERR_INPROGRESS = -102,
SHRPX_ERR_DCONN_CANCELED = -103, SHRPX_ERR_DCONN_CANCELED = -103,
SHRPX_ERR_RETRY = -104, SHRPX_ERR_RETRY = -104,
SHRPX_ERR_TLS_REQUIRED = -105,
}; };
} // namespace shrpx } // namespace shrpx

View File

@ -434,9 +434,25 @@ void Http2Upstream::start_downstream(Downstream *downstream) {
void Http2Upstream::initiate_downstream(Downstream *downstream) { void Http2Upstream::initiate_downstream(Downstream *downstream) {
int rv; int rv;
auto dconn = handler_->get_downstream_connection(downstream); auto dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn || if (!dconn) {
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = redirect_to_https(downstream);
} else {
rv = error_reply(downstream, 503);
}
if (rv != 0) {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
downstream->set_request_state(Downstream::CONNECT_FAIL);
downstream_queue_.mark_failure(downstream);
return;
}
rv = downstream->attach_downstream_connection(std::move(dconn));
if (rv != 0) {
// downstream connection fails, send error page // downstream connection fails, send error page
if (error_reply(downstream, 503) != 0) { if (error_reply(downstream, 503) != 0) {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
@ -1792,6 +1808,52 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
return 0; return 0;
} }
int Http2Upstream::on_downstream_abort_request_with_https_redirect(
Downstream *downstream) {
int rv;
rv = redirect_to_https(downstream);
if (rv != 0) {
return -1;
}
handler_->signal_write();
return 0;
}
int Http2Upstream::redirect_to_https(Downstream *downstream) {
auto &req = downstream->request();
if (req.method == HTTP_CONNECT || req.scheme != "http") {
return error_reply(downstream, 400);
}
auto authority = util::extract_host(req.authority);
if (authority.empty()) {
return error_reply(downstream, 400);
}
auto &balloc = downstream->get_block_allocator();
auto config = get_config();
auto &httpconf = config->http;
StringRef loc;
if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
req.path);
} else {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
StringRef::from_lit(":"),
httpconf.redirect_https_port, req.path);
}
auto &resp = downstream->response();
resp.http_status = 308;
resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
http2::HD_LOCATION);
return send_reply(downstream, nullptr, 0);
}
int Http2Upstream::consume(int32_t stream_id, size_t len) { int Http2Upstream::consume(int32_t stream_id, size_t len) {
int rv; int rv;
@ -1879,6 +1941,8 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
std::unique_ptr<DownstreamConnection> dconn; std::unique_ptr<DownstreamConnection> dconn;
rv = 0;
if (no_retry || downstream->no_more_retry()) { if (no_retry || downstream->no_more_retry()) {
goto fail; goto fail;
} }
@ -1886,7 +1950,7 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
// downstream connection is clean; we can retry with new // downstream connection is clean; we can retry with new
// downstream connection. // downstream connection.
dconn = handler_->get_downstream_connection(downstream); dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn) { if (!dconn) {
goto fail; goto fail;
} }
@ -1904,7 +1968,12 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
return 0; return 0;
fail: fail:
if (on_downstream_abort_request(downstream, 503) != 0) { if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = on_downstream_abort_request(downstream, 503);
}
if (rv != 0) {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
} }
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();

View File

@ -54,6 +54,8 @@ public:
virtual int on_timeout(Downstream *downstream); virtual int on_timeout(Downstream *downstream);
virtual int on_downstream_abort_request(Downstream *downstream, virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code); unsigned int status_code);
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
virtual ClientHandler *get_client_handler() const; virtual ClientHandler *get_client_handler() const;
virtual int downstream_read(DownstreamConnection *dconn); virtual int downstream_read(DownstreamConnection *dconn);
@ -120,6 +122,8 @@ public:
size_t get_max_buffer_size() const; size_t get_max_buffer_size() const;
int redirect_to_https(Downstream *downstream);
private: private:
DefaultMemchunks wb_; DefaultMemchunks wb_;
std::unique_ptr<HttpsUpstream> pre_upstream_; std::unique_ptr<HttpsUpstream> pre_upstream_;

View File

@ -89,7 +89,8 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();
auto ndconn = handler->get_downstream_connection(downstream); int rv;
auto ndconn = handler->get_downstream_connection(rv, downstream);
if (ndconn) { if (ndconn) {
if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) { if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) {
return; return;
@ -98,7 +99,13 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
downstream->set_request_state(Downstream::CONNECT_FAIL); downstream->set_request_state(Downstream::CONNECT_FAIL);
if (upstream->on_downstream_abort_request(downstream, 504) != 0) { if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = upstream->on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = upstream->on_downstream_abort_request(downstream, 504);
}
if (rv != 0) {
delete handler; delete handler;
} }
} }
@ -132,7 +139,8 @@ void backend_retry(Downstream *downstream) {
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();
auto ndconn = handler->get_downstream_connection(downstream); int rv;
auto ndconn = handler->get_downstream_connection(rv, downstream);
if (ndconn) { if (ndconn) {
if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) { if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) {
return; return;
@ -141,7 +149,13 @@ void backend_retry(Downstream *downstream) {
downstream->set_request_state(Downstream::CONNECT_FAIL); downstream->set_request_state(Downstream::CONNECT_FAIL);
if (upstream->on_downstream_abort_request(downstream, 503) != 0) { if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = upstream->on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = upstream->on_downstream_abort_request(downstream, 503);
}
if (rv != 0) {
delete handler; delete handler;
} }
} }

View File

@ -421,10 +421,18 @@ int htp_hdrs_completecb(http_parser *htp) {
return 0; return 0;
} }
auto dconn = handler->get_downstream_connection(downstream); auto dconn = handler->get_downstream_connection(rv, downstream);
if (!dconn || if (!dconn) {
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { if (rv == SHRPX_ERR_TLS_REQUIRED) {
upstream->redirect_to_https(downstream);
}
downstream->set_request_state(Downstream::CONNECT_FAIL);
return -1;
}
if (downstream->attach_downstream_connection(std::move(dconn)) != 0) {
downstream->set_request_state(Downstream::CONNECT_FAIL); downstream->set_request_state(Downstream::CONNECT_FAIL);
return -1; return -1;
@ -1225,6 +1233,52 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
return 0; return 0;
} }
int HttpsUpstream::on_downstream_abort_request_with_https_redirect(
Downstream *downstream) {
redirect_to_https(downstream);
handler_->signal_write_no_wait();
return 0;
}
int HttpsUpstream::redirect_to_https(Downstream *downstream) {
auto &req = downstream->request();
if (req.method == HTTP_CONNECT || req.scheme != "http" ||
req.authority.empty()) {
error_reply(400);
return 0;
}
auto authority = util::extract_host(req.authority);
if (authority.empty()) {
error_reply(400);
return 0;
}
auto &balloc = downstream->get_block_allocator();
auto config = get_config();
auto &httpconf = config->http;
StringRef loc;
if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
req.path);
} else {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
StringRef::from_lit(":"),
httpconf.redirect_https_port, req.path);
}
auto &resp = downstream->response();
resp.http_status = 308;
resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
http2::HD_LOCATION);
resp.fs.add_header_token(StringRef::from_lit("connection"),
StringRef::from_lit("close"), false,
http2::HD_CONNECTION);
return send_reply(downstream, nullptr, 0);
}
void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const { void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const {
std::string nhdrs; std::string nhdrs;
for (auto chunk = buf->head; chunk; chunk = chunk->next) { for (auto chunk = buf->head; chunk; chunk = chunk->next) {
@ -1267,11 +1321,13 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
downstream_->add_retry(); downstream_->add_retry();
rv = 0;
if (no_retry || downstream_->no_more_retry()) { if (no_retry || downstream_->no_more_retry()) {
goto fail; goto fail;
} }
dconn = handler_->get_downstream_connection(downstream_.get()); dconn = handler_->get_downstream_connection(rv, downstream_.get());
if (!dconn) { if (!dconn) {
goto fail; goto fail;
} }
@ -1289,7 +1345,12 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
return 0; return 0;
fail: fail:
if (on_downstream_abort_request(downstream_.get(), 503) != 0) { if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = on_downstream_abort_request(downstream_.get(), 503);
}
if (rv != 0) {
return -1; return -1;
} }
downstream_->pop_downstream_connection(); downstream_->pop_downstream_connection();

View File

@ -50,6 +50,8 @@ public:
virtual int on_event(); virtual int on_event();
virtual int on_downstream_abort_request(Downstream *downstream, virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code); unsigned int status_code);
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
virtual ClientHandler *get_client_handler() const; virtual ClientHandler *get_client_handler() const;
virtual int downstream_read(DownstreamConnection *dconn); virtual int downstream_read(DownstreamConnection *dconn);
@ -91,6 +93,7 @@ public:
void reset_current_header_length(); void reset_current_header_length();
void log_response_headers(DefaultMemchunks *buf) const; void log_response_headers(DefaultMemchunks *buf) const;
int redirect_to_https(Downstream *downstream);
private: private:
ClientHandler *handler_; ClientHandler *handler_;

View File

@ -370,7 +370,7 @@ void SpdyUpstream::start_downstream(Downstream *downstream) {
void SpdyUpstream::initiate_downstream(Downstream *downstream) { void SpdyUpstream::initiate_downstream(Downstream *downstream) {
int rv; int rv;
auto dconn = handler_->get_downstream_connection(downstream); auto dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn || if (!dconn ||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { (rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
@ -1265,6 +1265,13 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
return 0; return 0;
} }
int SpdyUpstream::on_downstream_abort_request_with_https_redirect(
Downstream *downstream) {
// This should not be called since SPDY is only available with TLS.
assert(0);
return 0;
}
int SpdyUpstream::consume(int32_t stream_id, size_t len) { int SpdyUpstream::consume(int32_t stream_id, size_t len) {
int rv; int rv;
@ -1345,7 +1352,7 @@ int SpdyUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
// downstream connection is clean; we can retry with new // downstream connection is clean; we can retry with new
// downstream connection. // downstream connection.
dconn = handler_->get_downstream_connection(downstream); dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn) { if (!dconn) {
goto fail; goto fail;
} }

View File

@ -51,6 +51,8 @@ public:
virtual int on_timeout(Downstream *downstream); virtual int on_timeout(Downstream *downstream);
virtual int on_downstream_abort_request(Downstream *downstream, virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code); unsigned int status_code);
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
virtual ClientHandler *get_client_handler() const; virtual ClientHandler *get_client_handler() const;
virtual int downstream_read(DownstreamConnection *dconn); virtual int downstream_read(DownstreamConnection *dconn);
virtual int downstream_write(DownstreamConnection *dconn); virtual int downstream_write(DownstreamConnection *dconn);

View File

@ -45,6 +45,10 @@ public:
virtual int on_timeout(Downstream *downstream) { return 0; }; virtual int on_timeout(Downstream *downstream) { return 0; };
virtual int on_downstream_abort_request(Downstream *downstream, virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code) = 0; unsigned int status_code) = 0;
// Called when the current request is aborted without forwarding it
// to backend, and it should be redirected to https URI.
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 0;
virtual int downstream_read(DownstreamConnection *dconn) = 0; virtual int downstream_read(DownstreamConnection *dconn) = 0;
virtual int downstream_write(DownstreamConnection *dconn) = 0; virtual int downstream_write(DownstreamConnection *dconn) = 0;
virtual int downstream_eof(DownstreamConnection *dconn) = 0; virtual int downstream_eof(DownstreamConnection *dconn) = 0;

View File

@ -77,7 +77,7 @@ bool match_shared_downstream_addr(
} }
if (lhs->affinity != rhs->affinity || if (lhs->affinity != rhs->affinity ||
lhs->require_upstream_tls != rhs->require_upstream_tls) { lhs->redirect_if_not_tls != rhs->redirect_if_not_tls) {
return false; return false;
} }
@ -192,7 +192,7 @@ void Worker::replace_downstream_config(
shared_addr->addrs.resize(src.addrs.size()); shared_addr->addrs.resize(src.addrs.size());
shared_addr->affinity = src.affinity; shared_addr->affinity = src.affinity;
shared_addr->affinity_hash = src.affinity_hash; shared_addr->affinity_hash = src.affinity_hash;
shared_addr->require_upstream_tls = src.require_upstream_tls; shared_addr->redirect_if_not_tls = src.redirect_if_not_tls;
size_t num_http1 = 0; size_t num_http1 = 0;
size_t num_http2 = 0; size_t num_http2 = 0;

View File

@ -137,7 +137,7 @@ struct SharedDownstreamAddr {
http1_pri{}, http1_pri{},
http2_pri{}, http2_pri{},
affinity{AFFINITY_NONE}, affinity{AFFINITY_NONE},
require_upstream_tls{false} {} redirect_if_not_tls{false} {}
SharedDownstreamAddr(const SharedDownstreamAddr &) = delete; SharedDownstreamAddr(const SharedDownstreamAddr &) = delete;
SharedDownstreamAddr(SharedDownstreamAddr &&) = delete; SharedDownstreamAddr(SharedDownstreamAddr &&) = delete;
@ -172,8 +172,9 @@ struct SharedDownstreamAddr {
WeightedPri http2_pri; WeightedPri http2_pri;
// Session affinity // Session affinity
shrpx_session_affinity affinity; shrpx_session_affinity affinity;
// true if this group requires that client connection must be TLS. // true if this group requires that client connection must be TLS,
bool require_upstream_tls; // and the request must be redirected to https URI.
bool redirect_if_not_tls;
}; };
struct DownstreamAddrGroup { struct DownstreamAddrGroup {

View File

@ -1432,6 +1432,26 @@ StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) {
return StringRef{iov.base, p}; return StringRef{iov.base, p};
} }
StringRef extract_host(const StringRef &hostport) {
if (hostport[0] == '[') {
// assume this is IPv6 numeric address
auto p = std::find(std::begin(hostport), std::end(hostport), ']');
if (p == std::end(hostport)) {
return StringRef{};
}
if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
return StringRef{};
}
return StringRef{std::begin(hostport), p + 1};
}
auto p = std::find(std::begin(hostport), std::end(hostport), ':');
if (p == std::begin(hostport)) {
return StringRef{};
}
return StringRef{std::begin(hostport), p};
}
} // namespace util } // namespace util
} // namespace nghttp2 } // namespace nghttp2

View File

@ -739,6 +739,11 @@ uint32_t hash32(const StringRef &s);
// returns 0 if it succeeds, or -1. // returns 0 if it succeeds, or -1.
int sha256(uint8_t *buf, const StringRef &s); int sha256(uint8_t *buf, const StringRef &s);
// Returns host from |hostport|. If host cannot be found in
// |hostport|, returns empty string. The returned string might not be
// NULL-terminated.
StringRef extract_host(const StringRef &hostport);
} // namespace util } // namespace util
} // namespace nghttp2 } // namespace nghttp2

View File

@ -609,4 +609,22 @@ void test_util_decode_hex(void) {
CU_ASSERT("" == util::decode_hex(balloc, StringRef{})); CU_ASSERT("" == util::decode_hex(balloc, StringRef{}));
} }
void test_util_extract_host(void) {
CU_ASSERT(StringRef::from_lit("foo") ==
util::extract_host(StringRef::from_lit("foo")));
CU_ASSERT(StringRef::from_lit("foo") ==
util::extract_host(StringRef::from_lit("foo:")));
CU_ASSERT(StringRef::from_lit("foo") ==
util::extract_host(StringRef::from_lit("foo:0")));
CU_ASSERT(StringRef::from_lit("[::1]") ==
util::extract_host(StringRef::from_lit("[::1]")));
CU_ASSERT(StringRef::from_lit("[::1]") ==
util::extract_host(StringRef::from_lit("[::1]:")));
CU_ASSERT(util::extract_host(StringRef::from_lit(":foo")).empty());
CU_ASSERT(util::extract_host(StringRef::from_lit("[::1")).empty());
CU_ASSERT(util::extract_host(StringRef::from_lit("[::1]0")).empty());
CU_ASSERT(util::extract_host(StringRef{}).empty());
}
} // namespace shrpx } // namespace shrpx

View File

@ -66,6 +66,7 @@ void test_util_random_alpha_digit(void);
void test_util_format_hex(void); void test_util_format_hex(void);
void test_util_is_hex_string(void); void test_util_is_hex_string(void);
void test_util_decode_hex(void); void test_util_decode_hex(void);
void test_util_extract_host(void);
} // namespace shrpx } // namespace shrpx