Commit 79524471 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

nghttpx: Add --require-http-scheme option

parent a67822b3
...@@ -198,6 +198,7 @@ OPTIONS = [ ...@@ -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) { ...@@ -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[]) { ...@@ -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) ||
......
...@@ -3237,6 +3237,13 @@ HTTP: ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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, ...@@ -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 = ...@@ -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 { ...@@ -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 { ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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) { ...@@ -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) { ...@@ -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); ...@@ -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) { ...@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment