diff --git a/forwarding/__init__.py b/forwarding/__init__.py new file mode 100644 index 000000000..b680c3725 --- /dev/null +++ b/forwarding/__init__.py @@ -0,0 +1,3 @@ +__all__ = ['test_match_host_forwarded', 'test_forwarded_hdr'] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/forwarding/test_forwarded_hdr.py b/forwarding/test_forwarded_hdr.py new file mode 100644 index 000000000..72dc2a077 --- /dev/null +++ b/forwarding/test_forwarded_hdr.py @@ -0,0 +1,184 @@ +""" +Tests for validate Forwarded header. +""" +from helpers import chains +from framework import tester + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2022 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +class TestForwardedBase(tester.TempestaTest, base=True): + + backends = [ + { + 'id' : 'backend1', + 'type' : 'deproxy', + 'port' : '8000', + 'response' : 'static', + 'response_content' : + 'HTTP/1.1 200 OK\r\n' + 'Content-Length: 0\r\n\r\n' + } + ] + + tempesta = { + 'config' : + """ + srv_group grp1 { + server ${server_ip}:8000; + } + vhost app { + proxy_pass grp1; + } + http_chain { + -> app; + } + """ + } + + clients = [ + { + 'id' : 'deproxy', + 'type' : 'deproxy', + 'addr' : "${tempesta_ip}", + 'port' : '80' + } + ] + + req_params = [] + + response_status = '200' + + def setUp(self): + tester.TempestaTest.setUp(self) + + def start_all(self): + self.start_all_servers() + self.start_tempesta() + self.start_all_clients() + self.deproxy_manager.start() + self.assertTrue(self.wait_all_connections()) + + def prepare_request(self, req_param): + req = ('GET / HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Forwarded: %s\r\n\r\n' % req_param) + + return req + + def client_send_req(self, client, req): + curr_responses = len(client.responses) + client.make_requests(req) + client.wait_for_response(timeout=1) + self.assertEqual(curr_responses + 1, len(client.responses)) + + return client.last_response + + def test_run(self): + self.start_all() + client = self.get_client('deproxy') + for param in self.req_params: + req = self.prepare_request(param) + resp = self.client_send_req(client, req) + self.assertEqual(resp.status, self.response_status) + client.restart() + +class TestForwardedBaseAllowed(TestForwardedBase): + """ + Test of allowed requests. Test fails, if status of any + of requests not equal 200 + """ + + req_params = [ + 'host=example.com', + 'host=example.com:8080', + 'for=1.1.1.1', + 'for=1.1.1.1:8080', + 'by=2.2.2.2', + 'by=2.2.2.2:8080', + 'proto=http', + 'host=example.com;for=1.1.1.1', + 'host=example.com;for=1.1.1.1;by=2.2.2.2', + 'host=example.com;for=1.1.1.1;by=2.2.2.2;proto=http', + 'host=example.com;for=1.1.1.1, for=2.3.3.4', + 'for=1.1.1.1, for=2.3.3.4, for=4.5.2.1', + 'for="_gazonk"', + 'For="[2001:db8:cafe::17]:4711"', + 'for=192.0.2.60;proto=http;by=203.0.113.43', + 'for=192.0.2.43, for=198.51.100.17', + 'for=_hidden, for=_SEVKISEK' + ] + +class TestForwardedBaseDisallowed(TestForwardedBase): + """ + Test of disallowed requests. Test fails, if status of any + of requests not equal 400 + """ + response_status = '400' + + req_params = [ + 'host=example.com:0', + 'host=example.com:65536', + 'host=example.com:8080;', + 'host=example.com:', + 'host=[1:2:3]', + 'host="[1:2:3]:"', + 'host="[1:aabb:3:kk]"', + 'host=[]', + 'host="[]"' + 'host=example.com; for=1.1.1.1', + 'host=example.com ;for=1.1.1.1', + 'host=example.com ; for=1.1.1.1', + 'myparam=123', + 'host=example.com;myparap=123', + 'for=1.1.1.$', + 'for=1".1.1.1"', + 'by=1.1.1.$', + 'by=1".1.1.1"', + 'proto=h"ttp"s', + 'proto=ht/tp', + 'for=;' + 'by=;', + 'proto=;', + 'host=;' + ] + +class TestForwardedBaseMalicious(TestForwardedBase): + """ + Test of malicious requests. Test fails, if status of any + of requests not equal 400. + For each pattern stored in 'req_params' we append + each malicious string stored in 'malicious' + """ + response_status = '400' + + req_params = [ + 'for=%s', + 'host=%s', + 'by=%s', + 'proto=%s', + 'host=%s;for=1.1.1.1;by=2.2.2.2;proto=http', + 'host=example.com;for=%s;by=2.2.2.2;proto=http', + 'host=example.com;for=1.1.1.1;by=%s;proto=http', + 'host=example.com;for=1.1.1.1;by=2.2.2.2;proto=%s' + ] + + malicious = [ + '', + '">', + '" onlick=alert(1)', + '\' sqlinj' + ] + + def test_malicious(self): + self.start_all() + client = self.get_client('deproxy') + for param in self.req_params: + for evil_str in self.malicious: + req = self.prepare_request(param % (evil_str)) + resp = self.client_send_req(client, req) + self.assertEqual(resp.status, self.response_status) + client.restart() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/forwarding/test_match_host_forwarded.py b/forwarding/test_match_host_forwarded.py new file mode 100644 index 000000000..7480ee746 --- /dev/null +++ b/forwarding/test_match_host_forwarded.py @@ -0,0 +1,250 @@ +""" +Tests for verifying correctness of matching +all host headers (URI, Host, Forwarded). +""" +from helpers import chains +from framework import tester + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2022 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +class TestMatchHost(tester.TempestaTest): + + backends = [ + { + 'id' : 0, + 'type' : 'deproxy', + 'port' : '8000', + 'response' : 'static', + 'response_content' : + 'HTTP/1.1 200 OK\r\n' + 'Content-Length: 0\r\n\r\n' + }, + { + 'id' : 1, + 'type' : 'deproxy', + 'port' : '8001', + 'response' : 'static', + 'response_content' : + 'HTTP/1.1 200 OK\r\n' + 'Content-Length: 0\r\n\r\n' + }, + { + 'id' : 2, + 'type' : 'deproxy', + 'port' : '8002', + 'response' : 'static', + 'response_content' : + 'HTTP/1.1 200 OK\r\n' + 'Content-Length: 0\r\n\r\n' + } + ] + + tempesta = { + 'config' : + """ + block_action attack reply; + srv_group grp1 { + server ${server_ip}:8000; + } + srv_group grp2 { + server ${server_ip}:8001; + } + srv_group grp3 { + server ${server_ip}:8002; + } + vhost shop { + proxy_pass grp1; + } + vhost wiki { + proxy_pass grp2; + } + vhost app { + proxy_pass grp3; + } + http_chain { + hdr host == "testapp.com" -> app; + hdr forwarded == "host=testshop.com" -> shop; + host == "badhost.com" -> block; + host == "testshop.com" -> shop; + host == "testwiki.com" -> wiki; + host == "testapp.com" -> app; + host == [fd80::1cb2:ad12:ca16:98ef]:8080 -> app; + -> block; + } + """ + } + + requests_opt = [ + { + 'uri' : 'http://testshop.com', #<--must be matched by "host eq" + 'headers' : [ + ('Host', 'testwiki.com'), + ('Forwarded', 'host=testapp.com') + ], + 'block' : False, + 'sid' : 0 + }, + { + 'uri' : 'http://testshop.com', #<--must be matched by "host eq" + 'headers' : [ + ('Host', 'badhost.com'), + ('Forwarded', 'host=badhost.com') + ], + 'block' : False, + 'sid' : 0 + }, + { + 'uri' : 'http://testshop.com', + 'headers' : [ + ('Host', 'testapp.com'), #<--must be matched by "hdr host == testapp.com" + ('Forwarded', 'host=testwiki.com') + ], + 'block' : False, + 'sid' : 2 + }, + { + 'uri' : 'http://badhost.com', + 'headers' : [ + ('Host', 'badhost.com'), + ('Forwarded', 'host=testshop.com') #<--must be matched by "hdr forwarded" + ], + 'block' : False, + 'sid' : 0 + }, + { + 'uri' : 'http://badhost.com', + 'headers' : [ + ('Host', 'badhost.com'), + ('Forwarded', 'host=unkhost.com'), + ('Forwarded', 'host=testshop.com') #<--must be matched by "hdr forwarded" + ], + 'block' : False, + 'sid' : 0 + }, + { + 'uri' : '/foo', + 'headers' : [ + ('Host', 'testwiki.com'), #<--must be matched by "host eq" + ('Forwarded', 'host=testapp.com') + ], + 'block' : False, + 'sid' : 1 + }, + { + 'uri' : '/foo', + 'headers' : [ + ('Host', 'unkhost.com'), + ('Forwarded', 'host=testapp.com') #<--must be matched by "host eq" + ], + 'block' : False, + 'sid' : 2 + }, + { + 'uri' : '/foo', + 'headers' : [ + ('Host', 'unkhost.com'), + ('Forwarded', 'HoSt=TesTaPp.cOm') #<--must be matched by "host eq" + ], + 'block' : False, + 'sid' : 2 + }, + { + 'uri' : '/foo', + 'headers' : [ + ('Host', 'unkhost.com'), + ('Forwarded', 'host="[fd80::1cb2:ad12:ca16:98ef]:8080"') #<--must be matched by "host eq" + ], + 'block' : False, + 'sid' : 2 + }, + { + 'uri' : '/foo', + 'headers' : [ + ('Host', 'badhost.com'), + ('Forwarded', 'host=testapp.com') + ], + 'block' : True, + 'sid' : 0 + }, + { + 'uri' : '/foo', + 'headers' : [ + ('Host', 'unkhost.com') + ], + 'block' : True, + 'sid' : 0 + } + ] + + blocked_response_status = '403' + chains = [] + + def add_client(self, cid): + client = { + 'id' : cid, + 'type' : 'deproxy', + 'addr' : "${tempesta_ip}", + 'port' : '80' + } + self.clients.append(client) + + def init_chain(self, req_opt): + ch = chains.base(uri=req_opt['uri']) + if req_opt['block']: + for header, value in req_opt['headers']: + ch.request.headers.delete_all(header) + ch.request.headers.add(header, value) + ch.request.update() + ch.fwd_request = None + else: + for request in [ch.request, ch.fwd_request]: + for header, value in req_opt['headers']: + request.headers.delete_all(header) + request.headers.add(header, value) + request.update() + self.chains.append(ch) + + def setUp(self): + del(self.chains[:]) + count = len(self.requests_opt) + for i in range(count): + self.init_chain(self.requests_opt[i]) + self.add_client(i) + tester.TempestaTest.setUp(self) + + def start_all(self): + self.start_all_servers() + self.start_tempesta() + self.start_all_clients() + self.deproxy_manager.start() + self.assertTrue(self.wait_all_connections()) + + def process(self, client, server, chain): + client.make_request(chain.request.msg) + client.wait_for_response() + + if chain.fwd_request: + chain.fwd_request.set_expected() + self.assertEqual(server.last_request, chain.fwd_request) + else: + last_response_status = client.last_response.status + self.assertEqual(self.blocked_response_status, last_response_status) + + def test_chains(self): + """ + Send requests with different hosts + and check correctness of forwarding + by compare last request on client and + server. + """ + self.start_all() + count = len(self.chains) + for i in range(count): + sid = self.requests_opt[i]['sid'] + self.process(self.get_client(i), + self.get_server(sid), + self.chains[i]) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests_disabled.json b/tests_disabled.json index d3654f840..419210b35 100644 --- a/tests_disabled.json +++ b/tests_disabled.json @@ -407,6 +407,9 @@ }, { "name" : "multiple_listeners", "reason" : "Disabled by issue #1624" + }, + { "name" : "forwarding", + "reason" : "Disabled, because PR #1629 still not marged" } ] }