This is an automated email from the ASF dual-hosted git repository. cmcfarlen pushed a commit to branch 10.0.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/10.0.x by this push: new e1fd38e6c1 Add a setting to choose the data source of IP address for ACL (#12298) e1fd38e6c1 is described below commit e1fd38e6c19e0a3184b670b7ef7b05a330de7d30 Author: Masakazu Kitajo <mas...@apache.org> AuthorDate: Mon Jun 16 19:07:59 2025 -0600 Add a setting to choose the data source of IP address for ACL (#12298) * Add a setting to choose the data source of IP address for ACL * Address isssues --- .../configuration/proxy-protocol.en.rst | 4 + doc/admin-guide/files/records.yaml.en.rst | 14 +++ include/proxy/IPAllow.h | 4 + src/iocore/net/SNIActionPerformer.cc | 16 ++- src/proxy/IPAllow.cc | 33 +++++- src/proxy/http/HttpSessionAccept.cc | 10 ++ src/proxy/http/remap/UrlRewrite.cc | 30 ++++- src/proxy/http2/Http2SessionAccept.cc | 16 ++- src/records/RecordsConfig.cc | 8 ++ tests/gold_tests/ip_allow/ip_allow.test.py | 24 +++- .../replays/http_proxy_protocol.replay.yaml | 54 +++++++++ tests/gold_tests/remap/remap_acl.test.py | 93 ++++++++++----- .../remap_acl_get_post_allowed_pp.replay.yaml | 132 +++++++++++++++++++++ .../tls/replay/ip_allow_proxy.replay.yaml | 113 ++++++++++++++++++ tests/gold_tests/tls/tls_sni_ip_allow.test.py | 38 ++++-- 15 files changed, 540 insertions(+), 49 deletions(-) diff --git a/doc/admin-guide/configuration/proxy-protocol.en.rst b/doc/admin-guide/configuration/proxy-protocol.en.rst index 5c5907988d..f58d518ab2 100644 --- a/doc/admin-guide/configuration/proxy-protocol.en.rst +++ b/doc/admin-guide/configuration/proxy-protocol.en.rst @@ -51,6 +51,10 @@ configured with :ts:cv:`proxy.config.http.proxy_protocol_allowlist`. :ts:cv:`proxy.config.http.server_ports` configuration, regardless of whether the connections have the Proxy Protocol header. +By default, |TS| uses client's IP address that is from the peer when it applies ACL. If you configure a port to +enable PROXY protocol and want to apply ACL against the IP address delivered by PROXY protocol, you need to have ``PROXY`` in +:ts:cv:`proxy.config.acl.subjects`. + 1. HTTP Forwarded Header The client IP address in the PROXY protocol header is passed to the origin server via an HTTP `Forwarded: diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst index a796fb2f01..0de674ec9f 100644 --- a/doc/admin-guide/files/records.yaml.en.rst +++ b/doc/admin-guide/files/records.yaml.en.rst @@ -2131,6 +2131,20 @@ IP Allow the use of this file, see :file:`ip_allow.yaml`. If this is a relative path, |TS| loads it relative to the ``SYSCONFDIR`` directory. +.. ts:cv:: CONFIG proxy.config.acl.subjects STRING PEER + + Specifies the list of data sources for getting client's IP address for ACL. + The value is a comma separated string, and the first available data source + will be used. If you configure a port to enable PROXY protocol, you probably + need to adjust this setting to have ``PROXY`` in the list. + + ============= ====================================================================== + Value Description + ============= ====================================================================== + ``PEER`` Use the IP address of the peer + ``PROXY`` Use the IP address from PROXY protocol + ============= ====================================================================== + Cache Control ============= diff --git a/include/proxy/IPAllow.h b/include/proxy/IPAllow.h index 0a1929584d..320d923123 100644 --- a/include/proxy/IPAllow.h +++ b/include/proxy/IPAllow.h @@ -147,6 +147,8 @@ public: static constexpr const char *MODULE_NAME = "IPAllow"; + enum Subject { PEER, PROXY, MAX_SUBJECTS }; + /** An access control record and support data. * The primary point of this is to hold the backing configuration in memory while the ACL * is in use. @@ -252,6 +254,8 @@ public: */ static bool has_no_rules(); + static uint8_t subjects[Subject::MAX_SUBJECTS]; + private: static size_t configid; ///< Configuration ID for update management. static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access. diff --git a/src/iocore/net/SNIActionPerformer.cc b/src/iocore/net/SNIActionPerformer.cc index 3657ca4b3d..946cede178 100644 --- a/src/iocore/net/SNIActionPerformer.cc +++ b/src/iocore/net/SNIActionPerformer.cc @@ -24,6 +24,7 @@ #include "swoc/swoc_file.h" #include "swoc/BufferWriter.h" #include "tscore/Layout.h" +#include "proxy/IPAllow.h" #include "SNIActionPerformer.h" @@ -412,8 +413,19 @@ SNI_IpAllow::SNIAction(SSL &ssl, ActionItem::Context const & /* ctx ATS_UNUSED * return SSL_TLSEXT_ERR_OK; } - auto ssl_vc = SSLNetVCAccess(&ssl); - auto ip = swoc::IPAddr(ssl_vc->get_remote_endpoint()); + auto ssl_vc = SSLNetVCAccess(&ssl); + const sockaddr *client_ip = nullptr; + for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) { + if (IpAllow::Subject::PEER == IpAllow::subjects[i]) { + client_ip = ssl_vc->get_remote_addr(); + break; + } else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] && + ssl_vc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) { + client_ip = ssl_vc->get_proxy_protocol_src_addr(); + break; + } + } + swoc::IPAddr ip = swoc::IPAddr(client_ip); // check the allowed ips if (ip_addrs.contains(ip)) { diff --git a/src/proxy/IPAllow.cc b/src/proxy/IPAllow.cc index 21f8cf85a7..e1a54c3a1d 100644 --- a/src/proxy/IPAllow.cc +++ b/src/proxy/IPAllow.cc @@ -24,6 +24,8 @@ limitations under the License. */ +#include <string_view> + #include "proxy/IPAllow.h" #include "records/RecCore.h" #include "swoc/Errata.h" @@ -67,8 +69,9 @@ enum AclOp { const IpAllow::Record IpAllow::ALLOW_ALL_RECORD(ALL_METHOD_MASK); const IpAllow::ACL IpAllow::DENY_ALL_ACL; -size_t IpAllow::configid = 0; -bool IpAllow::accept_check_p = true; // initializing global flag for fast deny +size_t IpAllow::configid = 0; +bool IpAllow::accept_check_p = true; // initializing global flag for fast deny +uint8_t IpAllow::subjects[Subject::MAX_SUBJECTS]; static ConfigUpdateHandler<IpAllow> *ipAllowUpdate; @@ -212,6 +215,32 @@ IpAllow::IpAllow(const char *ip_allow_config_var, const char *ip_categories_conf if (!path.empty()) { ip_categories_config_file = ats_scoped_str(path).get(); } + + RecString subjects_char; + REC_ReadConfigStringAlloc(subjects_char, "proxy.config.acl.subjects"); + std::string_view subjects_sv{subjects_char}; + int i = 0; + std::string_view::size_type s, e; + for (s = 0, e = 0; s < subjects_sv.size() && e != subjects_sv.npos; s = e + 1) { + e = subjects_sv.find(",", s); + std::string_view subject_sv = subjects_sv.substr(s, e); + if (i >= MAX_SUBJECTS) { + Error("Too many ACL subjects were provided"); + } + if (subject_sv == "PEER") { + subjects[i] = Subject::PEER; + ++i; + } else if (subject_sv == "PROXY") { + subjects[i] = Subject::PROXY; + ++i; + } else { + Dbg(dbg_ctl_ip_allow, "Unknown subject %.*s was ignored", static_cast<int>(subject_sv.length()), subject_sv.data()); + } + } + if (i < Subject::MAX_SUBJECTS) { + subjects[i] = Subject::MAX_SUBJECTS; + } + ats_free(subjects_char); } BufferWriter & diff --git a/src/proxy/http/HttpSessionAccept.cc b/src/proxy/http/HttpSessionAccept.cc index c686a95596..d216ff550f 100644 --- a/src/proxy/http/HttpSessionAccept.cc +++ b/src/proxy/http/HttpSessionAccept.cc @@ -39,6 +39,16 @@ HttpSessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferReade IpAllow::ACL acl; ip_port_text_buffer ipb; + for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) { + if (IpAllow::Subject::PEER == IpAllow::subjects[i]) { + client_ip = netvc->get_remote_addr(); + break; + } else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] && + netvc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) { + client_ip = netvc->get_proxy_protocol_src_addr(); + break; + } + } acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR); if (!acl.isValid()) { // if there's no ACL, it's a hard deny. Warning("client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb))); diff --git a/src/proxy/http/remap/UrlRewrite.cc b/src/proxy/http/remap/UrlRewrite.cc index 94cd34e2e0..a418008923 100644 --- a/src/proxy/http/remap/UrlRewrite.cc +++ b/src/proxy/http/remap/UrlRewrite.cc @@ -460,6 +460,26 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const int method_wksidx = (method != -1) ? (method - HTTP_WKSIDX_CONNECT) : -1; ink_release_assert(ats_is_ip(&s->client_info.src_addr)); + const IpEndpoint *src_addr = nullptr; + const IpEndpoint *local_addr = nullptr; + const ProxyProtocol &pp_info = s->state_machine->get_ua_txn()->get_netvc()->get_proxy_protocol_info(); + for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) { + if (IpAllow::Subject::PEER == IpAllow::subjects[i]) { + src_addr = &s->client_info.src_addr; + local_addr = &s->client_info.dst_addr; + break; + } else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] && pp_info.version != ProxyProtocolVersion::UNDEFINED) { + src_addr = &pp_info.src_addr; + local_addr = &pp_info.dst_addr; + break; + } + } + + if (src_addr == nullptr) { + // Use addresses from peer if none of the configured sources are avaialable + src_addr = &s->client_info.src_addr; + local_addr = &s->client_info.dst_addr; + } s->client_connection_allowed = true; // Default is that we allow things unless some filter matches @@ -487,7 +507,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const if (rp->src_ip_valid) { bool src_ip_matches = false; for (int j = 0; j < rp->src_ip_cnt && !src_ip_matches; j++) { - bool in_range = rp->src_ip_array[j].contains(s->client_info.src_addr); + bool in_range = rp->src_ip_array[j].contains(*src_addr); if (rp->src_ip_array[j].invert) { if (!in_range) { src_ip_matches = true; @@ -506,7 +526,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const if (ip_matches && rp->src_ip_category_valid) { bool category_ip_matches = false; for (int j = 0; j < rp->src_ip_category_cnt && !category_ip_matches; j++) { - bool in_category = rp->src_ip_category_array[j].contains(s->client_info.src_addr); + bool in_category = rp->src_ip_category_array[j].contains(*src_addr); if (rp->src_ip_category_array[j].invert) { if (!in_category) { category_ip_matches = true; @@ -525,16 +545,14 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const if (ip_matches && rp->in_ip_valid) { bool in_ip_matches = false; for (int j = 0; j < rp->in_ip_cnt && !in_ip_matches; j++) { - IpEndpoint incoming_addr; - incoming_addr.assign(s->state_machine->get_ua_txn()->get_netvc()->get_local_addr()); if (dbg_ctl_url_rewrite.on()) { char buf1[128], buf2[128], buf3[128]; - ats_ip_ntop(incoming_addr, buf1, sizeof(buf1)); + ats_ip_ntop(local_addr, buf1, sizeof(buf1)); rp->in_ip_array[j].start.toString(buf2, sizeof(buf2)); rp->in_ip_array[j].end.toString(buf3, sizeof(buf3)); Dbg(dbg_ctl_url_rewrite, "Trying to match incoming address %s in range %s - %s.", buf1, buf2, buf3); } - bool in_range = rp->in_ip_array[j].contains(incoming_addr); + bool in_range = rp->in_ip_array[j].contains(*local_addr); if (rp->in_ip_array[j].invert) { if (!in_range) { in_ip_matches = true; diff --git a/src/proxy/http2/Http2SessionAccept.cc b/src/proxy/http2/Http2SessionAccept.cc index 8d9449a37d..d97c6d18f3 100644 --- a/src/proxy/http2/Http2SessionAccept.cc +++ b/src/proxy/http2/Http2SessionAccept.cc @@ -42,8 +42,20 @@ Http2SessionAccept::~Http2SessionAccept() = default; bool Http2SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferReader *reader) { - sockaddr const *client_ip = netvc->get_remote_addr(); - IpAllow::ACL session_acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR); + sockaddr const *client_ip = nullptr; + + for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) { + if (IpAllow::Subject::PEER == IpAllow::subjects[i]) { + client_ip = netvc->get_remote_addr(); + break; + } else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] && + netvc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) { + client_ip = netvc->get_proxy_protocol_src_addr(); + break; + } + } + + IpAllow::ACL session_acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR); if (!session_acl.isValid()) { ip_port_text_buffer ipb; Warning("HTTP/2 client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb))); diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc index 0193b02f47..8ea7c3693a 100644 --- a/src/records/RecordsConfig.cc +++ b/src/records/RecordsConfig.cc @@ -140,6 +140,14 @@ static const RecordElement RecordsConfig[] = {RECT_CONFIG, "proxy.config.srv_enabled", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} , + //############################################################################## + //# + //# ACL + //# + //############################################################################## + {RECT_CONFIG, "proxy.config.acl.subjects", RECD_STRING, "PEER", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + //############################################################################## //# //# Support for disabling check for Accept-* / Content-* header mismatch diff --git a/tests/gold_tests/ip_allow/ip_allow.test.py b/tests/gold_tests/ip_allow/ip_allow.test.py index 6c99a13b64..606736fe3f 100644 --- a/tests/gold_tests/ip_allow/ip_allow.test.py +++ b/tests/gold_tests/ip_allow/ip_allow.test.py @@ -242,7 +242,8 @@ class Test_ip_allow: :param tr: The TestRun object to associate the ts process with. """ - ts = tr.MakeATSProcess(f"ts-{Test_ip_allow.ts_counter}", enable_quic=self.is_h3, enable_tls=True) + ts = tr.MakeATSProcess( + f"ts-{Test_ip_allow.ts_counter}", enable_quic=self.is_h3, enable_tls=True, enable_proxy_protocol=True) Test_ip_allow.ts_counter += 1 self._ts = ts @@ -252,13 +253,14 @@ class Test_ip_allow: self._ts.Disk.records_config.update( { 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'v_quic|quic|http|ip_allow', + 'proxy.config.diags.debug.tags': 'v_quic|quic|http|ip_allow|proxyprotocol', 'proxy.config.http.push_method_enabled': 1, 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), 'proxy.config.quic.no_activity_timeout_in': 0, 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', 'proxy.config.http.connect_ports': f"{self._server.Variables.http_port}", + 'proxy.config.acl.subjects': 'PROXY,PEER', }) self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}') @@ -278,6 +280,7 @@ class Test_ip_allow: tr.AddVerifierClientProcess( f'client-{Test_ip_allow.client_counter}', self.replay_file, + http_ports=[self._ts.Variables.proxy_protocol_port], https_ports=[self._ts.Variables.ssl_port], http3_ports=[self._ts.Variables.ssl_port], keys=self.replay_keys) @@ -333,3 +336,20 @@ test_ip_allow_optional_methods = Test_ip_allow( is_h3=False, expect_request_rejected=False) test_ip_allow_optional_methods.run() + +# TEST 7: Verify IP address from PROXY protocol is used. +IP_ALLOW_CONFIG_PROXY_PROTOCOL = '''ip_allow: + - apply: in + ip_addrs: 1.2.3.4 + action: allow + - apply: in + ip_addrs: 0/0 + action: deny +''' +test_ip_allow_proxy_protocol = Test_ip_allow( + "ip_allow_proxy_protocol", + replay_file='replays/http_proxy_protocol.replay.yaml', + ip_allow_config=IP_ALLOW_CONFIG_PROXY_PROTOCOL, + is_h3=False, + expect_request_rejected=False) +test_ip_allow_proxy_protocol.run() diff --git a/tests/gold_tests/ip_allow/replays/http_proxy_protocol.replay.yaml b/tests/gold_tests/ip_allow/replays/http_proxy_protocol.replay.yaml new file mode 100644 index 0000000000..996182c0f4 --- /dev/null +++ b/tests/gold_tests/ip_allow/replays/http_proxy_protocol.replay.yaml @@ -0,0 +1,54 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The replay file executes various HTTP requests to verify the ip_allow policy +# applies by default to all methods. + +meta: + version: "1.0" + + blocks: + - standard_response: &standard_response + server-response: + status: 200 + reason: OK + headers: + fields: + - [ Content-Length, 20 ] +sessions: +- protocol: + - name: http + version: 1 + - name: proxy-protocol + version: 2 + src-addr: "1.2.3.4:1111" + dst-addr: "5.6.7.8:2222" + transactions: + + # GET + - client-request: + method: "GET" + version: "1.1" + url: /test/ip_allow/test_get + headers: + fields: + - [ Content-Length, 0 ] + - [ uuid, 1 ] + + <<: *standard_response + + proxy-response: + status: 200 diff --git a/tests/gold_tests/remap/remap_acl.test.py b/tests/gold_tests/remap/remap_acl.test.py index 32ea37c6f6..d3001a33f0 100644 --- a/tests/gold_tests/remap/remap_acl.test.py +++ b/tests/gold_tests/remap/remap_acl.test.py @@ -38,7 +38,7 @@ class Test_remap_acl: def __init__( self, name: str, replay_file: str, ip_allow_content: str, deactivate_ip_allow: bool, acl_behavior_policy: int, - acl_configuration: str, named_acls: List[Tuple[str, str]], expected_responses: List[int]): + acl_configuration: str, named_acls: List[Tuple[str, str]], expected_responses: List[int], proxy_protocol: bool): """Initialize the test. :param name: The name of the test. @@ -60,7 +60,7 @@ class Test_remap_acl: tr = Test.AddTestRun(name) self._configure_server(tr) self._configure_traffic_server(tr) - self._configure_client(tr) + self._configure_client(tr, proxy_protocol) def _configure_server(self, tr: 'TestRun') -> None: """Configure the server. @@ -79,17 +79,18 @@ class Test_remap_acl: """ name = f"ts-{Test_remap_acl._ts_counter}" - ts = tr.MakeATSProcess(name, enable_cache=False, enable_tls=True) + ts = tr.MakeATSProcess(name, enable_cache=False, enable_tls=True, enable_proxy_protocol=True) Test_remap_acl._ts_counter += 1 self._ts = ts ts.Disk.records_config.update( { 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|url|remap|ip_allow', + 'proxy.config.diags.debug.tags': 'http|url|remap|ip_allow|proxyprotocol', 'proxy.config.http.push_method_enabled': 1, 'proxy.config.http.connect_ports': self._server.Variables.http_port, 'proxy.config.url_remap.acl_behavior_policy': self._acl_behavior_policy, + 'proxy.config.acl.subjects': 'PROXY,PEER', }) remap_config_lines = [] @@ -107,14 +108,15 @@ class Test_remap_acl: ts.Disk.remap_config.AddLines(remap_config_lines) ts.Disk.ip_allow_yaml.AddLines(self._ip_allow_content.split("\n")) - def _configure_client(self, tr: 'TestRun') -> None: + def _configure_client(self, tr: 'TestRun', proxy_protocol: bool) -> None: """Run the test. :param tr: The TestRun object to associate the client process with. """ name = f"client-{Test_remap_acl._client_counter}" - p = tr.AddVerifierClientProcess(name, self._replay_file, http_ports=[self._ts.Variables.port]) + port = self._ts.Variables.port if proxy_protocol == False else self._ts.Variables.proxy_protocol_port + p = tr.AddVerifierClientProcess(name, self._replay_file, http_ports=[port]) Test_remap_acl._client_counter += 1 p.StartBefore(self._server) p.StartBefore(self._ts) @@ -239,7 +241,19 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=127.0.0.1 @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) + +test_ip_allow_optional_methods_pp = Test_remap_acl( + "Verify non-allowed methods are blocked (PP).", + replay_file='remap_acl_get_post_allowed_pp.replay.yaml', + ip_allow_content=IP_ALLOW_CONTENT, + deactivate_ip_allow=False, + acl_behavior_policy=1, + acl_configuration='@action=set_allow @src_ip=1.2.3.4 @method=GET @method=POST', + named_acls=[], + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=True) test_ip_allow_optional_methods = Test_remap_acl( "Verify add_allow adds an allowed method.", @@ -249,7 +263,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=add_allow @src_ip=127.0.0.1 @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify add_allow adds allowed methods.", @@ -259,7 +274,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=add_allow @src_ip=127.0.0.1 @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify if no ACLs match, ip_allow.yaml is used.", @@ -269,7 +285,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=1.2.3.4 @method=GET @method=POST', named_acls=[], - expected_responses=[200, 403, 403, 403, 403]) + expected_responses=[200, 403, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify @src_ip=all works.", @@ -279,7 +296,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=all @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify @src_ip_category works.", @@ -289,7 +307,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip_category=ACME_LOCAL @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify no @src_ip implies all IP addresses.", @@ -299,7 +318,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify denied methods are blocked.", @@ -309,7 +329,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_deny @src_ip=127.0.0.1 @method=GET @method=POST', named_acls=[], - expected_responses=[403, 403, 200, 200, 400]) + expected_responses=[403, 403, 200, 200, 400], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify add_deny adds blocked methods.", @@ -319,7 +340,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=add_deny @src_ip=127.0.0.1 @method=GET', named_acls=[], - expected_responses=[403, 403, 403, 403, 403]) + expected_responses=[403, 403, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify a default deny filter rule works.", @@ -329,7 +351,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=1.2.3.4 @method=GET @method=POST', named_acls=[('deny', '@action=set_deny')], - expected_responses=[403, 403, 403, 403, 403]) + expected_responses=[403, 403, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify inverting @src_ip works.", @@ -339,7 +362,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=~127.0.0.1 @method=GET @method=POST', named_acls=[('deny', '@action=set_deny')], - expected_responses=[403, 403, 403, 403, 403]) + expected_responses=[403, 403, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify inverting @src_ip works with the rule matching.", @@ -349,7 +373,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=~3.4.5.6 @method=GET @method=POST', named_acls=[('deny', '@action=set_deny')], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify inverting @src_ip_category works.", @@ -359,7 +384,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip_category=~ACME_LOCAL @method=GET @method=POST', named_acls=[('deny', '@action=set_deny')], - expected_responses=[403, 403, 403, 403, 403]) + expected_responses=[403, 403, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify inverting @src_ip_category works with the rule matching.", @@ -369,7 +395,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip_category=~ACME_EXTERNAL @method=GET @method=POST', named_acls=[('deny', '@action=set_deny')], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify @src_ip and @src_ip_category AND together.", @@ -381,7 +408,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_configuration='@action=set_allow @src_ip=127.0.0.1 @src_ip_category=ACME_EXTERNAL @method=GET @method=POST', # Therefore, this named deny filter will block. named_acls=[('deny', '@action=set_deny')], - expected_responses=[403, 403, 403, 403, 403]) + expected_responses=[403, 403, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify defined in-line ACLS are evaluated before named ones.", @@ -391,7 +419,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=127.0.0.1 @method=GET @method=POST', named_acls=[('deny', '@action=set_deny')], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify remap.config line overrides ip_allow rule.", @@ -401,7 +430,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @src_ip=127.0.0.1 @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify we can deactivate the ip_allow filter.", @@ -413,7 +443,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_configuration='@action=set_allow @src_ip=1.2.3.4 @method=GET @method=POST', named_acls=[], # Nothing will block the request since ip_allow.yaml is off. - expected_responses=[200, 200, 200, 200, 400]) + expected_responses=[200, 200, 200, 200, 400], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify in_ip matches on IP as expected.", @@ -423,7 +454,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @in_ip=127.0.0.1 @method=GET @method=POST', named_acls=[], - expected_responses=[200, 200, 403, 403, 403]) + expected_responses=[200, 200, 403, 403, 403], + proxy_protocol=False) test_ip_allow_optional_methods = Test_remap_acl( "Verify in_ip rules do not match on other IPs.", @@ -433,7 +465,8 @@ test_ip_allow_optional_methods = Test_remap_acl( acl_behavior_policy=1, acl_configuration='@action=set_allow @in_ip=3.4.5.6 @method=GET @method=POST', named_acls=[], - expected_responses=[200, 403, 403, 403, 403]) + expected_responses=[200, 403, 403, 403, 403], + proxy_protocol=False) test_named_acl_deny = Test_remap_acl( "Verify a named ACL is applied if an in-line ACL is absent.", @@ -443,7 +476,8 @@ test_named_acl_deny = Test_remap_acl( acl_behavior_policy=1, acl_configuration='', named_acls=[('deny', '@action=set_deny @method=HEAD @method=POST')], - expected_responses=[200, 403, 403, 403]) + expected_responses=[200, 403, 403, 403], + proxy_protocol=False) def replay_proxy_response(filename, replay_file, get_proxy_response, post_proxy_response): @@ -492,6 +526,7 @@ for idx, test in enumerate(all_acl_combination_tests): acl_configuration=test["inline"], named_acls=[("acl", test["named_acl"])] if test["named_acl"] != "" else [], expected_responses=[test["GET response"], test["POST response"]], + proxy_protocol=False, ) """ Test all ACL combinations @@ -516,4 +551,6 @@ for idx, test in enumerate(all_deactivate_ip_allow_tests): acl_behavior_policy=0 if test["policy"] == "legacy" else 1, acl_configuration=test["inline"], named_acls=[("acl", test["named_acl"])] if test["named_acl"] != "" else [], - expected_responses=[test["GET response"], test["POST response"]]) + expected_responses=[test["GET response"], test["POST response"]], + proxy_protocol=False, + ) diff --git a/tests/gold_tests/remap/remap_acl_get_post_allowed_pp.replay.yaml b/tests/gold_tests/remap/remap_acl_get_post_allowed_pp.replay.yaml new file mode 100644 index 0000000000..1e8b9323b0 --- /dev/null +++ b/tests/gold_tests/remap/remap_acl_get_post_allowed_pp.replay.yaml @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This expects a remap.config that allows GET and POST, but denies all other +# methods. + +meta: + version: "1.0" + + blocks: + - standard_response: &standard_response + server-response: + status: 200 + reason: OK + headers: + fields: + - [ Content-Length, 20 ] + +sessions: +- protocol: + - name: http + version: 1 + - name: proxy-protocol + version: 2 + src-addr: "1.2.3.4:1111" + dst-addr: "5.6.7.8:2222" + transactions: + + - client-request: + method: "GET" + version: "1.1" + url: /test/ip_allow/test_get + headers: + fields: + - [ Content-Length, 0 ] + - [ uuid, get ] + - [ X-Request, get ] + + <<: *standard_response + + proxy-response: + status: 200 + + # POST also is in the allow list. + - client-request: + method: "POST" + version: "1.1" + url: /test/ip_allow/test_post + headers: + fields: + - [Content-Length, 10] + - [ uuid, post ] + - [ X-Request, post ] + + <<: *standard_response + + proxy-response: + status: 200 + + # PUT rejected + - client-request: + method: "PUT" + version: "1.1" + url: /test/ip_allow/test_put + headers: + fields: + - [ Host, example.com ] + - [ uuid, put ] + - [ X-Request, put ] + - [ Content-Length, 113 ] + content: + encoding: plain + data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" + + # Not received. + <<: *standard_response + + # Verify that ATS rejected the PUSH. + proxy-response: + status: 403 + + # DELETE rejected + - client-request: + method: "DELETE" + version: "1.1" + url: /test/ip_allow/test_delete + headers: + fields: + - [ Host, example.com ] + - [ uuid, delete ] + - [ X-Request, delete ] + - [ Content-Length, 0 ] + + <<: *standard_response + + # Verify that ATS rejects the DELETE. + proxy-response: + status: 403 + + # PUSH rejected + - client-request: + method: "PUSH" + version: "1.1" + url: /test/ip_allow/test_push + headers: + fields: + - [ Host, example.com ] + - [ uuid, push ] + - [ X-Request, push ] + - [ Content-Length, 113 ] + content: + encoding: plain + data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" + + <<: *standard_response + + # Verify that ATS rejected the PUSH. + proxy-response: + status: 403 diff --git a/tests/gold_tests/tls/replay/ip_allow_proxy.replay.yaml b/tests/gold_tests/tls/replay/ip_allow_proxy.replay.yaml new file mode 100644 index 0000000000..88219d0573 --- /dev/null +++ b/tests/gold_tests/tls/replay/ip_allow_proxy.replay.yaml @@ -0,0 +1,113 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +meta: + version: '1.0' + +sessions: + +- protocol: + - name: http + version: 2 + - name: tls + sni: pp.block.me.com + - name: proxy-protocol + version: 2 + src-addr: "1.2.3.4:1111" + dst-addr: "5.6.7.8:2222" + - name: tcp + - name: ip + + transactions: + + # + # This request should be blocked per sni.yaml ip_allow. + # + - client-request: + headers: + fields: + - [ :method, GET ] + - [ :scheme, https ] + - [ :authority, pp.block.me.com ] + - [ :path, /pictures/flower.jpeg ] + - [ uuid, blocked-request ] + + # + # The request should not make it to the server and the client should simply + # receive a connection close. + # + server-response: + headers: + fields: + - [ :status, 200 ] + - [ Date, "Sat, 16 Mar 2019 03:11:36 GMT" ] + - [ Content-Type, image/jpeg ] + - [ X-Response, blocked-response ] + content: + size: 3432 + +- protocol: + - name: http + version: 2 + - name: tls + sni: pp.allow.me.com + - name: proxy-protocol + version: 2 + src-addr: "1.2.3.4:1111" + dst-addr: "5.6.7.8:2222" + - name: tcp + - name: ip + + transactions: + + # + # This request, on the other hand, should be allowed. + # + - client-request: + headers: + fields: + - [ :method, GET ] + - [ :scheme, https ] + - [ :authority, pp.allow.me.com ] + - [ :path, /pictures/flower.jpeg ] + - [ Content-Type, image/jpeg ] + - [ uuid, allowed-request ] + content: + size: 399 + + proxy-request: + url: + - [ path, { value: flower.jpeg, as: contains } ] + + # + # The request should not make it to the server and the client should simply + # receive a connection close. + # + server-response: + headers: + fields: + - [ :status, 200 ] + - [ Date, "Sat, 16 Mar 2019 03:11:36 GMT" ] + - [ Content-Type, image/jpeg ] + - [ X-Response, allowed-response ] + content: + size: 35 + + proxy-response: + headers: + fields: + - [ :status, { value: 200, as: equal } ] + - [ X-Response, { value: allowed-response, as: equal } ] diff --git a/tests/gold_tests/tls/tls_sni_ip_allow.test.py b/tests/gold_tests/tls/tls_sni_ip_allow.test.py index 739bbf00cc..84cb9f6b9f 100644 --- a/tests/gold_tests/tls/tls_sni_ip_allow.test.py +++ b/tests/gold_tests/tls/tls_sni_ip_allow.test.py @@ -25,6 +25,7 @@ Test.Summary = '''Test sni.yaml ip_allow.''' class ConnectionType: GET = 0 TUNNEL = 1 + PROXY = 2 class TestSniIpAllow: @@ -38,19 +39,21 @@ class TestSniIpAllow: """Configure a test run. :param connect_type: The type of connection to use. """ - tr = Test.AddTestRun("Verify ip_allow of sni.yaml") + tr = Test.AddTestRun(f'Verify ip_allow of sni.yaml ({connect_type})') if connect_type == ConnectionType.GET: self._replay_file: str = "replay/ip_allow.replay.yaml" elif connect_type == ConnectionType.TUNNEL: self._replay_file: str = "replay/ip_allow_tunnel.replay.yaml" + elif connect_type == ConnectionType.PROXY: + self._replay_file: str = "replay/ip_allow_proxy.replay.yaml" else: raise ValueError(f'Invalid connect_type: {connect_type}') self._dns = self._configure_dns(tr) self._server = self._configure_server(tr) self._ts = self._configure_trafficserver(tr, connect_type, self._dns, self._server) - self._configure_client(tr, self._dns, self._server, self._ts) + self._configure_client(tr, self._dns, self._server, self._ts, connect_type) def _configure_dns(self, tr: 'TestRun') -> 'Process': """Configure a DNS for the TestRun. @@ -86,7 +89,7 @@ class TestSniIpAllow: :return: The Traffic Server Process. """ name = f'ts{TestSniIpAllow._ts_counter}' - ts = tr.MakeATSProcess(name, enable_tls=True, enable_cache=False) + ts = tr.MakeATSProcess(name, enable_tls=True, enable_cache=False, enable_proxy_protocol=True) TestSniIpAllow._ts_counter += 1 ts.Disk.sni_yaml.AddLines( [ @@ -98,6 +101,12 @@ class TestSniIpAllow: ts.Disk.sni_yaml.AddLines([ f' tunnel_route: backend.server.com:{server.Variables.https_port}', ]) + if connect_type == ConnectionType.PROXY: + ts.Disk.sni_yaml.AddLines( + [ + '- fqdn: pp.block.me.com', + ' ip_allow: 192.168.10.1', # Therefore 1.2.3.4 should be blocked. + ]) ts.Disk.sni_yaml.AddLines([ '- fqdn: allow.me.com', ' ip_allow: 127.0.0.1', @@ -106,6 +115,11 @@ class TestSniIpAllow: ts.Disk.sni_yaml.AddLines([ f' tunnel_route: backend.server.com:{server.Variables.https_port}', ]) + if connect_type == ConnectionType.PROXY: + ts.Disk.sni_yaml.AddLines([ + '- fqdn: pp.allow.me.com', + ' ip_allow: 1.2.3.4', + ]) ts.addDefaultSSLFiles() ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine(f'map / http://remapped.backend.server.com:{server.Variables.http_port}/') @@ -117,7 +131,8 @@ class TestSniIpAllow: 'proxy.config.dns.nameservers': f"127.0.0.1:{dns.Variables.Port}", 'proxy.config.dns.resolv_conf': 'NULL', 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|ssl', + 'proxy.config.diags.debug.tags': 'http|ssl|proxyprotocol', + 'proxy.config.acl.subjects': 'PROXY,PEER', }) if connect_type == ConnectionType.TUNNEL: ts.Disk.records_config.update({ @@ -125,7 +140,8 @@ class TestSniIpAllow: }) return ts - def _configure_client(self, tr: 'TestRun', dns: 'Process', server: 'Process', ts: 'Process') -> None: + def _configure_client( + self, tr: 'TestRun', dns: 'Process', server: 'Process', ts: 'Process', connect_type: ConnectionType.GET) -> None: """Configure the client for the TestRun. :param tr: The TestRun to configure with the client. :param dns: The DNS Process. @@ -133,8 +149,15 @@ class TestSniIpAllow: :param ts: The Traffic Server Process. """ name = f'client{TestSniIpAllow._client_counter}' - p = tr.AddVerifierClientProcess( - name, self._replay_file, http_ports=[ts.Variables.port], https_ports=[ts.Variables.ssl_port]) + if connect_type == ConnectionType.PROXY: + p = tr.AddVerifierClientProcess( + name, + self._replay_file, + http_ports=[ts.Variables.proxy_protocol_port], + https_ports=[ts.Variables.proxy_protocol_ssl_port]) + else: + p = tr.AddVerifierClientProcess( + name, self._replay_file, http_ports=[ts.Variables.port], https_ports=[ts.Variables.ssl_port]) TestSniIpAllow._client_counter += 1 ts.StartBefore(server) ts.StartBefore(dns) @@ -151,3 +174,4 @@ class TestSniIpAllow: TestSniIpAllow(ConnectionType.GET) TestSniIpAllow(ConnectionType.TUNNEL) +TestSniIpAllow(ConnectionType.PROXY)