Merge pull request #819 from nghttp2/nghttpx-https-redirect
nghttpx: Redirect to HTTPS URI with redirect-if-no-tls parameter in backend option
This commit is contained in:
commit
0797e89a90
|
@ -160,6 +160,7 @@ OPTIONS = [
|
|||
"accesslog-write-early",
|
||||
"tls-min-proto-version",
|
||||
"tls-max-proto-version",
|
||||
"redirect-https-port",
|
||||
]
|
||||
|
||||
LOGVARS = [
|
||||
|
|
|
@ -533,6 +533,49 @@ func TestH1H1RespPhaseReturn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestH1H1HTTPSRedirect tests that the request to the backend which
|
||||
// requires TLS is redirected to https URI.
|
||||
func TestH1H1HTTPSRedirect(t *testing.T) {
|
||||
st := newServerTester([]string{"--redirect-if-not-tls"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H1HTTPSRedirect",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 308; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want {
|
||||
t.Errorf("location: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1HTTPSRedirectPort tests that the request to the backend
|
||||
// which requires TLS is redirected to https URI with given port.
|
||||
func TestH1H1HTTPSRedirectPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--redirect-if-not-tls", "--redirect-https-port=8443"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
path: "/foo?bar",
|
||||
name: "TestH1H1HTTPSRedirectPort",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 308; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want {
|
||||
t.Errorf("location: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// // TestH1H2ConnectFailure tests that server handles the situation that
|
||||
// // connection attempt to HTTP/2 backend failed.
|
||||
// func TestH1H2ConnectFailure(t *testing.T) {
|
||||
|
|
|
@ -1405,6 +1405,49 @@ func TestH2H1DNS(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestH2H1HTTPSRedirect tests that the request to the backend which
|
||||
// requires TLS is redirected to https URI.
|
||||
func TestH2H1HTTPSRedirect(t *testing.T) {
|
||||
st := newServerTester([]string{"--redirect-if-not-tls"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1HTTPSRedirect",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 308; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want {
|
||||
t.Errorf("location: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1HTTPSRedirectPort tests that the request to the backend
|
||||
// which requires TLS is redirected to https URI with given port.
|
||||
func TestH2H1HTTPSRedirectPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--redirect-if-not-tls", "--redirect-https-port=8443"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
path: "/foo?bar",
|
||||
name: "TestH2H1HTTPSRedirectPort",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 308; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want {
|
||||
t.Errorf("location: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1GracefulShutdown tests graceful shutdown.
|
||||
func TestH2H1GracefulShutdown(t *testing.T) {
|
||||
st := newServerTester(nil, t, noopHandler)
|
||||
|
|
|
@ -101,10 +101,8 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
|
|||
|
||||
args := []string{}
|
||||
|
||||
backendTLS := false
|
||||
dns := false
|
||||
externalDNS := false
|
||||
acceptProxyProtocol := false
|
||||
var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS bool
|
||||
|
||||
for _, k := range src_args {
|
||||
switch k {
|
||||
case "--http2-bridge":
|
||||
|
@ -116,6 +114,8 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
|
|||
externalDNS = true
|
||||
case "--accept-proxy-protocol":
|
||||
acceptProxyProtocol = true
|
||||
case "--redirect-if-not-tls":
|
||||
redirectIfNotTLS = true
|
||||
default:
|
||||
args = append(args, k)
|
||||
}
|
||||
|
@ -164,6 +164,10 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
|
|||
b += ";dns"
|
||||
}
|
||||
|
||||
if redirectIfNotTLS {
|
||||
b += ";redirect-if-not-tls"
|
||||
}
|
||||
|
||||
noTLS := ";no-tls"
|
||||
if frontendTLS {
|
||||
noTLS = ""
|
||||
|
|
|
@ -185,6 +185,8 @@ int main(int argc, char *argv[]) {
|
|||
!CU_add_test(pSuite, "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_extract_host",
|
||||
shrpx::test_util_extract_host) ||
|
||||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
||||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
||||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
||||
|
|
46
src/shrpx.cc
46
src/shrpx.cc
|
@ -1447,6 +1447,7 @@ void fill_default_config(Config *config) {
|
|||
httpconf.max_request_header_fields = 100;
|
||||
httpconf.response_header_field_buffer = 64_k;
|
||||
httpconf.max_response_header_fields = 500;
|
||||
httpconf.redirect_https_port = StringRef::from_lit("443");
|
||||
|
||||
auto &http2conf = config->http2;
|
||||
{
|
||||
|
@ -1678,12 +1679,12 @@ Connections:
|
|||
The parameters are delimited by ";". The available
|
||||
parameters are: "proto=<PROTO>", "tls",
|
||||
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
|
||||
"affinity=<METHOD>", "dns", and "frontend-tls". The
|
||||
parameter consists of keyword, and optionally followed
|
||||
by "=" and value. For example, the parameter "proto=h2"
|
||||
consists of the keyword "proto" and value "h2". The
|
||||
parameter "tls" consists of the keyword "tls" without
|
||||
value. Each parameter is described as follows.
|
||||
"affinity=<METHOD>", "dns", and "redirect-if-not-tls".
|
||||
The parameter consists of keyword, and optionally
|
||||
followed by "=" and value. For example, the parameter
|
||||
"proto=h2" consists of the keyword "proto" and value
|
||||
"h2". The parameter "tls" consists of the keyword "tls"
|
||||
without value. Each parameter is described as follows.
|
||||
|
||||
The backend application protocol can be specified using
|
||||
optional "proto" parameter, and in the form of
|
||||
|
@ -1740,16 +1741,18 @@ Connections:
|
|||
backend host name at start up, or reloading
|
||||
configuration is skipped.
|
||||
|
||||
If "frontend-tls" parameter is used, the matched backend
|
||||
requires frontend TLS connection. In other words, even
|
||||
if pattern is matched, frontend connection is not TLS
|
||||
protected, the request is forwarded to one of catch-all
|
||||
backends. For this reason, catch-all backend cannot
|
||||
have "frontend-tls" parameter. If at least one backend
|
||||
has "frontend-tls" parameter, this feature is enabled
|
||||
for all backend servers sharing the same <PATTERN>. It
|
||||
is advised to set "frontend-tls" parameter to all
|
||||
backends explicitly if this feature is desired.
|
||||
If "redirect-if-not-tls" parameter is used, the matched
|
||||
backend requires that frontend connection is TLS
|
||||
encrypted. If it isn't, nghttpx responds to the request
|
||||
with 308 status code, and https URI the client should
|
||||
use instead is included in Location header field. The
|
||||
port number in redirect URI is 443 by default, and can
|
||||
be changed using --redirect-https-port option. If at
|
||||
least one backend has "redirect-if-not-tls" parameter,
|
||||
this feature is enabled for all backend servers sharing
|
||||
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
|
||||
not contain these characters. Since ";" has special
|
||||
|
@ -2545,6 +2548,12 @@ HTTP:
|
|||
Don't rewrite server header field in default mode. When
|
||||
--http2-proxy is used, these headers will not be altered
|
||||
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-max-request-body=<SIZE>
|
||||
|
@ -3253,6 +3262,7 @@ int main(int argc, char **argv) {
|
|||
152},
|
||||
{SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag,
|
||||
153},
|
||||
{SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
int option_index = 0;
|
||||
|
@ -3975,6 +3985,10 @@ int main(int argc, char **argv) {
|
|||
cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION,
|
||||
StringRef{optarg});
|
||||
break;
|
||||
case 154:
|
||||
// --redirect-https-port
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -961,7 +961,7 @@ uint32_t next_cycle(const WeightedPri &pri) {
|
|||
} // namespace
|
||||
|
||||
std::unique_ptr<DownstreamConnection>
|
||||
ClientHandler::get_downstream_connection(Downstream *downstream) {
|
||||
ClientHandler::get_downstream_connection(int &err, Downstream *downstream) {
|
||||
size_t group_idx;
|
||||
auto &downstreamconf = *worker_->get_downstream_config();
|
||||
auto &routerconf = downstreamconf.router;
|
||||
|
@ -971,6 +971,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
|
||||
const auto &req = downstream->request();
|
||||
|
||||
err = 0;
|
||||
|
||||
switch (faddr_->alt_mode) {
|
||||
case ALTMODE_API:
|
||||
return make_unique<APIDownstreamConnection>(worker_);
|
||||
|
@ -1009,11 +1011,13 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
|
||||
}
|
||||
|
||||
if (groups[group_idx]->shared_addr->require_upstream_tls && !conn_.tls.ssl) {
|
||||
CLOG(INFO, this) << "Downstream address group " << group_idx
|
||||
<< " requires frontend TLS connection. Send request to "
|
||||
"catch-all group.";
|
||||
group_idx = catch_all;
|
||||
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
|
||||
<< " requires frontend TLS connection.";
|
||||
}
|
||||
err = SHRPX_ERR_TLS_REQUIRED;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto &group = groups[group_idx];
|
||||
|
@ -1087,6 +1091,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
CLOG(INFO, this) << "No working downstream address found";
|
||||
}
|
||||
|
||||
err = -1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1099,6 +1104,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
auto http2session = select_http2_session(group);
|
||||
|
||||
if (http2session == nullptr) {
|
||||
err = -1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,8 +99,12 @@ public:
|
|||
|
||||
void pool_downstream_connection(std::unique_ptr<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>
|
||||
get_downstream_connection(Downstream *downstream);
|
||||
get_downstream_connection(int &err, Downstream *downstream);
|
||||
MemchunkPool *get_mcpool();
|
||||
SSL *get_ssl() const;
|
||||
// Call this function when HTTP/2 connection header is received at
|
||||
|
|
|
@ -747,7 +747,7 @@ struct DownstreamParams {
|
|||
shrpx_session_affinity affinity;
|
||||
bool tls;
|
||||
bool dns;
|
||||
bool frontend_tls;
|
||||
bool redirect_if_not_tls;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
@ -823,8 +823,8 @@ int parse_downstream_params(DownstreamParams &out,
|
|||
}
|
||||
} else if (util::strieq_l("dns", param)) {
|
||||
out.dns = true;
|
||||
} else if (util::strieq_l("frontend-tls", param)) {
|
||||
out.frontend_tls = true;
|
||||
} else if (util::strieq_l("redirect-if-not-tls", param)) {
|
||||
out.redirect_if_not_tls = true;
|
||||
} else if (!param.empty()) {
|
||||
LOG(ERROR) << "backend: " << param << ": unknown keyword";
|
||||
return -1;
|
||||
|
@ -919,8 +919,8 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
|
|||
}
|
||||
// If at least one backend requires frontend TLS connection,
|
||||
// enable it for all backends sharing the same pattern.
|
||||
if (params.frontend_tls) {
|
||||
g.require_upstream_tls = true;
|
||||
if (params.redirect_if_not_tls) {
|
||||
g.redirect_if_not_tls = true;
|
||||
}
|
||||
g.addrs.push_back(addr);
|
||||
done = true;
|
||||
|
@ -936,7 +936,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
|
|||
auto &g = addr_groups.back();
|
||||
g.addrs.push_back(addr);
|
||||
g.affinity = params.affinity;
|
||||
g.require_upstream_tls = params.frontend_tls;
|
||||
g.redirect_if_not_tls = params.redirect_if_not_tls;
|
||||
|
||||
if (pattern[0] == '*') {
|
||||
// wildcard pattern
|
||||
|
@ -1753,6 +1753,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
}
|
||||
break;
|
||||
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)) {
|
||||
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);
|
||||
case SHRPX_OPTID_TLS_MAX_PROTO_VERSION:
|
||||
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:
|
||||
LOG(WARN) << "conf: ignored";
|
||||
|
||||
|
@ -3663,12 +3676,6 @@ int configure_downstream_group(Config *config, bool http2_proxy,
|
|||
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;
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
|
|
@ -331,6 +331,8 @@ constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION =
|
|||
StringRef::from_lit("tls-min-proto-version");
|
||||
constexpr auto SHRPX_OPT_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;
|
||||
|
||||
|
@ -434,9 +436,7 @@ struct AffinityHash {
|
|||
|
||||
struct DownstreamAddrGroupConfig {
|
||||
DownstreamAddrGroupConfig(const StringRef &pattern)
|
||||
: pattern(pattern),
|
||||
affinity(AFFINITY_NONE),
|
||||
require_upstream_tls(false) {}
|
||||
: pattern(pattern), affinity(AFFINITY_NONE), redirect_if_not_tls(false) {}
|
||||
|
||||
StringRef pattern;
|
||||
std::vector<DownstreamAddrConfig> addrs;
|
||||
|
@ -445,8 +445,9 @@ struct DownstreamAddrGroupConfig {
|
|||
std::vector<AffinityHash> affinity_hash;
|
||||
// Session affinity
|
||||
shrpx_session_affinity affinity;
|
||||
// true if this group requires that client connection must be TLS.
|
||||
bool require_upstream_tls;
|
||||
// true if this group requires that client connection must be TLS,
|
||||
// and the request must be redirected to https URI.
|
||||
bool redirect_if_not_tls;
|
||||
};
|
||||
|
||||
struct TicketKey {
|
||||
|
@ -639,6 +640,9 @@ struct HttpConfig {
|
|||
HeaderRefs add_request_headers;
|
||||
HeaderRefs add_response_headers;
|
||||
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 max_request_header_fields;
|
||||
size_t response_header_field_buffer;
|
||||
|
@ -1016,6 +1020,7 @@ enum {
|
|||
SHRPX_OPTID_PSK_SECRETS,
|
||||
SHRPX_OPTID_READ_BURST,
|
||||
SHRPX_OPTID_READ_RATE,
|
||||
SHRPX_OPTID_REDIRECT_HTTPS_PORT,
|
||||
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
|
||||
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
|
||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||
|
|
|
@ -38,6 +38,7 @@ enum ErrorCode {
|
|||
SHRPX_ERR_INPROGRESS = -102,
|
||||
SHRPX_ERR_DCONN_CANCELED = -103,
|
||||
SHRPX_ERR_RETRY = -104,
|
||||
SHRPX_ERR_TLS_REQUIRED = -105,
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -434,9 +434,25 @@ void Http2Upstream::start_downstream(Downstream *downstream) {
|
|||
void Http2Upstream::initiate_downstream(Downstream *downstream) {
|
||||
int rv;
|
||||
|
||||
auto dconn = handler_->get_downstream_connection(downstream);
|
||||
if (!dconn ||
|
||||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
|
||||
auto dconn = handler_->get_downstream_connection(rv, downstream);
|
||||
if (!dconn) {
|
||||
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
|
||||
if (error_reply(downstream, 503) != 0) {
|
||||
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||
|
@ -1792,6 +1808,52 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
|||
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 rv;
|
||||
|
||||
|
@ -1879,6 +1941,8 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
|
|||
|
||||
std::unique_ptr<DownstreamConnection> dconn;
|
||||
|
||||
rv = 0;
|
||||
|
||||
if (no_retry || downstream->no_more_retry()) {
|
||||
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.
|
||||
|
||||
dconn = handler_->get_downstream_connection(downstream);
|
||||
dconn = handler_->get_downstream_connection(rv, downstream);
|
||||
if (!dconn) {
|
||||
goto fail;
|
||||
}
|
||||
|
@ -1904,7 +1968,12 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
|
|||
return 0;
|
||||
|
||||
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);
|
||||
}
|
||||
downstream->pop_downstream_connection();
|
||||
|
|
|
@ -54,6 +54,8 @@ public:
|
|||
virtual int on_timeout(Downstream *downstream);
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
virtual int
|
||||
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
|
@ -120,6 +122,8 @@ public:
|
|||
|
||||
size_t get_max_buffer_size() const;
|
||||
|
||||
int redirect_to_https(Downstream *downstream);
|
||||
|
||||
private:
|
||||
DefaultMemchunks wb_;
|
||||
std::unique_ptr<HttpsUpstream> pre_upstream_;
|
||||
|
|
|
@ -89,7 +89,8 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
|
||||
downstream->pop_downstream_connection();
|
||||
|
||||
auto ndconn = handler->get_downstream_connection(downstream);
|
||||
int rv;
|
||||
auto ndconn = handler->get_downstream_connection(rv, downstream);
|
||||
if (ndconn) {
|
||||
if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) {
|
||||
return;
|
||||
|
@ -98,7 +99,13 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +139,8 @@ void backend_retry(Downstream *downstream) {
|
|||
|
||||
downstream->pop_downstream_connection();
|
||||
|
||||
auto ndconn = handler->get_downstream_connection(downstream);
|
||||
int rv;
|
||||
auto ndconn = handler->get_downstream_connection(rv, downstream);
|
||||
if (ndconn) {
|
||||
if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) {
|
||||
return;
|
||||
|
@ -141,7 +149,13 @@ void backend_retry(Downstream *downstream) {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,10 +421,18 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto dconn = handler->get_downstream_connection(downstream);
|
||||
auto dconn = handler->get_downstream_connection(rv, downstream);
|
||||
|
||||
if (!dconn ||
|
||||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
|
||||
if (!dconn) {
|
||||
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);
|
||||
|
||||
return -1;
|
||||
|
@ -1225,6 +1233,52 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
|
|||
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 {
|
||||
std::string nhdrs;
|
||||
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();
|
||||
|
||||
rv = 0;
|
||||
|
||||
if (no_retry || downstream_->no_more_retry()) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dconn = handler_->get_downstream_connection(downstream_.get());
|
||||
dconn = handler_->get_downstream_connection(rv, downstream_.get());
|
||||
if (!dconn) {
|
||||
goto fail;
|
||||
}
|
||||
|
@ -1289,7 +1345,12 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
|
|||
return 0;
|
||||
|
||||
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;
|
||||
}
|
||||
downstream_->pop_downstream_connection();
|
||||
|
|
|
@ -50,6 +50,8 @@ public:
|
|||
virtual int on_event();
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
virtual int
|
||||
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
|
@ -91,6 +93,7 @@ public:
|
|||
|
||||
void reset_current_header_length();
|
||||
void log_response_headers(DefaultMemchunks *buf) const;
|
||||
int redirect_to_https(Downstream *downstream);
|
||||
|
||||
private:
|
||||
ClientHandler *handler_;
|
||||
|
|
|
@ -370,7 +370,7 @@ void SpdyUpstream::start_downstream(Downstream *downstream) {
|
|||
void SpdyUpstream::initiate_downstream(Downstream *downstream) {
|
||||
int rv;
|
||||
|
||||
auto dconn = handler_->get_downstream_connection(downstream);
|
||||
auto dconn = handler_->get_downstream_connection(rv, downstream);
|
||||
|
||||
if (!dconn ||
|
||||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
|
||||
|
@ -1265,6 +1265,13 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
|
|||
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 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.
|
||||
|
||||
dconn = handler_->get_downstream_connection(downstream);
|
||||
dconn = handler_->get_downstream_connection(rv, downstream);
|
||||
if (!dconn) {
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
virtual int on_timeout(Downstream *downstream);
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
virtual int
|
||||
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
virtual int downstream_write(DownstreamConnection *dconn);
|
||||
|
|
|
@ -45,6 +45,10 @@ public:
|
|||
virtual int on_timeout(Downstream *downstream) { return 0; };
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
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_write(DownstreamConnection *dconn) = 0;
|
||||
virtual int downstream_eof(DownstreamConnection *dconn) = 0;
|
||||
|
|
|
@ -77,7 +77,7 @@ bool match_shared_downstream_addr(
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ void Worker::replace_downstream_config(
|
|||
shared_addr->addrs.resize(src.addrs.size());
|
||||
shared_addr->affinity = src.affinity;
|
||||
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_http2 = 0;
|
||||
|
|
|
@ -137,7 +137,7 @@ struct SharedDownstreamAddr {
|
|||
http1_pri{},
|
||||
http2_pri{},
|
||||
affinity{AFFINITY_NONE},
|
||||
require_upstream_tls{false} {}
|
||||
redirect_if_not_tls{false} {}
|
||||
|
||||
SharedDownstreamAddr(const SharedDownstreamAddr &) = delete;
|
||||
SharedDownstreamAddr(SharedDownstreamAddr &&) = delete;
|
||||
|
@ -172,8 +172,9 @@ struct SharedDownstreamAddr {
|
|||
WeightedPri http2_pri;
|
||||
// Session affinity
|
||||
shrpx_session_affinity affinity;
|
||||
// true if this group requires that client connection must be TLS.
|
||||
bool require_upstream_tls;
|
||||
// true if this group requires that client connection must be TLS,
|
||||
// and the request must be redirected to https URI.
|
||||
bool redirect_if_not_tls;
|
||||
};
|
||||
|
||||
struct DownstreamAddrGroup {
|
||||
|
|
20
src/util.cc
20
src/util.cc
|
@ -1432,6 +1432,26 @@ StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) {
|
|||
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 nghttp2
|
||||
|
|
|
@ -739,6 +739,11 @@ uint32_t hash32(const StringRef &s);
|
|||
// returns 0 if it succeeds, or -1.
|
||||
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 nghttp2
|
||||
|
|
|
@ -609,4 +609,22 @@ void test_util_decode_hex(void) {
|
|||
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
|
||||
|
|
|
@ -66,6 +66,7 @@ void test_util_random_alpha_digit(void);
|
|||
void test_util_format_hex(void);
|
||||
void test_util_is_hex_string(void);
|
||||
void test_util_decode_hex(void);
|
||||
void test_util_extract_host(void);
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
|
|
Loading…
Reference in New Issue