Skip to content

Commit 9a9934a

Browse files
bneradtzwoop
authored andcommitted
Add back validatation that the scheme matches the wire protocol (#9005)
This adds back in the scheme and wire protocol check (see #8465) along with a configuration to be able to disable the check if the verification is not desired. (cherry picked from commit 7ec147e)
1 parent 55c83f4 commit 9a9934a

7 files changed

Lines changed: 95 additions & 20 deletions

File tree

doc/admin-guide/files/records.config.en.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3938,6 +3938,29 @@ Client-Related Configuration
39383938
if sni_policy = ``verify_with_name_source``, the sni will be the host header value and the name
39393939
to check in the server certificate will be the remap header value.
39403940

3941+
.. ts:cv:: CONFIG proxy.config.ssl.client.scheme_proto_mismatch_policy INT 2
3942+
:overridable:
3943+
3944+
This option controls how |TS| behaves when the client side connection
3945+
protocol and the client request's scheme do not match. For example, if
3946+
enforcement is enabled by setting this value to ``2`` and the client
3947+
connection is a cleartext HTTP connection but the scheme of the URL is
3948+
``https://``, then |TS| will emit a warning and return an immediate 400 HTTP
3949+
response without proxying the request to the origin.
3950+
3951+
The default value is ``2``, meaning that |TS| will enforce that the protocol
3952+
matches the scheme.
3953+
3954+
===== ======================================================================
3955+
Value Description
3956+
===== ======================================================================
3957+
``0`` Disable verification that the protocol and scheme match.
3958+
``1`` Check that the protocol and scheme match, but only emit a warning if
3959+
they do not.
3960+
``2`` Check that the protocol and scheme match and, if they do not, emit a
3961+
warning and return an immediate HTTP 400 response.
3962+
===== ======================================================================
3963+
39413964
.. ts:cv:: CONFIG proxy.config.ssl.client.TLSv1 INT 0
39423965
39433966
Enables (``1``) or disables (``0``) TLSv1.0 in the ATS client context. If not specified, enabled by default

mgmt/RecordsConfig.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,8 @@ static const RecordElement RecordsConfig[] =
11761176
,
11771177
{RECT_CONFIG, "proxy.config.ssl.client.sni_policy", RECD_STRING, "host", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
11781178
,
1179+
{RECT_CONFIG, "proxy.config.ssl.client.scheme_proto_mismatch_policy", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
1180+
,
11791181
{RECT_CONFIG, "proxy.config.ssl.origin_session_cache", RECD_INT, "1", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
11801182
,
11811183
{RECT_CONFIG, "proxy.config.ssl.origin_session_cache.size", RECD_INT, "10240", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}

proxy/http/HttpConfig.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,7 @@ HttpConfig::startup()
14111411
HttpEstablishStaticConfigByte(c.http_host_sni_policy, "proxy.config.http.host_sni_policy");
14121412

14131413
HttpEstablishStaticConfigStringAlloc(c.oride.ssl_client_sni_policy, "proxy.config.ssl.client.sni_policy");
1414+
HttpEstablishStaticConfigByte(c.scheme_proto_mismatch_policy, "proxy.config.ssl.client.scheme_proto_mismatch_policy");
14141415

14151416
OutboundConnTrack::config_init(&c.global_outbound_conntrack, &c.oride.outbound_conntrack);
14161417

@@ -1702,6 +1703,7 @@ HttpConfig::reconfigure()
17021703
params->redirect_actions_string = ats_strdup(m_master.redirect_actions_string);
17031704
params->redirect_actions_map = parse_redirect_actions(params->redirect_actions_string, params->redirect_actions_self_action);
17041705
params->http_host_sni_policy = m_master.http_host_sni_policy;
1706+
params->scheme_proto_mismatch_policy = m_master.scheme_proto_mismatch_policy;
17051707

17061708
params->oride.ssl_client_sni_policy = ats_strdup(m_master.oride.ssl_client_sni_policy);
17071709

proxy/http/HttpConfig.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,8 @@ struct HttpConfigParams : public ConfigInfo {
856856
MgmtInt http_request_line_max_size = 65535;
857857
MgmtInt http_hdr_field_max_size = 131070;
858858

859-
MgmtByte http_host_sni_policy = 0;
859+
MgmtByte http_host_sni_policy = 0;
860+
MgmtByte scheme_proto_mismatch_policy = 2;
860861

861862
// noncopyable
862863
/////////////////////////////////////

proxy/http/HttpSM.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,20 @@ HttpSM::state_read_client_request_header(int event, void *data)
849849
break;
850850
}
851851

852+
if (!is_internal && t_state.http_config_param->scheme_proto_mismatch_policy != 0) {
853+
auto scheme = t_state.hdr_info.client_request.url_get()->scheme_get_wksidx();
854+
if ((client_connection_is_ssl && (scheme == URL_WKSIDX_HTTP || scheme == URL_WKSIDX_WS)) ||
855+
(!client_connection_is_ssl && (scheme == URL_WKSIDX_HTTPS || scheme == URL_WKSIDX_WSS))) {
856+
Warning("scheme [%s] vs. protocol [%s] mismatch", hdrtoken_index_to_wks(scheme),
857+
client_connection_is_ssl ? "tls" : "plaintext");
858+
if (t_state.http_config_param->scheme_proto_mismatch_policy == 2) {
859+
t_state.http_return_code = HTTP_STATUS_BAD_REQUEST;
860+
call_transact_and_set_next_state(HttpTransact::BadRequest);
861+
break;
862+
}
863+
}
864+
}
865+
852866
if (_from_early_data) {
853867
// Only allow early data for safe methods defined in RFC7231 Section 4.2.1.
854868
// https://tools.ietf.org/html/rfc7231#section-4.2.1

tests/gold_tests/forward_proxy/forward_proxy.replay.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,3 @@ sessions:
1919
headers:
2020
fields:
2121
- [ Content-Length, 8 ]
22-
23-
proxy-response:
24-
status: 200

tests/gold_tests/forward_proxy/forward_proxy.test.py

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
'''
2-
'''
1+
"""
2+
"""
33
# Licensed to the Apache Software Foundation (ASF) under one
44
# or more contributor license agreements. See the NOTICE file
55
# distributed with this work for additional information
@@ -16,25 +16,49 @@
1616
# See the License for the specific language governing permissions and
1717
# limitations under the License.
1818

19-
import sys
19+
from typing import Union
2020

2121
Test.Summary = 'Verify ATS can function as a forward proxy'
2222
Test.ContinueOnFail = True
2323

2424

2525
class ForwardProxyTest:
26-
def __init__(self):
26+
_scheme_proto_mismatch_policy: Union[int, None]
27+
_ts_counter: int = 0
28+
_server_counter: int = 0
29+
30+
def __init__(self, verify_scheme_matches_protocol: Union[int, None]):
31+
"""Construct a ForwardProxyTest object.
32+
33+
:param verify_scheme_matches_protocol: The value with which to
34+
configure Traffic Server's
35+
proxy.config.ssl.client.scheme_proto_mismatch_policy. A value of None
36+
means that no value will be explicitly set in the records.config.
37+
:type verify_scheme_matches_protocol: int or None
38+
"""
39+
self._scheme_proto_mismatch_policy = verify_scheme_matches_protocol
2740
self.setupOriginServer()
2841
self.setupTS()
2942

3043
def setupOriginServer(self):
31-
self.server = Test.MakeVerifierServerProcess("server", "forward_proxy.replay.yaml")
32-
self.server.Streams.All = Testers.ContainsExpression(
33-
'Received an HTTP/1 request with key 1',
34-
'Verify that the server received the request.')
44+
"""Configure the Proxy Verifier server."""
45+
proc_name = f"server{ForwardProxyTest._server_counter}"
46+
self.server = Test.MakeVerifierServerProcess(proc_name, "forward_proxy.replay.yaml")
47+
ForwardProxyTest._server_counter += 1
48+
if self._scheme_proto_mismatch_policy in (2, None):
49+
self.server.Streams.All = Testers.ExcludesExpression(
50+
'Received an HTTP/1 request with key 1',
51+
'Verify that the server did not receive the request.')
52+
else:
53+
self.server.Streams.All = Testers.ContainsExpression(
54+
'Received an HTTP/1 request with key 1',
55+
'Verify that the server received the request.')
3556

3657
def setupTS(self):
37-
self.ts = Test.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
58+
"""Configure the Traffic Server process."""
59+
proc_name = f"ts{ForwardProxyTest._ts_counter}"
60+
self.ts = Test.MakeATSProcess(proc_name, enable_tls=True, enable_cache=False)
61+
ForwardProxyTest._ts_counter += 1
3862
self.ts.addDefaultSSLFiles()
3963
self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
4064
self.ts.Disk.remap_config.AddLine(
@@ -50,10 +74,13 @@ def setupTS(self):
5074
'proxy.config.diags.debug.tags': "http",
5175
})
5276

77+
if self._scheme_proto_mismatch_policy is not None:
78+
self.ts.Disk.records_config.update({
79+
'proxy.config.ssl.client.scheme_proto_mismatch_policy': self._scheme_proto_mismatch_policy,
80+
})
81+
5382
def addProxyHttpsToHttpCase(self):
54-
"""
55-
Test ATS as an HTTPS forward proxy behind an HTTP server.
56-
"""
83+
"""Test ATS as an HTTPS forward proxy behind an HTTP server."""
5784
tr = Test.AddTestRun()
5885
tr.Processes.Default.StartBefore(self.server)
5986
tr.Processes.Default.StartBefore(self.ts)
@@ -65,12 +92,21 @@ def addProxyHttpsToHttpCase(self):
6592
tr.StillRunningAfter = self.server
6693
tr.StillRunningAfter = self.ts
6794

68-
tr.Processes.Default.Streams.All = Testers.ContainsExpression(
69-
'< HTTP/1.1 200 OK',
70-
'Verify that curl received a 200 OK response.')
95+
if self._scheme_proto_mismatch_policy in (2, None):
96+
tr.Processes.Default.Streams.All = Testers.ContainsExpression(
97+
'< HTTP/1.1 400 Invalid HTTP Request',
98+
'Verify that the request was rejected.')
99+
else:
100+
tr.Processes.Default.Streams.All = Testers.ContainsExpression(
101+
'< HTTP/1.1 200 OK',
102+
'Verify that curl received a 200 OK response.')
71103

72104
def run(self):
105+
"""Configure the TestRun instances for this set of tests."""
73106
self.addProxyHttpsToHttpCase()
74107

75108

76-
ForwardProxyTest().run()
109+
ForwardProxyTest(verify_scheme_matches_protocol=None).run()
110+
ForwardProxyTest(verify_scheme_matches_protocol=0).run()
111+
ForwardProxyTest(verify_scheme_matches_protocol=1).run()
112+
ForwardProxyTest(verify_scheme_matches_protocol=2).run()

0 commit comments

Comments
 (0)