def run_test(self, status_code=200, is_frang=False): klog = dmesg.DmesgFinder(ratelimited=False) curl = self.get_client('curl') referer = 'http2-referer-%d' % status_code user_agent = 'http2-user-agent-%d' % status_code curl.options.append('-e "%s"' % referer) curl.options.append('-A "%s"' % user_agent) self.start_all_servers() self.start_tempesta() if is_frang: try: self.run_curl(curl) except: pass else: self.run_curl(curl) nginx = self.get_server('nginx') nginx.get_stats() self.assertEqual(0 if is_frang else 1, nginx.requests, msg="Unexpected number forwarded requests to backend") msg = AccessLogLine.from_dmesg(klog) self.assertTrue(msg is not None, "No access_log message in dmesg") self.assertEqual(msg.method, 'GET', 'Wrong method') self.assertEqual(msg.status, status_code, 'Wrong HTTP status') self.assertEqual(msg.user_agent, user_agent) self.assertEqual(msg.referer, referer) return msg
def test_with_tlsperf(self): """ Test with tls-perf which uses usual openssl configuration and works as most usual ssl clients. tls-perf doesn't have fixed frame rate, but it works fast and we use small limits to ensure that all messages are received almost immediately. """ self.start_all_servers() self.start_tempesta() srv = self.get_server('0') self.deproxy_manager.start() self.assertTrue(srv.wait_for_connections(timeout=1)) klog = dmesg.DmesgFinder(ratelimited=False) tls_perf = self.get_client('tls-perf-with-tickets') tls_perf.start() self.wait_while_busy(tls_perf) self.assertEqual(klog.warn_count(self.TLS_WARN), 0, "Frang limits warning was incorrectly shown") tls_perf = self.get_client('tls-perf') tls_perf.start() self.wait_while_busy(tls_perf) self.assertEqual(klog.warn_count(self.TLS_WARN), 1, "Frang limits warning is not shown")
def test_host_sni_mismatch(self): """ With the `http_host_required` limit, the host header and SNI name must be identical. Otherwise request will be filtered. After client send a request that doesnt match his SNI, t is blocked """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "Host: tempesta-tech.com\r\n" \ "\r\n" \ "GET / HTTP/1.1\r\n" \ "Host: tempesta-tech.com \r\n" \ "\r\n" \ "GET / HTTP/1.1\r\n" \ "Host: example.com\r\n" \ "\r\n" deproxy_cl = self.get_client('usual-client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(2, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.TLS_WARN), 1, "Frang limits warning is not shown")
def setUp(self): tf_cfg.dbg(3, '\tInit test case...') if not remote.wait_available(): raise Exception("Tempesta node is unavaliable") self.oops = dmesg.DmesgFinder() self.oops_ignore = [] self.__create_servers() self.__create_tempesta() self.__create_clients()
def test_success_path_http1x(self): self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) for status, body in [ (200, 'body http ok'), (204, None), (302, 'redirect body'), (404, 'not-found body'), (500, 'internal-server-error body'), ]: self.check_response(klog, status, body)
def test_bad_user_agent(self): self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) req = self.make_request('/some-uri', user_agent='bad\nagent', referer='Ok-Referer') msg = self.send_request_and_get_dmesg(klog, req) self.assertTrue(msg is not None, "No access_log message in dmesg") self.assertNotEqual(msg.status, 0, 'Empty response status') # Make sure that some fields are properly set self.assertEqual(msg.method, 'GET', 'Wrong method') self.assertEqual(msg.uri, '/some-uri', 'Wrong uri') self.assertNotEqual(msg.ip, '-', 'Wrong ip')
def setUp(self): # Init members used in tearDown function. self.oops = dmesg.DmesgFinder() self.oops_ignore = [] self.tempesta = None self.servers = [] tf_cfg.dbg(3) # Step to the next line after name of test case. tf_cfg.dbg(3, '\tInit test case...') if not remote.wait_available(): raise Exception("Tempesta node is unavaliable") self.create_clients() self.create_servers() self.create_tempesta()
def test_frang(self): self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) req = self.make_request('/longer-than-10-symbols-uri', user_agent='user-agent', referer='referer') msg = self.send_request_and_get_dmesg(klog, req) self.assertTrue(msg is not None, 'No access_log message in dmesg') self.assertEqual(msg.method, 'GET', 'Wrong method') self.assertEqual(msg.status, 403, 'Wrong HTTP status') self.assertEqual(msg.uri, '/longer-than-10-symbols-uri', 'Wrong uri') self.assertEqual(msg.user_agent, 'user-agent', 'Wrong user-agent') self.assertEqual(msg.referer, 'referer', 'Wrong referer') self.assertNotEqual(msg.ip, '-', 'Wrong ip')
def test_uri_truncate(self): self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) req = self.make_request('/too-long-uri_' + '1' * 4000, user_agent='user-agent', referer='referer') msg = self.send_request_and_get_dmesg(klog, req) self.assertTrue(msg is not None, "No access_log message in dmesg") self.assertEqual(msg.method, 'GET', 'Wrong method') self.assertEqual(msg.status, 200, 'Wrong HTTP status') self.assertEqual(msg.uri[:len('/too-long-uri_1111')], '/too-long-uri_1111', 'Wrong uri prefix') self.assertEqual(msg.uri[-4:], '1...', 'URI does not looks truncated') self.assertEqual(msg.user_agent, 'user-agent', 'Wrong user-agent') self.assertEqual(msg.referer, 'referer', 'Wrong referer') self.assertNotEqual(msg.ip, '-', 'Wrong ip')
def test_host_missing(self): """ Host header is missing, but required. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "\r\n" deproxy_cl = self.get_client('client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(0, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.WARN_UNKNOWN), 1, "Frang limits warning is not shown")
def test_host_sni_bypass_check(self): """ SNI is not set. Requests to any ports are allowed. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "Host: example.com\r\n" \ "\r\n" deproxy_cl = self.get_client('no-sni-client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(1, len(deproxy_cl.responses)) self.assertEqual(klog.warn_count(self.TLS_WARN), 0, "Frang limits warning was unexpectedly shown")
def test_host_empty(self): """ Host header has empty value. Restricted by Tempesta security rules. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "Host: \r\n" \ "\r\n" deproxy_cl = self.get_client('client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(0, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.WARN_UNKNOWN), 1, "Frang limits warning is not shown")
def test_host_mismatch(self): """ Host header and authority in uri has different values. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET http://[email protected]/ HTTP/1.1\r\n" \ "Host: example.com\r\n" \ "\r\n" deproxy_cl = self.get_client('client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(0, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.WARN_DIFFER), 1, "Frang limits warning is not shown")
def try_tls_vers(self, version): klog = dmesg.DmesgFinder(ratelimited=False) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(self.io_to) sock.connect((self.addr, self.port)) try: context = ssl.SSLContext(protocol=version) tls_sock = context.wrap_socket(sock) except ssl.SSLError as e: # Correct connection termination with PROTOCOL_VERSION alert. if e.reason == "TLSV1_ALERT_PROTOCOL_VERSION": return True except IOError as e: if self.verbose: print("TLS handshake failed w/o warning") if self.verbose: print("Connection of unsupported TLS 1.%d established" % version) return False
def test_host_old_proto(self): """ Host header in http request below http/1.1. Restricted by Tempesta security rules. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.0\r\n" \ "Host: tempesta-tech.com\r\n" \ "\r\n" deproxy_cl = self.get_client('client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(0, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.WARN_OLD_PROTO), 1, "Frang limits warning is not shown")
def test_auto_port_mismatch(self): """ After client send a request that has port mismatch in host header, # it is blocked. Port is defined from implicit values. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "Host: tempesta-tech.com\r\n" \ "\r\n" deproxy_cl = self.get_client('over-444-port') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(0, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.TLS_WARN_PORT), 1, "Frang limits warning is not shown")
def test_host_mismatch_empty(self): """ Host header is empty, only authority in uri points to specific virtual host. Not allowed by RFC. """ self.start_all() klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET http://[email protected]/ HTTP/1.1\r\n" \ "Host: \r\n" \ "\r\n" deproxy_cl = self.get_client('client') deproxy_cl.start() deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() self.assertEqual(0, len(deproxy_cl.responses)) self.assertTrue(deproxy_cl.connection_is_closed()) self.assertEqual(klog.warn_count(self.WARN_UNKNOWN), 1, "Frang limits warning is not shown")
def test_pass(self): """ Authority header contains host in DNS form, request is allowed. """ curl = self.get_client('curl-dns') self.start_all_servers() self.start_tempesta() srv = self.get_server('0') self.deproxy_manager.start() self.assertTrue(srv.wait_for_connections(timeout=1)) klog = dmesg.DmesgFinder(ratelimited=False) curl.start() self.wait_while_busy(curl) self.assertEqual(0, curl.returncode, msg=("Curl return code is not 0 (%d)." % (curl.returncode))) self.assertEqual(klog.warn_count(self.WARN_IP_ADDR), 0, "Frang limits warning is incorrectly shown") curl.stop()
def test_block(self): """ Authority header contains name in IP address form, request is rejected. """ curl = self.get_client('curl-ip') self.start_all_servers() self.start_tempesta() srv = self.get_server('0') self.deproxy_manager.start() self.assertTrue(srv.wait_for_connections(timeout=1)) klog = dmesg.DmesgFinder(ratelimited=False) curl.start() self.wait_while_busy(curl) self.assertEqual(1, curl.returncode, msg=("Curl return code is not 1 (%d)." % (curl.returncode))) self.assertEqual(klog.warn_count(self.WARN_IP_ADDR), 1, "Frang limits warning is not shown") curl.stop()
def test(self): self.start_all_servers() self.start_tempesta() self.deproxy_manager.start() srv = self.get_server('0') self.assertTrue(srv.wait_for_connections(timeout=1)) self.klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "Host: tempesta-tech.com\r\n" \ "\r\n" connected = 0 not_connected = 0 for i in range(11): deproxy_cl = self.get_client('%d' % i) deproxy_cl.start() # Push some data as request, but it will be sent as plain HTTP # to a TLS socket, so 'Bad TLS record' handshake error happens # on the Tempesta side. Since a TLS connection isn't established, # not TLS alerts are send by Tempesta. deproxy_cl.make_requests(requests) # Give some time to process events. time.sleep(0.01) if not deproxy_cl.connection_is_closed(): connected += 1 else: not_connected += 1 # Need to stop client or TLS warning is now shown until Tempesta # shutdown deproxy_cl.stop() self.assertEqual(0, connected) self.assertEqual(11, not_connected) wc = self.klog.warn_count(self.TLS_WARN) # We run more requests to be in time for the limit, so there could be # several warnings. self.assertTrue(wc >= 1, "Frang limits warning is not shown")
def test_with_deproxy(self): """ Test with deproxy, Python has no session resumption on client side, thus clients will use a new TLS session every time. If self.BURST is disabled, make a pause to open connections slower. Don't use high limit values here: deproxy is not fast. """ self.start_all_servers() self.start_tempesta() self.deproxy_manager.start() srv = self.get_server('0') self.assertTrue(srv.wait_for_connections(timeout=1)) klog = dmesg.DmesgFinder(ratelimited=False) requests = "GET / HTTP/1.1\r\n" \ "Host: tempesta-tech.com\r\n" \ "\r\n" connected = 0 not_connected = 0 for i in range(11): deproxy_cl = self.get_client('%d' % i) deproxy_cl.start() # Test works more stable if client sends a request deproxy_cl.make_requests(requests) deproxy_cl.wait_for_response() if not deproxy_cl.connection_is_closed(): connected += 1 else: not_connected += 1 if i == 5 and not self.BURST: time.sleep(0.5) self.assertEqual(1, not_connected) self.assertEqual(10, connected) self.assertEqual(klog.warn_count(self.TLS_WARN), 1, "Frang limits warning is not shown")