Skip to content

Commit e1fd38e

Browse files
authored
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
1 parent bfbc349 commit e1fd38e

15 files changed

Lines changed: 540 additions & 49 deletions

File tree

doc/admin-guide/configuration/proxy-protocol.en.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ configured with :ts:cv:`proxy.config.http.proxy_protocol_allowlist`.
5151
:ts:cv:`proxy.config.http.server_ports` configuration, regardless of whether
5252
the connections have the Proxy Protocol header.
5353

54+
By default, |TS| uses client's IP address that is from the peer when it applies ACL. If you configure a port to
55+
enable PROXY protocol and want to apply ACL against the IP address delivered by PROXY protocol, you need to have ``PROXY`` in
56+
:ts:cv:`proxy.config.acl.subjects`.
57+
5458
1. HTTP Forwarded Header
5559

5660
The client IP address in the PROXY protocol header is passed to the origin server via an HTTP `Forwarded:

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2131,6 +2131,20 @@ IP Allow
21312131
the use of this file, see :file:`ip_allow.yaml`. If this is a relative path,
21322132
|TS| loads it relative to the ``SYSCONFDIR`` directory.
21332133

2134+
.. ts:cv:: CONFIG proxy.config.acl.subjects STRING PEER
2135+
2136+
Specifies the list of data sources for getting client's IP address for ACL.
2137+
The value is a comma separated string, and the first available data source
2138+
will be used. If you configure a port to enable PROXY protocol, you probably
2139+
need to adjust this setting to have ``PROXY`` in the list.
2140+
2141+
============= ======================================================================
2142+
Value Description
2143+
============= ======================================================================
2144+
``PEER`` Use the IP address of the peer
2145+
``PROXY`` Use the IP address from PROXY protocol
2146+
============= ======================================================================
2147+
21342148

21352149
Cache Control
21362150
=============

include/proxy/IPAllow.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ class IpAllow : public ConfigInfo
147147

148148
static constexpr const char *MODULE_NAME = "IPAllow";
149149

150+
enum Subject { PEER, PROXY, MAX_SUBJECTS };
151+
150152
/** An access control record and support data.
151153
* The primary point of this is to hold the backing configuration in memory while the ACL
152154
* is in use.
@@ -252,6 +254,8 @@ class IpAllow : public ConfigInfo
252254
*/
253255
static bool has_no_rules();
254256

257+
static uint8_t subjects[Subject::MAX_SUBJECTS];
258+
255259
private:
256260
static size_t configid; ///< Configuration ID for update management.
257261
static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access.

src/iocore/net/SNIActionPerformer.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swoc/swoc_file.h"
2525
#include "swoc/BufferWriter.h"
2626
#include "tscore/Layout.h"
27+
#include "proxy/IPAllow.h"
2728

2829
#include "SNIActionPerformer.h"
2930

@@ -412,8 +413,19 @@ SNI_IpAllow::SNIAction(SSL &ssl, ActionItem::Context const & /* ctx ATS_UNUSED *
412413
return SSL_TLSEXT_ERR_OK;
413414
}
414415

415-
auto ssl_vc = SSLNetVCAccess(&ssl);
416-
auto ip = swoc::IPAddr(ssl_vc->get_remote_endpoint());
416+
auto ssl_vc = SSLNetVCAccess(&ssl);
417+
const sockaddr *client_ip = nullptr;
418+
for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) {
419+
if (IpAllow::Subject::PEER == IpAllow::subjects[i]) {
420+
client_ip = ssl_vc->get_remote_addr();
421+
break;
422+
} else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] &&
423+
ssl_vc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) {
424+
client_ip = ssl_vc->get_proxy_protocol_src_addr();
425+
break;
426+
}
427+
}
428+
swoc::IPAddr ip = swoc::IPAddr(client_ip);
417429

418430
// check the allowed ips
419431
if (ip_addrs.contains(ip)) {

src/proxy/IPAllow.cc

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
limitations under the License.
2525
*/
2626

27+
#include <string_view>
28+
2729
#include "proxy/IPAllow.h"
2830
#include "records/RecCore.h"
2931
#include "swoc/Errata.h"
@@ -67,8 +69,9 @@ enum AclOp {
6769
const IpAllow::Record IpAllow::ALLOW_ALL_RECORD(ALL_METHOD_MASK);
6870
const IpAllow::ACL IpAllow::DENY_ALL_ACL;
6971

70-
size_t IpAllow::configid = 0;
71-
bool IpAllow::accept_check_p = true; // initializing global flag for fast deny
72+
size_t IpAllow::configid = 0;
73+
bool IpAllow::accept_check_p = true; // initializing global flag for fast deny
74+
uint8_t IpAllow::subjects[Subject::MAX_SUBJECTS];
7275

7376
static ConfigUpdateHandler<IpAllow> *ipAllowUpdate;
7477

@@ -212,6 +215,32 @@ IpAllow::IpAllow(const char *ip_allow_config_var, const char *ip_categories_conf
212215
if (!path.empty()) {
213216
ip_categories_config_file = ats_scoped_str(path).get();
214217
}
218+
219+
RecString subjects_char;
220+
REC_ReadConfigStringAlloc(subjects_char, "proxy.config.acl.subjects");
221+
std::string_view subjects_sv{subjects_char};
222+
int i = 0;
223+
std::string_view::size_type s, e;
224+
for (s = 0, e = 0; s < subjects_sv.size() && e != subjects_sv.npos; s = e + 1) {
225+
e = subjects_sv.find(",", s);
226+
std::string_view subject_sv = subjects_sv.substr(s, e);
227+
if (i >= MAX_SUBJECTS) {
228+
Error("Too many ACL subjects were provided");
229+
}
230+
if (subject_sv == "PEER") {
231+
subjects[i] = Subject::PEER;
232+
++i;
233+
} else if (subject_sv == "PROXY") {
234+
subjects[i] = Subject::PROXY;
235+
++i;
236+
} else {
237+
Dbg(dbg_ctl_ip_allow, "Unknown subject %.*s was ignored", static_cast<int>(subject_sv.length()), subject_sv.data());
238+
}
239+
}
240+
if (i < Subject::MAX_SUBJECTS) {
241+
subjects[i] = Subject::MAX_SUBJECTS;
242+
}
243+
ats_free(subjects_char);
215244
}
216245

217246
BufferWriter &

src/proxy/http/HttpSessionAccept.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ HttpSessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferReade
3939
IpAllow::ACL acl;
4040
ip_port_text_buffer ipb;
4141

42+
for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) {
43+
if (IpAllow::Subject::PEER == IpAllow::subjects[i]) {
44+
client_ip = netvc->get_remote_addr();
45+
break;
46+
} else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] &&
47+
netvc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) {
48+
client_ip = netvc->get_proxy_protocol_src_addr();
49+
break;
50+
}
51+
}
4252
acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR);
4353
if (!acl.isValid()) { // if there's no ACL, it's a hard deny.
4454
Warning("client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb)));

src/proxy/http/remap/UrlRewrite.cc

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,26 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const
460460
int method_wksidx = (method != -1) ? (method - HTTP_WKSIDX_CONNECT) : -1;
461461

462462
ink_release_assert(ats_is_ip(&s->client_info.src_addr));
463+
const IpEndpoint *src_addr = nullptr;
464+
const IpEndpoint *local_addr = nullptr;
465+
const ProxyProtocol &pp_info = s->state_machine->get_ua_txn()->get_netvc()->get_proxy_protocol_info();
466+
for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) {
467+
if (IpAllow::Subject::PEER == IpAllow::subjects[i]) {
468+
src_addr = &s->client_info.src_addr;
469+
local_addr = &s->client_info.dst_addr;
470+
break;
471+
} else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] && pp_info.version != ProxyProtocolVersion::UNDEFINED) {
472+
src_addr = &pp_info.src_addr;
473+
local_addr = &pp_info.dst_addr;
474+
break;
475+
}
476+
}
477+
478+
if (src_addr == nullptr) {
479+
// Use addresses from peer if none of the configured sources are avaialable
480+
src_addr = &s->client_info.src_addr;
481+
local_addr = &s->client_info.dst_addr;
482+
}
463483

464484
s->client_connection_allowed = true; // Default is that we allow things unless some filter matches
465485

@@ -487,7 +507,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const
487507
if (rp->src_ip_valid) {
488508
bool src_ip_matches = false;
489509
for (int j = 0; j < rp->src_ip_cnt && !src_ip_matches; j++) {
490-
bool in_range = rp->src_ip_array[j].contains(s->client_info.src_addr);
510+
bool in_range = rp->src_ip_array[j].contains(*src_addr);
491511
if (rp->src_ip_array[j].invert) {
492512
if (!in_range) {
493513
src_ip_matches = true;
@@ -506,7 +526,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const
506526
if (ip_matches && rp->src_ip_category_valid) {
507527
bool category_ip_matches = false;
508528
for (int j = 0; j < rp->src_ip_category_cnt && !category_ip_matches; j++) {
509-
bool in_category = rp->src_ip_category_array[j].contains(s->client_info.src_addr);
529+
bool in_category = rp->src_ip_category_array[j].contains(*src_addr);
510530
if (rp->src_ip_category_array[j].invert) {
511531
if (!in_category) {
512532
category_ip_matches = true;
@@ -525,16 +545,14 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, const url_mapping *const
525545
if (ip_matches && rp->in_ip_valid) {
526546
bool in_ip_matches = false;
527547
for (int j = 0; j < rp->in_ip_cnt && !in_ip_matches; j++) {
528-
IpEndpoint incoming_addr;
529-
incoming_addr.assign(s->state_machine->get_ua_txn()->get_netvc()->get_local_addr());
530548
if (dbg_ctl_url_rewrite.on()) {
531549
char buf1[128], buf2[128], buf3[128];
532-
ats_ip_ntop(incoming_addr, buf1, sizeof(buf1));
550+
ats_ip_ntop(local_addr, buf1, sizeof(buf1));
533551
rp->in_ip_array[j].start.toString(buf2, sizeof(buf2));
534552
rp->in_ip_array[j].end.toString(buf3, sizeof(buf3));
535553
Dbg(dbg_ctl_url_rewrite, "Trying to match incoming address %s in range %s - %s.", buf1, buf2, buf3);
536554
}
537-
bool in_range = rp->in_ip_array[j].contains(incoming_addr);
555+
bool in_range = rp->in_ip_array[j].contains(*local_addr);
538556
if (rp->in_ip_array[j].invert) {
539557
if (!in_range) {
540558
in_ip_matches = true;

src/proxy/http2/Http2SessionAccept.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,20 @@ Http2SessionAccept::~Http2SessionAccept() = default;
4242
bool
4343
Http2SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferReader *reader)
4444
{
45-
sockaddr const *client_ip = netvc->get_remote_addr();
46-
IpAllow::ACL session_acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR);
45+
sockaddr const *client_ip = nullptr;
46+
47+
for (int i = 0; i < IpAllow::Subject::MAX_SUBJECTS; ++i) {
48+
if (IpAllow::Subject::PEER == IpAllow::subjects[i]) {
49+
client_ip = netvc->get_remote_addr();
50+
break;
51+
} else if (IpAllow::Subject::PROXY == IpAllow::subjects[i] &&
52+
netvc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) {
53+
client_ip = netvc->get_proxy_protocol_src_addr();
54+
break;
55+
}
56+
}
57+
58+
IpAllow::ACL session_acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR);
4759
if (!session_acl.isValid()) {
4860
ip_port_text_buffer ipb;
4961
Warning("HTTP/2 client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb)));

src/records/RecordsConfig.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ static const RecordElement RecordsConfig[] =
140140
{RECT_CONFIG, "proxy.config.srv_enabled", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
141141
,
142142

143+
//##############################################################################
144+
//#
145+
//# ACL
146+
//#
147+
//##############################################################################
148+
{RECT_CONFIG, "proxy.config.acl.subjects", RECD_STRING, "PEER", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
149+
,
150+
143151
//##############################################################################
144152
//#
145153
//# Support for disabling check for Accept-* / Content-* header mismatch

tests/gold_tests/ip_allow/ip_allow.test.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ def _configure_traffic_server(self, tr: 'TestRun'):
242242
243243
:param tr: The TestRun object to associate the ts process with.
244244
"""
245-
ts = tr.MakeATSProcess(f"ts-{Test_ip_allow.ts_counter}", enable_quic=self.is_h3, enable_tls=True)
245+
ts = tr.MakeATSProcess(
246+
f"ts-{Test_ip_allow.ts_counter}", enable_quic=self.is_h3, enable_tls=True, enable_proxy_protocol=True)
246247

247248
Test_ip_allow.ts_counter += 1
248249
self._ts = ts
@@ -252,13 +253,14 @@ def _configure_traffic_server(self, tr: 'TestRun'):
252253
self._ts.Disk.records_config.update(
253254
{
254255
'proxy.config.diags.debug.enabled': 1,
255-
'proxy.config.diags.debug.tags': 'v_quic|quic|http|ip_allow',
256+
'proxy.config.diags.debug.tags': 'v_quic|quic|http|ip_allow|proxyprotocol',
256257
'proxy.config.http.push_method_enabled': 1,
257258
'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
258259
'proxy.config.quic.no_activity_timeout_in': 0,
259260
'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
260261
'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
261262
'proxy.config.http.connect_ports': f"{self._server.Variables.http_port}",
263+
'proxy.config.acl.subjects': 'PROXY,PEER',
262264
})
263265

264266
self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}')
@@ -278,6 +280,7 @@ def run(self):
278280
tr.AddVerifierClientProcess(
279281
f'client-{Test_ip_allow.client_counter}',
280282
self.replay_file,
283+
http_ports=[self._ts.Variables.proxy_protocol_port],
281284
https_ports=[self._ts.Variables.ssl_port],
282285
http3_ports=[self._ts.Variables.ssl_port],
283286
keys=self.replay_keys)
@@ -333,3 +336,20 @@ def run(self):
333336
is_h3=False,
334337
expect_request_rejected=False)
335338
test_ip_allow_optional_methods.run()
339+
340+
# TEST 7: Verify IP address from PROXY protocol is used.
341+
IP_ALLOW_CONFIG_PROXY_PROTOCOL = '''ip_allow:
342+
- apply: in
343+
ip_addrs: 1.2.3.4
344+
action: allow
345+
- apply: in
346+
ip_addrs: 0/0
347+
action: deny
348+
'''
349+
test_ip_allow_proxy_protocol = Test_ip_allow(
350+
"ip_allow_proxy_protocol",
351+
replay_file='replays/http_proxy_protocol.replay.yaml',
352+
ip_allow_config=IP_ALLOW_CONFIG_PROXY_PROTOCOL,
353+
is_h3=False,
354+
expect_request_rejected=False)
355+
test_ip_allow_proxy_protocol.run()

0 commit comments

Comments
 (0)