github.com/nginxinc/kubernetes-ingress@v1.12.5/tests/suite/test_app_protect_waf_policies.py (about) 1 import requests, logging 2 import pytest, json 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 create_ap_waf_policy_from_yaml, 11 ) 12 from suite.resources_utils import ( 13 wait_before_test, 14 create_items_from_yaml, 15 wait_before_test, 16 get_file_contents, 17 get_service_endpoint, 18 ) 19 from suite.custom_resources_utils import ( 20 read_ap_custom_resource, 21 create_crd_from_yaml, 22 delete_crd, 23 create_ap_usersig_from_yaml, 24 delete_ap_usersig, 25 delete_and_create_ap_policy_from_yaml, 26 delete_virtual_server, 27 create_virtual_server_from_yaml, 28 patch_virtual_server_from_yaml, 29 patch_v_s_route_from_yaml, 30 create_v_s_route_from_yaml, 31 delete_v_s_route, 32 create_policy_from_yaml, 33 delete_policy, 34 read_policy, 35 ) 36 from suite.yaml_utils import get_first_ingress_host_from_yaml, get_name_from_yaml 37 38 ap_pol_name = "" 39 log_name = "" 40 std_vs_src = f"{TEST_DATA}/ap-waf/standard/virtual-server.yaml" 41 waf_spec_vs_src = f"{TEST_DATA}/ap-waf/virtual-server-waf-spec.yaml" 42 waf_route_vs_src = f"{TEST_DATA}/ap-waf/virtual-server-waf-route.yaml" 43 waf_subroute_vsr_src = f"{TEST_DATA}/ap-waf/virtual-server-route-waf-subroute.yaml" 44 waf_pol_default_src = f"{TEST_DATA}/ap-waf/policies/waf-default.yaml" 45 waf_pol_dataguard_src = f"{TEST_DATA}/ap-waf/policies/waf-dataguard.yaml" 46 ap_policy_uds = "dataguard-alarm-uds" 47 uds_crd_resource = f"{TEST_DATA}/ap-waf/ap-ic-uds.yaml" 48 valid_resp_addr = "Server address:" 49 valid_resp_name = "Server name:" 50 invalid_resp_title = "Request Rejected" 51 invalid_resp_body = "The requested URL was rejected. Please consult with your administrator." 52 53 54 @pytest.fixture(scope="class") 55 def appprotect_setup(request, kube_apis, test_namespace) -> None: 56 """ 57 Deploy simple application and all the AppProtect(dataguard-alarm) resources under test in one namespace. 58 59 :param request: pytest fixture 60 :param kube_apis: client apis 61 :param ingress_controller_endpoint: public endpoint 62 :param test_namespace: 63 """ 64 65 print("------------------------- Deploy logconf -----------------------------") 66 src_log_yaml = f"{TEST_DATA}/ap-waf/logconf.yaml" 67 global log_name 68 log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace) 69 70 print("------------------------- Create UserSig CRD resource-----------------------------") 71 usersig_name = create_ap_usersig_from_yaml( 72 kube_apis.custom_objects, uds_crd_resource, test_namespace 73 ) 74 75 print(f"------------------------- Deploy dataguard-alarm appolicy ---------------------------") 76 src_pol_yaml = f"{TEST_DATA}/ap-waf/{ap_policy_uds}.yaml" 77 global ap_pol_name 78 ap_pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, test_namespace) 79 80 def fin(): 81 print("Clean up:") 82 delete_ap_policy(kube_apis.custom_objects, ap_pol_name, test_namespace) 83 delete_ap_usersig(kube_apis.custom_objects, usersig_name, test_namespace) 84 delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) 85 86 request.addfinalizer(fin) 87 88 89 def assert_ap_crd_info(ap_crd_info, policy_name) -> None: 90 """ 91 Assert fields in AppProtect policy documents 92 :param ap_crd_info: CRD output from k8s API 93 :param policy_name: 94 """ 95 assert ap_crd_info["kind"] == "APPolicy" 96 assert ap_crd_info["metadata"]["name"] == policy_name 97 assert ap_crd_info["spec"]["policy"]["enforcementMode"] == "blocking" 98 assert ( 99 ap_crd_info["spec"]["policy"]["blocking-settings"]["violations"][0]["name"] 100 == "VIOL_DATA_GUARD" 101 ) 102 103 104 def assert_invalid_responses(response) -> None: 105 """ 106 Assert responses when policy config is blocking requests 107 :param response: Response 108 """ 109 assert invalid_resp_title in response.text 110 assert invalid_resp_body in response.text 111 assert response.status_code == 200 112 113 114 def assert_valid_responses(response) -> None: 115 """ 116 Assert responses when policy config is allowing requests 117 :param response: Response 118 """ 119 assert valid_resp_name in response.text 120 assert valid_resp_addr in response.text 121 assert response.status_code == 200 122 123 124 @pytest.mark.skip_for_nginx_oss 125 @pytest.mark.appprotect 126 @pytest.mark.parametrize( 127 "crd_ingress_controller_with_ap, virtual_server_setup", 128 [ 129 ( 130 { 131 "type": "complete", 132 "extra_args": [ 133 f"-enable-custom-resources", 134 f"-enable-leader-election=false", 135 f"-enable-app-protect", 136 f"-enable-preview-policies", 137 ], 138 }, 139 {"example": "ap-waf", "app_type": "simple",}, 140 ) 141 ], 142 indirect=True, 143 ) 144 class TestAppProtectWAFPolicyVS: 145 def restore_default_vs(self, kube_apis, virtual_server_setup) -> None: 146 """ 147 Restore VirtualServer without policy spec 148 """ 149 delete_virtual_server( 150 kube_apis.custom_objects, virtual_server_setup.vs_name, virtual_server_setup.namespace 151 ) 152 create_virtual_server_from_yaml( 153 kube_apis.custom_objects, std_vs_src, virtual_server_setup.namespace 154 ) 155 wait_before_test() 156 157 @pytest.mark.smoke 158 @pytest.mark.parametrize( 159 "vs_src, waf", 160 [ 161 (waf_spec_vs_src, waf_pol_default_src), 162 (waf_spec_vs_src, waf_pol_dataguard_src), 163 (waf_route_vs_src, waf_pol_default_src), 164 (waf_route_vs_src, waf_pol_dataguard_src), 165 ], 166 ) 167 def test_ap_waf_policy_block( 168 self, 169 kube_apis, 170 crd_ingress_controller_with_ap, 171 virtual_server_setup, 172 appprotect_setup, 173 test_namespace, 174 vs_src, 175 waf, 176 ): 177 """ 178 Test waf policy when enabled with default and dataguard-alarm AP Policies 179 """ 180 print(f"Create waf policy") 181 if waf == waf_pol_dataguard_src: 182 create_ap_waf_policy_from_yaml( 183 kube_apis.custom_objects, 184 waf, 185 test_namespace, 186 test_namespace, 187 True, 188 False, 189 ap_pol_name, 190 log_name, 191 "syslog:server=127.0.0.1:514", 192 ) 193 elif waf == waf_pol_default_src: 194 pol_name = create_policy_from_yaml(kube_apis.custom_objects, waf, test_namespace) 195 else: 196 pytest.fail(f"Invalid argument") 197 198 wait_before_test() 199 print(f"Patch vs with policy: {vs_src}") 200 patch_virtual_server_from_yaml( 201 kube_apis.custom_objects, 202 virtual_server_setup.vs_name, 203 vs_src, 204 virtual_server_setup.namespace, 205 ) 206 wait_before_test() 207 ap_crd_info = read_ap_custom_resource( 208 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy_uds 209 ) 210 assert_ap_crd_info(ap_crd_info, ap_policy_uds) 211 wait_before_test(120) 212 213 print( 214 "----------------------- Send request with embedded malicious script----------------------" 215 ) 216 response1 = requests.get( 217 virtual_server_setup.backend_1_url + "</script>", 218 headers={"host": virtual_server_setup.vs_host}, 219 ) 220 print(response1.text) 221 222 print( 223 "----------------------- Send request with blocked keyword in UDS----------------------" 224 ) 225 response2 = requests.get( 226 virtual_server_setup.backend_1_url, 227 headers={"host": virtual_server_setup.vs_host}, 228 data="kic", 229 ) 230 print(response2.text) 231 232 delete_policy(kube_apis.custom_objects, "waf-policy", test_namespace) 233 self.restore_default_vs(kube_apis, virtual_server_setup) 234 assert_invalid_responses(response1) 235 if waf == waf_pol_dataguard_src: 236 assert_invalid_responses(response2) 237 elif waf == waf_pol_default_src: 238 assert_valid_responses(response2) 239 else: 240 pytest.fail(f"Invalid arguments") 241 242 @pytest.mark.parametrize( 243 "vs_src, waf", 244 [(waf_spec_vs_src, waf_pol_dataguard_src), (waf_route_vs_src, waf_pol_dataguard_src),], 245 ) 246 def test_ap_waf_policy_allow( 247 self, 248 kube_apis, 249 crd_ingress_controller_with_ap, 250 virtual_server_setup, 251 appprotect_setup, 252 test_namespace, 253 vs_src, 254 waf, 255 ): 256 """ 257 Test waf policy when disabled 258 """ 259 print(f"Create waf policy") 260 create_ap_waf_policy_from_yaml( 261 kube_apis.custom_objects, 262 waf, 263 test_namespace, 264 test_namespace, 265 False, 266 False, 267 ap_pol_name, 268 log_name, 269 "syslog:server=127.0.0.1:514", 270 ) 271 wait_before_test() 272 print(f"Patch vs with policy: {vs_src}") 273 patch_virtual_server_from_yaml( 274 kube_apis.custom_objects, 275 virtual_server_setup.vs_name, 276 vs_src, 277 virtual_server_setup.namespace, 278 ) 279 wait_before_test() 280 ap_crd_info = read_ap_custom_resource( 281 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy_uds 282 ) 283 assert_ap_crd_info(ap_crd_info, ap_policy_uds) 284 wait_before_test(120) 285 286 print( 287 "----------------------- Send request with embedded malicious script----------------------" 288 ) 289 response1 = requests.get( 290 virtual_server_setup.backend_1_url + "</script>", 291 headers={"host": virtual_server_setup.vs_host}, 292 ) 293 print(response1.text) 294 295 print( 296 "----------------------- Send request with blocked keyword in UDS----------------------" 297 ) 298 response2 = requests.get( 299 virtual_server_setup.backend_1_url, 300 headers={"host": virtual_server_setup.vs_host}, 301 data="kic", 302 ) 303 print(response2.text) 304 305 delete_policy(kube_apis.custom_objects, "waf-policy", test_namespace) 306 self.restore_default_vs(kube_apis, virtual_server_setup) 307 assert_valid_responses(response1) 308 assert_valid_responses(response2) 309 310 def test_ap_waf_policy_logs( 311 self, 312 kube_apis, 313 crd_ingress_controller_with_ap, 314 virtual_server_setup, 315 appprotect_setup, 316 test_namespace, 317 ): 318 """ 319 Test waf policy logs 320 """ 321 src_syslog_yaml = f"{TEST_DATA}/ap-waf/syslog.yaml" 322 log_loc = f"/var/log/messages" 323 create_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) 324 syslog_ep = get_service_endpoint(kube_apis, "syslog-svc", test_namespace) 325 syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name 326 print(f"Create waf policy") 327 create_ap_waf_policy_from_yaml( 328 kube_apis.custom_objects, 329 waf_pol_dataguard_src, 330 test_namespace, 331 test_namespace, 332 True, 333 True, 334 ap_pol_name, 335 log_name, 336 f"syslog:server={syslog_ep}:514", 337 ) 338 wait_before_test() 339 print(f"Patch vs with policy: {waf_spec_vs_src}") 340 patch_virtual_server_from_yaml( 341 kube_apis.custom_objects, 342 virtual_server_setup.vs_name, 343 waf_spec_vs_src, 344 virtual_server_setup.namespace, 345 ) 346 wait_before_test() 347 ap_crd_info = read_ap_custom_resource( 348 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy_uds 349 ) 350 assert_ap_crd_info(ap_crd_info, ap_policy_uds) 351 wait_before_test(120) 352 353 print( 354 "----------------------- Send request with embedded malicious script----------------------" 355 ) 356 response = requests.get( 357 virtual_server_setup.backend_1_url + "</script>", 358 headers={"host": virtual_server_setup.vs_host}, 359 ) 360 print(response.text) 361 wait_before_test(5) 362 log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) 363 364 delete_policy(kube_apis.custom_objects, "waf-policy", test_namespace) 365 self.restore_default_vs(kube_apis, virtual_server_setup) 366 367 assert_invalid_responses(response) 368 assert ( 369 f'ASM:attack_type="Non-browser Client,Abuse of Functionality,Cross Site Scripting (XSS)"' 370 in log_contents 371 ) 372 assert f'severity="Critical"' in log_contents 373 assert f'request_status="blocked"' in log_contents 374 assert f'outcome="REJECTED"' in log_contents 375 376 377 @pytest.mark.skip_for_nginx_oss 378 @pytest.mark.appprotect 379 @pytest.mark.parametrize( 380 "crd_ingress_controller_with_ap, v_s_route_setup", 381 [ 382 ( 383 { 384 "type": "complete", 385 "extra_args": [ 386 f"-enable-custom-resources", 387 f"-enable-leader-election=false", 388 f"-enable-app-protect", 389 f"-enable-preview-policies", 390 ], 391 }, 392 {"example": "virtual-server-route"}, 393 ) 394 ], 395 indirect=True, 396 ) 397 class TestAppProtectWAFPolicyVSR: 398 def restore_default_vsr(self, kube_apis, v_s_route_setup) -> None: 399 """ 400 Function to revert vsr deployments to standard state 401 """ 402 patch_src_m = f"{TEST_DATA}/virtual-server-route/route-multiple.yaml" 403 patch_v_s_route_from_yaml( 404 kube_apis.custom_objects, 405 v_s_route_setup.route_m.name, 406 patch_src_m, 407 v_s_route_setup.route_m.namespace, 408 ) 409 wait_before_test() 410 411 @pytest.mark.parametrize( 412 "ap_enable", 413 [ 414 True, 415 # False 416 ], 417 ) 418 def test_ap_waf_policy_block( 419 self, 420 kube_apis, 421 crd_ingress_controller_with_ap, 422 v_s_route_setup, 423 appprotect_setup, 424 test_namespace, 425 ap_enable, 426 ): 427 """ 428 Test if WAF policy is working with VSR deployments 429 """ 430 req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}" 431 432 print(f"Create waf policy") 433 create_ap_waf_policy_from_yaml( 434 kube_apis.custom_objects, 435 waf_pol_dataguard_src, 436 v_s_route_setup.route_m.namespace, 437 test_namespace, 438 ap_enable, 439 ap_enable, 440 ap_pol_name, 441 log_name, 442 "syslog:server=127.0.0.1:514", 443 ) 444 wait_before_test() 445 print(f"Patch vsr with policy: {waf_subroute_vsr_src}") 446 patch_v_s_route_from_yaml( 447 kube_apis.custom_objects, 448 v_s_route_setup.route_m.name, 449 waf_subroute_vsr_src, 450 v_s_route_setup.route_m.namespace, 451 ) 452 wait_before_test() 453 ap_crd_info = read_ap_custom_resource( 454 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy_uds 455 ) 456 assert_ap_crd_info(ap_crd_info, ap_policy_uds) 457 wait_before_test(120) 458 response = requests.get( 459 f"{req_url}{v_s_route_setup.route_m.paths[0]}+'</script>'", 460 headers={"host": v_s_route_setup.vs_host}, 461 ) 462 print(response.text) 463 delete_policy(kube_apis.custom_objects, "waf-policy", v_s_route_setup.route_m.namespace) 464 self.restore_default_vsr(kube_apis, v_s_route_setup) 465 if ap_enable == True: 466 assert_invalid_responses(response) 467 elif ap_enable == False: 468 assert_valid_responses(response) 469 else: 470 pytest.fail(f"Invalid arguments")