github.com/nginxinc/kubernetes-ingress@v1.12.5/tests/suite/test_app_protect.py (about) 1 import requests 2 import pytest, time 3 4 from settings import TEST_DATA, DEPLOYMENTS 5 from suite.custom_resources_utils import ( 6 create_ap_logconf_from_yaml, 7 create_ap_policy_from_yaml, 8 delete_ap_policy, 9 delete_ap_logconf, 10 ) 11 from suite.resources_utils import ( 12 wait_before_test, 13 create_example_app, 14 wait_until_all_pods_are_ready, 15 create_items_from_yaml, 16 delete_items_from_yaml, 17 delete_common_app, 18 ensure_connection_to_public_endpoint, 19 create_ingress_with_ap_annotations, 20 ensure_response_from_backend, 21 wait_before_test, 22 get_events, 23 ) 24 from suite.yaml_utils import get_first_ingress_host_from_yaml 25 26 27 ap_policies_under_test = ["dataguard-alarm", "file-block", "malformed-block"] 28 valid_resp_addr = "Server address:" 29 valid_resp_name = "Server name:" 30 invalid_resp_title = "Request Rejected" 31 invalid_resp_body = "The requested URL was rejected. Please consult with your administrator." 32 33 34 class BackendSetup: 35 """ 36 Encapsulate the example details. 37 38 Attributes: 39 req_url (str): 40 ingress_host (str): 41 """ 42 43 def __init__(self, req_url, req_url_2, ingress_host): 44 self.req_url = req_url 45 self.req_url_2 = req_url_2 46 self.ingress_host = ingress_host 47 48 49 @pytest.fixture(scope="function") 50 def backend_setup(request, kube_apis, ingress_controller_endpoint, test_namespace) -> BackendSetup: 51 """ 52 Deploy a simple application and AppProtect manifests. 53 54 :param request: pytest fixture 55 :param kube_apis: client apis 56 :param ingress_controller_endpoint: public endpoint 57 :param test_namespace: 58 :return: BackendSetup 59 """ 60 policy = request.param["policy"] 61 print("------------------------- Deploy backend application -------------------------") 62 create_example_app(kube_apis, "simple", test_namespace) 63 req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend1" 64 req_url_2 = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend2" 65 wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) 66 ensure_connection_to_public_endpoint( 67 ingress_controller_endpoint.public_ip, 68 ingress_controller_endpoint.port, 69 ingress_controller_endpoint.port_ssl, 70 ) 71 72 print("------------------------- Deploy Secret -----------------------------") 73 src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" 74 create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) 75 76 print("------------------------- Deploy logconf -----------------------------") 77 src_log_yaml = f"{TEST_DATA}/appprotect/logconf.yaml" 78 log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace) 79 80 print(f"------------------------- Deploy appolicy: {policy} ---------------------------") 81 src_pol_yaml = f"{TEST_DATA}/appprotect/{policy}.yaml" 82 pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, test_namespace) 83 84 print("------------------------- Deploy ingress -----------------------------") 85 ingress_host = {} 86 src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" 87 create_ingress_with_ap_annotations( 88 kube_apis, src_ing_yaml, test_namespace, policy, "True", "True", "127.0.0.1:514" 89 ) 90 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 91 wait_before_test() 92 93 def fin(): 94 print("Clean up:") 95 src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" 96 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 97 delete_ap_policy(kube_apis.custom_objects, pol_name, test_namespace) 98 delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) 99 delete_common_app(kube_apis, "simple", test_namespace) 100 src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" 101 delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) 102 103 request.addfinalizer(fin) 104 105 return BackendSetup(req_url, req_url_2, ingress_host) 106 107 108 @pytest.mark.skip_for_nginx_oss 109 @pytest.mark.appprotect 110 @pytest.mark.smoke 111 @pytest.mark.parametrize( 112 "crd_ingress_controller_with_ap", 113 [{"extra_args": [f"-enable-custom-resources", f"-enable-app-protect"]}], 114 indirect=True, 115 ) 116 class TestAppProtect: 117 @pytest.mark.parametrize("backend_setup", [{"policy": "dataguard-alarm"}], indirect=True) 118 def test_responses_dataguard_alarm( 119 self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace 120 ): 121 """ 122 Test dataguard-alarm AppProtect policy: Block malicious script in url 123 """ 124 print("------------- Run test for AP policy: dataguard-alarm --------------") 125 print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") 126 127 ensure_response_from_backend( 128 backend_setup.req_url, backend_setup.ingress_host, check404=True 129 ) 130 131 print("----------------------- Send valid request ----------------------") 132 resp_valid = requests.get( 133 backend_setup.req_url, headers={"host": backend_setup.ingress_host}, verify=False 134 ) 135 print(resp_valid.text) 136 assert valid_resp_addr in resp_valid.text 137 assert valid_resp_name in resp_valid.text 138 assert resp_valid.status_code == 200 139 140 print("---------------------- Send invalid request ---------------------") 141 resp_invalid = requests.get( 142 backend_setup.req_url + "/<script>", 143 headers={"host": backend_setup.ingress_host}, 144 verify=False, 145 ) 146 print(resp_invalid.text) 147 assert invalid_resp_title in resp_invalid.text 148 assert invalid_resp_body in resp_invalid.text 149 assert resp_invalid.status_code == 200 150 151 @pytest.mark.parametrize("backend_setup", [{"policy": "file-block"}], indirect=True) 152 def test_responses_file_block( 153 self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace 154 ): 155 """ 156 Test file-block AppProtect policy: Block executing types e.g. .bat and .exe 157 """ 158 print("------------- Run test for AP policy: file-block --------------") 159 print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") 160 161 ensure_response_from_backend( 162 backend_setup.req_url, backend_setup.ingress_host, check404=True 163 ) 164 165 print("----------------------- Send valid request ----------------------") 166 resp_valid = requests.get( 167 backend_setup.req_url, headers={"host": backend_setup.ingress_host}, verify=False 168 ) 169 print(resp_valid.text) 170 assert valid_resp_addr in resp_valid.text 171 assert valid_resp_name in resp_valid.text 172 assert resp_valid.status_code == 200 173 174 print("---------------------- Send invalid request ---------------------") 175 resp_invalid = requests.get( 176 backend_setup.req_url + "/test.bat", 177 headers={"host": backend_setup.ingress_host}, 178 verify=False, 179 ) 180 print(resp_invalid.text) 181 assert invalid_resp_title in resp_invalid.text 182 assert invalid_resp_body in resp_invalid.text 183 assert resp_invalid.status_code == 200 184 185 @pytest.mark.parametrize("backend_setup", [{"policy": "malformed-block"}], indirect=True) 186 def test_responses_malformed_block( 187 self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace 188 ): 189 """ 190 Test malformed-block blocking AppProtect policy: Block requests with invalid json or xml body 191 """ 192 print("------------- Run test for AP policy: malformed-block --------------") 193 print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") 194 195 ensure_response_from_backend( 196 backend_setup.req_url, backend_setup.ingress_host, check404=True 197 ) 198 199 print("----------------------- Send valid request with no body ----------------------") 200 headers = {"host": backend_setup.ingress_host} 201 resp_valid = requests.get(backend_setup.req_url, headers=headers, verify=False) 202 print(resp_valid.text) 203 assert valid_resp_addr in resp_valid.text 204 assert valid_resp_name in resp_valid.text 205 assert resp_valid.status_code == 200 206 207 print("----------------------- Send valid request with body ----------------------") 208 headers = {"Content-Type": "application/json", "host": backend_setup.ingress_host} 209 resp_valid = requests.post(backend_setup.req_url, headers=headers, data="{}", verify=False) 210 print(resp_valid.text) 211 assert valid_resp_addr in resp_valid.text 212 assert valid_resp_name in resp_valid.text 213 assert resp_valid.status_code == 200 214 215 print("---------------------- Send invalid request ---------------------") 216 resp_invalid = requests.post( 217 backend_setup.req_url, 218 headers=headers, 219 data="{{}}", 220 verify=False, 221 ) 222 print(resp_invalid.text) 223 assert invalid_resp_title in resp_invalid.text 224 assert invalid_resp_body in resp_invalid.text 225 assert resp_invalid.status_code == 200 226 227 @pytest.mark.parametrize("backend_setup", [{"policy": "csrf"}], indirect=True) 228 def test_responses_csrf( 229 self, 230 kube_apis, 231 ingress_controller_endpoint, 232 crd_ingress_controller_with_ap, 233 backend_setup, 234 test_namespace, 235 ): 236 """ 237 Test CSRF (Cross Site Request Forgery) AppProtect policy: Block requests with invalid/null/non-https origin-header 238 """ 239 print("------------- Run test for AP policy: CSRF --------------") 240 print(f"Request URL without CSRF protection: {backend_setup.req_url}") 241 print(f"Request URL with CSRF protection: {backend_setup.req_url_2}") 242 243 ensure_response_from_backend( 244 backend_setup.req_url_2, backend_setup.ingress_host, check404=True 245 ) 246 247 print("----------------------- Send request with http origin header ----------------------") 248 249 headers = {"host": backend_setup.ingress_host, "Origin": "http://appprotect.example.com"} 250 resp_valid = requests.post( 251 backend_setup.req_url, headers=headers, verify=False, cookies={"flavor": "darkchoco"} 252 ) 253 resp_invalid = requests.post( 254 backend_setup.req_url_2, headers=headers, verify=False, cookies={"flavor": "whitechoco"} 255 ) 256 257 print(resp_valid.text) 258 print(resp_invalid.text) 259 260 assert valid_resp_addr in resp_valid.text 261 assert valid_resp_name in resp_valid.text 262 assert resp_valid.status_code == 200 263 assert invalid_resp_title in resp_invalid.text 264 assert invalid_resp_body in resp_invalid.text 265 assert resp_invalid.status_code == 200 266 267 @pytest.mark.parametrize("backend_setup", [{"policy": "ap-user-def-browser"}], indirect=True) 268 def test_responses_user_def_browser( 269 self, 270 crd_ingress_controller_with_ap, 271 backend_setup, 272 ): 273 """ 274 Test User defined browser AppProtect policy: Block requests from built-in and user-defined browser based on action in policy. 275 """ 276 print("------------- Run test for AP policy: User Defined Browser --------------") 277 print(f"Request URL: {backend_setup.req_url}") 278 279 ensure_response_from_backend( 280 backend_setup.req_url, backend_setup.ingress_host, check404=True 281 ) 282 283 print("----------------------- Send request with User-Agent: browser ----------------------") 284 285 headers_firefox = { 286 "host": backend_setup.ingress_host, 287 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/59.0", 288 } 289 resp_firefox = requests.get(backend_setup.req_url, headers=headers_firefox, verify=False) 290 headers_chrome = { 291 "host": backend_setup.ingress_host, 292 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Chrome/76.0.3809.100", 293 } 294 resp_chrome = requests.get(backend_setup.req_url_2, headers=headers_chrome, verify=False) 295 headers_safari = { 296 "host": backend_setup.ingress_host, 297 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Safari/537.36", 298 } 299 resp_safari = requests.get(backend_setup.req_url_2, headers=headers_safari, verify=False) 300 headers_custom1 = { 301 "host": backend_setup.ingress_host, 302 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 custombrowser1/0.1", 303 } 304 resp_custom1 = requests.get(backend_setup.req_url_2, headers=headers_custom1, verify=False) 305 headers_custom2 = { 306 "host": backend_setup.ingress_host, 307 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 custombrowser2/0.1", 308 } 309 resp_custom2 = requests.get(backend_setup.req_url_2, headers=headers_custom2, verify=False) 310 311 assert ( 312 200 313 == resp_firefox.status_code 314 == resp_chrome.status_code 315 == resp_safari.status_code 316 == resp_custom1.status_code 317 == resp_custom2.status_code 318 ) 319 assert ( 320 valid_resp_addr in resp_firefox.text 321 and valid_resp_addr in resp_safari.text 322 and valid_resp_addr in resp_custom2.text 323 ) 324 assert ( 325 valid_resp_name in resp_firefox.text 326 and valid_resp_name in resp_safari.text 327 and valid_resp_name in resp_custom2.text 328 ) 329 assert ( 330 invalid_resp_title in resp_chrome.text and 331 invalid_resp_title in resp_custom1.text 332 ) 333 assert ( 334 invalid_resp_body in resp_chrome.text and 335 invalid_resp_body in resp_custom1.text 336 )