nghttpx: Add --require-http-scheme option
This commit is contained in:
parent
a67822b382
commit
79524471b4
|
@ -198,6 +198,7 @@ OPTIONS = [
|
||||||
"max-worker-processes",
|
"max-worker-processes",
|
||||||
"worker-process-grace-shutdown-period",
|
"worker-process-grace-shutdown-period",
|
||||||
"frontend-quic-initial-rtt",
|
"frontend-quic-initial-rtt",
|
||||||
|
"require-http-scheme",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGVARS = [
|
LOGVARS = [
|
||||||
|
|
|
@ -2870,3 +2870,88 @@ func TestH2H1ChunkedEndsPrematurely(t *testing.T) {
|
||||||
t.Errorf("res.errCode = %v; want %v", got, want)
|
t.Errorf("res.errCode = %v; want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption verifies that https
|
||||||
|
// scheme in non-encrypted connection is treated as error.
|
||||||
|
func TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--require-http-scheme"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Errorf("server should not forward this request")
|
||||||
|
})
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption",
|
||||||
|
scheme: "https",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := res.status, 400; got != want {
|
||||||
|
t.Errorf("status = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestH2H1RequireHTTPSchemeHTTPWithEncryption verifies that http
|
||||||
|
// scheme in encrypted connection is treated as error.
|
||||||
|
func TestH2H1RequireHTTPSchemeHTTPWithEncryption(t *testing.T) {
|
||||||
|
st := newServerTesterTLS([]string{"--require-http-scheme"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Errorf("server should not forward this request")
|
||||||
|
})
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1RequireHTTPSchemeHTTPWithEncryption",
|
||||||
|
scheme: "http",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := res.status, 400; got != want {
|
||||||
|
t.Errorf("status = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption verifies
|
||||||
|
// that unknown scheme in non-encrypted connection is treated as
|
||||||
|
// error.
|
||||||
|
func TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--require-http-scheme"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Errorf("server should not forward this request")
|
||||||
|
})
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption",
|
||||||
|
scheme: "unknown",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := res.status, 400; got != want {
|
||||||
|
t.Errorf("status = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption verifies that
|
||||||
|
// unknown scheme in encrypted connection is treated as error.
|
||||||
|
func TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption(t *testing.T) {
|
||||||
|
st := newServerTesterTLS([]string{"--require-http-scheme"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Errorf("server should not forward this request")
|
||||||
|
})
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption",
|
||||||
|
scheme: "unknown",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := res.status, 400; got != want {
|
||||||
|
t.Errorf("status = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -137,6 +137,8 @@ int main(int argc, char *argv[]) {
|
||||||
shrpx::test_shrpx_http_create_affinity_cookie) ||
|
shrpx::test_shrpx_http_create_affinity_cookie) ||
|
||||||
!CU_add_test(pSuite, "http_create_atlsvc_header_field_value",
|
!CU_add_test(pSuite, "http_create_atlsvc_header_field_value",
|
||||||
shrpx::test_shrpx_http_create_altsvc_header_value) ||
|
shrpx::test_shrpx_http_create_altsvc_header_value) ||
|
||||||
|
!CU_add_test(pSuite, "http_check_http_scheme",
|
||||||
|
shrpx::test_shrpx_http_check_http_scheme) ||
|
||||||
!CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
|
!CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
|
||||||
!CU_add_test(pSuite, "router_match_wildcard",
|
!CU_add_test(pSuite, "router_match_wildcard",
|
||||||
shrpx::test_shrpx_router_match_wildcard) ||
|
shrpx::test_shrpx_router_match_wildcard) ||
|
||||||
|
|
13
src/shrpx.cc
13
src/shrpx.cc
|
@ -3237,6 +3237,13 @@ HTTP:
|
||||||
"redirect-if-not-tls" parameter in --backend option.
|
"redirect-if-not-tls" parameter in --backend option.
|
||||||
Default: )"
|
Default: )"
|
||||||
<< config->http.redirect_https_port << R"(
|
<< config->http.redirect_https_port << R"(
|
||||||
|
--require-http-scheme
|
||||||
|
Always require http or https scheme in HTTP request. It
|
||||||
|
also requires that https scheme must be used for an
|
||||||
|
encrypted connection. Otherwise, http scheme must be
|
||||||
|
used. This option is recommended for a server
|
||||||
|
deployment which directly faces clients and the services
|
||||||
|
it provides only require http or https scheme.
|
||||||
|
|
||||||
API:
|
API:
|
||||||
--api-max-request-body=<SIZE>
|
--api-max-request-body=<SIZE>
|
||||||
|
@ -4238,6 +4245,7 @@ int main(int argc, char **argv) {
|
||||||
required_argument, &flag, 189},
|
required_argument, &flag, 189},
|
||||||
{SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag,
|
{SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag,
|
||||||
190},
|
190},
|
||||||
|
{SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -5142,6 +5150,11 @@ int main(int argc, char **argv) {
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT,
|
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT,
|
||||||
StringRef{optarg});
|
StringRef{optarg});
|
||||||
break;
|
break;
|
||||||
|
case 191:
|
||||||
|
// --require-http-scheme
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_REQUIRE_HTTP_SCHEME,
|
||||||
|
StringRef::from_lit("yes"));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2210,6 +2210,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
if (util::strieq_l("no-location-rewrit", name, 18)) {
|
if (util::strieq_l("no-location-rewrit", name, 18)) {
|
||||||
return SHRPX_OPTID_NO_LOCATION_REWRITE;
|
return SHRPX_OPTID_NO_LOCATION_REWRITE;
|
||||||
}
|
}
|
||||||
|
if (util::strieq_l("require-http-schem", name, 18)) {
|
||||||
|
return SHRPX_OPTID_REQUIRE_HTTP_SCHEME;
|
||||||
|
}
|
||||||
if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
|
if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
|
||||||
return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
|
return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
|
||||||
}
|
}
|
||||||
|
@ -4166,6 +4169,9 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
case SHRPX_OPTID_REQUIRE_HTTP_SCHEME:
|
||||||
|
config->http.require_http_scheme = util::strieq_l("yes", optarg);
|
||||||
|
return 0;
|
||||||
case SHRPX_OPTID_CONF:
|
case SHRPX_OPTID_CONF:
|
||||||
LOG(WARN) << "conf: ignored";
|
LOG(WARN) << "conf: ignored";
|
||||||
|
|
||||||
|
|
|
@ -401,6 +401,8 @@ constexpr auto SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD =
|
||||||
StringRef::from_lit("worker-process-grace-shutdown-period");
|
StringRef::from_lit("worker-process-grace-shutdown-period");
|
||||||
constexpr auto SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT =
|
constexpr auto SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT =
|
||||||
StringRef::from_lit("frontend-quic-initial-rtt");
|
StringRef::from_lit("frontend-quic-initial-rtt");
|
||||||
|
constexpr auto SHRPX_OPT_REQUIRE_HTTP_SCHEME =
|
||||||
|
StringRef::from_lit("require-http-scheme");
|
||||||
|
|
||||||
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -857,6 +859,7 @@ struct HttpConfig {
|
||||||
bool no_location_rewrite;
|
bool no_location_rewrite;
|
||||||
bool no_host_rewrite;
|
bool no_host_rewrite;
|
||||||
bool no_server_rewrite;
|
bool no_server_rewrite;
|
||||||
|
bool require_http_scheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Http2Config {
|
struct Http2Config {
|
||||||
|
@ -1295,6 +1298,7 @@ enum {
|
||||||
SHRPX_OPTID_READ_RATE,
|
SHRPX_OPTID_READ_RATE,
|
||||||
SHRPX_OPTID_REDIRECT_HTTPS_PORT,
|
SHRPX_OPTID_REDIRECT_HTTPS_PORT,
|
||||||
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
|
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
|
||||||
|
SHRPX_OPTID_REQUIRE_HTTP_SCHEME,
|
||||||
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
|
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
|
||||||
SHRPX_OPTID_RLIMIT_MEMLOCK,
|
SHRPX_OPTID_RLIMIT_MEMLOCK,
|
||||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||||
|
|
|
@ -271,6 +271,10 @@ StringRef create_altsvc_header_value(BlockAllocator &balloc,
|
||||||
return StringRef{iov.base, p};
|
return StringRef{iov.base, p};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_http_scheme(const StringRef &scheme, bool encrypted) {
|
||||||
|
return encrypted ? scheme == "https" : scheme == "http";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -83,6 +83,12 @@ bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
|
||||||
StringRef create_altsvc_header_value(BlockAllocator &balloc,
|
StringRef create_altsvc_header_value(BlockAllocator &balloc,
|
||||||
const std::vector<AltSvc> &altsvcs);
|
const std::vector<AltSvc> &altsvcs);
|
||||||
|
|
||||||
|
// Returns true if either of the following conditions holds:
|
||||||
|
// - scheme is https and encrypted is true
|
||||||
|
// - scheme is http and encrypted is false
|
||||||
|
// Otherwise returns false.
|
||||||
|
bool check_http_scheme(const StringRef &scheme, bool encrypted);
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -419,10 +419,16 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
||||||
|
|
||||||
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
||||||
|
|
||||||
|
if (config->http.require_http_scheme &&
|
||||||
|
!http::check_http_scheme(req.scheme, handler_->get_ssl() != nullptr)) {
|
||||||
|
if (error_reply(downstream, 400) != 0) {
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
auto upstream = downstream->get_upstream();
|
auto worker = handler_->get_worker();
|
||||||
auto handler = upstream->get_client_handler();
|
|
||||||
auto worker = handler->get_worker();
|
|
||||||
auto mruby_ctx = worker->get_mruby_context();
|
auto mruby_ctx = worker->get_mruby_context();
|
||||||
|
|
||||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||||
|
|
|
@ -2325,10 +2325,15 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) {
|
||||||
|
|
||||||
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
||||||
|
|
||||||
|
if (config->http.require_http_scheme &&
|
||||||
|
!http::check_http_scheme(req.scheme, /* encrypted = */true) {
|
||||||
|
if (error_reply(downstream, 400) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
auto upstream = downstream->get_upstream();
|
auto worker = handler_->get_worker();
|
||||||
auto handler = upstream->get_client_handler();
|
|
||||||
auto worker = handler->get_worker();
|
|
||||||
auto mruby_ctx = worker->get_mruby_context();
|
auto mruby_ctx = worker->get_mruby_context();
|
||||||
|
|
||||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||||
|
|
|
@ -154,4 +154,15 @@ void test_shrpx_http_create_altsvc_header_value(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_shrpx_http_check_http_scheme(void) {
|
||||||
|
CU_ASSERT(http::check_http_scheme(StringRef::from_lit("https"), true));
|
||||||
|
CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("https"), false));
|
||||||
|
CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("http"), true));
|
||||||
|
CU_ASSERT(http::check_http_scheme(StringRef::from_lit("http"), false));
|
||||||
|
CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), true));
|
||||||
|
CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), false));
|
||||||
|
CU_ASSERT(!http::check_http_scheme(StringRef{}, true));
|
||||||
|
CU_ASSERT(!http::check_http_scheme(StringRef{}, false));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -35,6 +35,7 @@ void test_shrpx_http_create_forwarded(void);
|
||||||
void test_shrpx_http_create_via_header_value(void);
|
void test_shrpx_http_create_via_header_value(void);
|
||||||
void test_shrpx_http_create_affinity_cookie(void);
|
void test_shrpx_http_create_affinity_cookie(void);
|
||||||
void test_shrpx_http_create_altsvc_header_value(void);
|
void test_shrpx_http_create_altsvc_header_value(void);
|
||||||
|
void test_shrpx_http_check_http_scheme(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -433,12 +433,18 @@ int htp_hdrs_completecb(llhttp_t *htp) {
|
||||||
|
|
||||||
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
||||||
|
|
||||||
|
auto &resp = downstream->response();
|
||||||
|
|
||||||
|
if (config->http.require_http_scheme &&
|
||||||
|
!http::check_http_scheme(req.scheme, handler->get_ssl() != nullptr)) {
|
||||||
|
resp.http_status = 400;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
auto worker = handler->get_worker();
|
auto worker = handler->get_worker();
|
||||||
auto mruby_ctx = worker->get_mruby_context();
|
auto mruby_ctx = worker->get_mruby_context();
|
||||||
|
|
||||||
auto &resp = downstream->response();
|
|
||||||
|
|
||||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||||
resp.http_status = 500;
|
resp.http_status = 500;
|
||||||
return -1;
|
return -1;
|
||||||
|
|
Loading…
Reference in New Issue