github.com/nginxinc/kubernetes-ingress@v1.12.5/tests/suite/test_app_protect_integration.py (about) 1 import requests 2 import pytest 3 import yaml 4 5 from settings import TEST_DATA, DEPLOYMENTS 6 from suite.custom_resources_utils import ( 7 create_ap_logconf_from_yaml, 8 create_ap_policy_from_yaml, 9 delete_ap_policy, 10 delete_ap_logconf, 11 ) 12 from suite.resources_utils import ( 13 wait_before_test, 14 create_example_app, 15 wait_until_all_pods_are_ready, 16 create_items_from_yaml, 17 delete_items_from_yaml, 18 delete_common_app, 19 ensure_connection_to_public_endpoint, 20 create_ingress, 21 create_ingress_with_ap_annotations, 22 ensure_response_from_backend, 23 wait_before_test, 24 get_ingress_nginx_template_conf, 25 get_first_pod_name, 26 get_file_contents, 27 get_service_endpoint, 28 ) 29 from suite.custom_resources_utils import ( 30 read_ap_custom_resource, 31 create_ap_usersig_from_yaml, 32 delete_and_create_ap_policy_from_yaml, 33 ) 34 from suite.yaml_utils import get_first_ingress_host_from_yaml, get_name_from_yaml 35 36 src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" 37 ap_policy = "dataguard-alarm" 38 ap_policy_uds = "dataguard-alarm-uds" 39 uds_crd = f"{DEPLOYMENTS}/common/crds/appprotect.f5.com_apusersigs.yaml" 40 uds_crd_resource = f"{TEST_DATA}/appprotect/ap-ic-uds.yaml" 41 valid_resp_addr = "Server address:" 42 valid_resp_name = "Server name:" 43 invalid_resp_title = "Request Rejected" 44 invalid_resp_body = "The requested URL was rejected. Please consult with your administrator." 45 46 47 class AppProtectSetup: 48 """ 49 Encapsulate the example details. 50 Attributes: 51 req_url (str): 52 """ 53 54 def __init__(self, req_url): 55 self.req_url = req_url 56 57 58 @pytest.fixture(scope="class") 59 def appprotect_setup( 60 request, kube_apis, ingress_controller_endpoint, test_namespace 61 ) -> AppProtectSetup: 62 """ 63 Deploy simple application and all the AppProtect(dataguard-alarm) resources under test in one namespace. 64 65 :param request: pytest fixture 66 :param kube_apis: client apis 67 :param ingress_controller_endpoint: public endpoint 68 :param test_namespace: 69 :return: BackendSetup 70 """ 71 print("------------------------- Deploy simple backend application -------------------------") 72 create_example_app(kube_apis, "simple", test_namespace) 73 req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend1" 74 wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) 75 ensure_connection_to_public_endpoint( 76 ingress_controller_endpoint.public_ip, 77 ingress_controller_endpoint.port, 78 ingress_controller_endpoint.port_ssl, 79 ) 80 81 print("------------------------- Deploy Secret -----------------------------") 82 src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" 83 create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) 84 85 print("------------------------- Deploy logconf -----------------------------") 86 src_log_yaml = f"{TEST_DATA}/appprotect/logconf.yaml" 87 log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace) 88 89 print(f"------------------------- Deploy dataguard-alarm appolicy ---------------------------") 90 src_pol_yaml = f"{TEST_DATA}/appprotect/{ap_policy}.yaml" 91 pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, test_namespace) 92 93 def fin(): 94 print("Clean up:") 95 delete_ap_policy(kube_apis.custom_objects, pol_name, test_namespace) 96 delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) 97 delete_common_app(kube_apis, "simple", test_namespace) 98 src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" 99 delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) 100 101 request.addfinalizer(fin) 102 103 return AppProtectSetup(req_url) 104 105 106 def assert_ap_crd_info(ap_crd_info, policy_name) -> None: 107 """ 108 Assert fields in AppProtect policy documents 109 :param ap_crd_info: CRD output from k8s API 110 """ 111 assert ap_crd_info["kind"] == "APPolicy" 112 assert ap_crd_info["metadata"]["name"] == policy_name 113 assert ap_crd_info["spec"]["policy"]["enforcementMode"] == "blocking" 114 assert ( 115 ap_crd_info["spec"]["policy"]["blocking-settings"]["violations"][0]["name"] 116 == "VIOL_DATA_GUARD" 117 ) 118 119 120 def assert_invalid_responses(response) -> None: 121 """ 122 Assert responses when policy config is blocking requests 123 :param response: Response 124 """ 125 assert invalid_resp_title in response.text 126 assert invalid_resp_body in response.text 127 assert response.status_code == 200 128 129 130 def assert_valid_responses(response) -> None: 131 """ 132 Assert responses when policy config is allowing requests 133 :param response: Response 134 """ 135 assert valid_resp_name in response.text 136 assert valid_resp_addr in response.text 137 assert response.status_code == 200 138 139 140 @pytest.mark.skip_for_nginx_oss 141 @pytest.mark.appprotect 142 @pytest.mark.parametrize( 143 "crd_ingress_controller_with_ap", 144 [{"extra_args": [f"-enable-custom-resources", f"-enable-app-protect"]}], 145 indirect=["crd_ingress_controller_with_ap"], 146 ) 147 class TestAppProtect: 148 def test_ap_nginx_config_entries( 149 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 150 ): 151 """ 152 Test to verify AppProtect annotations in nginx config 153 """ 154 conf_annotations = [ 155 f"app_protect_enable on;", 156 f"app_protect_policy_file /etc/nginx/waf/nac-policies/{test_namespace}_{ap_policy};", 157 f"app_protect_security_log_enable on;", 158 f"app_protect_security_log /etc/nginx/waf/nac-logconfs/{test_namespace}_logconf syslog:server=127.0.0.1:514;", 159 ] 160 161 create_ingress_with_ap_annotations( 162 kube_apis, src_ing_yaml, test_namespace, ap_policy, "True", "True", "127.0.0.1:514" 163 ) 164 165 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 166 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 167 168 pod_name = get_first_pod_name(kube_apis.v1, "nginx-ingress") 169 170 result_conf = get_ingress_nginx_template_conf( 171 kube_apis.v1, test_namespace, "appprotect-ingress", pod_name, "nginx-ingress" 172 ) 173 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 174 175 for _ in conf_annotations: 176 assert _ in result_conf 177 178 @pytest.mark.smoke 179 def test_ap_enable_true_policy_correct( 180 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 181 ): 182 """ 183 Test malicious script request is rejected while AppProtect is enabled in Ingress 184 """ 185 create_ingress_with_ap_annotations( 186 kube_apis, src_ing_yaml, test_namespace, ap_policy, "True", "True", "127.0.0.1:514" 187 ) 188 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 189 190 print("--------- Run test while AppProtect module is enabled with correct policy ---------") 191 192 ap_crd_info = read_ap_custom_resource( 193 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy 194 ) 195 assert_ap_crd_info(ap_crd_info, ap_policy) 196 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 197 198 print("----------------------- Send request ----------------------") 199 response = requests.get( 200 appprotect_setup.req_url + "/<script>", headers={"host": ingress_host}, verify=False 201 ) 202 print(response.text) 203 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 204 assert_invalid_responses(response) 205 206 def test_ap_enable_false_policy_correct( 207 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 208 ): 209 """ 210 Test malicious script request is working normally while AppProtect is disabled in Ingress 211 """ 212 create_ingress_with_ap_annotations( 213 kube_apis, src_ing_yaml, test_namespace, ap_policy, "False", "True", "127.0.0.1:514" 214 ) 215 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 216 217 print( 218 "--------- Run test while AppProtect module is disabled with correct policy ---------" 219 ) 220 221 ap_crd_info = read_ap_custom_resource( 222 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy 223 ) 224 assert_ap_crd_info(ap_crd_info, ap_policy) 225 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 226 227 print("----------------------- Send request ----------------------") 228 response = requests.get( 229 appprotect_setup.req_url + "/<script>", headers={"host": ingress_host}, verify=False 230 ) 231 print(response.text) 232 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 233 assert_valid_responses(response) 234 235 def test_ap_enable_true_policy_incorrect( 236 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 237 ): 238 """ 239 Test malicious script request is blocked by default policy while AppProtect is enabled with incorrect policy in ingress 240 """ 241 create_ingress_with_ap_annotations( 242 kube_apis, 243 src_ing_yaml, 244 test_namespace, 245 "invalid-policy", 246 "True", 247 "True", 248 "127.0.0.1:514", 249 ) 250 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 251 252 print( 253 "--------- Run test while AppProtect module is enabled with incorrect policy ---------" 254 ) 255 256 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 257 258 print("----------------------- Send request ----------------------") 259 response = requests.get( 260 appprotect_setup.req_url + "/<script>", headers={"host": ingress_host}, verify=False 261 ) 262 print(response.text) 263 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 264 assert_invalid_responses(response) 265 266 def test_ap_enable_false_policy_incorrect( 267 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 268 ): 269 """ 270 Test malicious script request is working normally while AppProtect is disabled in with incorrect policy in ingress 271 """ 272 create_ingress_with_ap_annotations( 273 kube_apis, 274 src_ing_yaml, 275 test_namespace, 276 "invalid-policy", 277 "False", 278 "True", 279 "127.0.0.1:514", 280 ) 281 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 282 283 print( 284 "--------- Run test while AppProtect module is disabled with incorrect policy ---------" 285 ) 286 287 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 288 289 print("----------------------- Send request ----------------------") 290 response = requests.get( 291 appprotect_setup.req_url + "/<script>", headers={"host": ingress_host}, verify=False 292 ) 293 print(response.text) 294 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 295 assert_valid_responses(response) 296 297 def test_ap_sec_logs_on( 298 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 299 ): 300 """ 301 Test corresponding log entries with correct policy (includes setting up a syslog server as defined in syslog.yaml) 302 """ 303 src_syslog_yaml = f"{TEST_DATA}/appprotect/syslog.yaml" 304 log_loc = f"/var/log/messages" 305 306 create_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) 307 308 syslog_ep = get_service_endpoint(kube_apis, "syslog-svc", test_namespace) 309 310 # items[-1] because syslog pod is last one to spin-up 311 syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name 312 313 create_ingress_with_ap_annotations( 314 kube_apis, src_ing_yaml, test_namespace, ap_policy, "True", "True", f"{syslog_ep}:514" 315 ) 316 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 317 318 print("--------- Run test while AppProtect module is enabled with correct policy ---------") 319 320 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 321 322 print("----------------------- Send invalid request ----------------------") 323 response = requests.get( 324 appprotect_setup.req_url + "/<script>", headers={"host": ingress_host}, verify=False 325 ) 326 print(response.text) 327 wait_before_test(5) 328 log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) 329 330 assert_invalid_responses(response) 331 assert ( 332 f'ASM:attack_type="Non-browser Client,Abuse of Functionality,Cross Site Scripting (XSS)"' 333 in log_contents 334 ) 335 assert f'severity="Critical"' in log_contents 336 assert f'request_status="blocked"' in log_contents 337 assert f'outcome="REJECTED"' in log_contents 338 339 print("----------------------- Send valid request ----------------------") 340 headers = { 341 "Host": ingress_host, 342 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0", 343 } 344 response = requests.get(appprotect_setup.req_url, headers=headers, verify=False) 345 print(response.text) 346 wait_before_test(10) 347 log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) 348 349 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 350 delete_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) 351 352 assert_valid_responses(response) 353 assert f'ASM:attack_type="N/A"' in log_contents 354 assert f'severity="Informational"' in log_contents 355 assert f'request_status="passed"' in log_contents 356 assert f'outcome="PASSED"' in log_contents 357 358 def test_ap_multi_sec_logs( 359 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 360 ): 361 """ 362 Test corresponding log entries with multiple log destinations (in this case, two syslog servers) 363 """ 364 src_syslog_yaml = f"{TEST_DATA}/appprotect/syslog.yaml" 365 src_syslog2_yaml = f"{TEST_DATA}/appprotect/syslog2.yaml" 366 log_loc = f"/var/log/messages" 367 368 print("Create two syslog servers") 369 create_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) 370 create_items_from_yaml(kube_apis, src_syslog2_yaml, test_namespace) 371 372 syslog_ep = get_service_endpoint(kube_apis, "syslog-svc", test_namespace) 373 syslog2_ep = get_service_endpoint(kube_apis, "syslog2-svc", test_namespace) 374 375 syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-2].metadata.name 376 syslog2_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name 377 378 with open(src_ing_yaml) as f: 379 doc = yaml.safe_load(f) 380 381 doc["metadata"]["annotations"]["appprotect.f5.com/app-protect-policy"] = ap_policy 382 doc["metadata"]["annotations"]["appprotect.f5.com/app-protect-enable"] = "True" 383 doc["metadata"]["annotations"][ 384 "appprotect.f5.com/app-protect-security-log-enable" 385 ] = "True" 386 387 # both lists need to be the same length, if one of the referenced configs is invalid/non-existent then no logconfs are applied. 388 doc["metadata"]["annotations"][ 389 "appprotect.f5.com/app-protect-security-log" 390 ] = f"{test_namespace}/logconf,{test_namespace}/logconf" 391 392 doc["metadata"]["annotations"][ 393 "appprotect.f5.com/app-protect-security-log-destination" 394 ] = f"syslog:server={syslog_ep}:514,syslog:server={syslog2_ep}:514" 395 396 create_ingress(kube_apis.extensions_v1_beta1, test_namespace, doc) 397 398 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 399 400 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 401 402 print("----------------------- Send request ----------------------") 403 response = requests.get( 404 appprotect_setup.req_url + "/<script>", headers={"host": ingress_host}, verify=False 405 ) 406 print(response.text) 407 log_contents = "" 408 log2_contents = "" 409 retry = 0 410 while ( 411 "ASM:attack_type" not in log_contents 412 and "ASM:attack_type" not in log2_contents 413 and retry <= 30 414 ): 415 log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) 416 log2_contents = get_file_contents(kube_apis.v1, log_loc, syslog2_pod, test_namespace) 417 retry += 1 418 wait_before_test(1) 419 print(f"Security log not updated, retrying... #{retry}") 420 421 assert_invalid_responses(response) 422 423 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 424 delete_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) 425 delete_items_from_yaml(kube_apis, src_syslog2_yaml, test_namespace) 426 427 # check logs in dest. #1 i.e. syslog server #1 428 assert ( 429 f'ASM:attack_type="Non-browser Client,Abuse of Functionality,Cross Site Scripting (XSS)"' 430 in log_contents 431 and f'severity="Critical"' in log_contents 432 and f'request_status="blocked"' in log_contents 433 and f'outcome="REJECTED"' in log_contents 434 ) 435 # check logs in dest. #2 i.e. syslog server #2 436 assert ( 437 f'ASM:attack_type="Non-browser Client,Abuse of Functionality,Cross Site Scripting (XSS)"' 438 in log2_contents 439 and f'severity="Critical"' in log2_contents 440 and f'request_status="blocked"' in log2_contents 441 and f'outcome="REJECTED"' in log2_contents 442 ) 443 444 def test_ap_enable_true_policy_correct_uds( 445 self, kube_apis, crd_ingress_controller_with_ap, appprotect_setup, test_namespace 446 ): 447 """ 448 Test request with UDS rule string is rejected while AppProtect with User Defined Signatures is enabled in Ingress 449 """ 450 451 usersig_name = create_ap_usersig_from_yaml( 452 kube_apis.custom_objects, uds_crd_resource, test_namespace 453 ) 454 # Apply dataguard-alarm AP policy with UDS 455 delete_and_create_ap_policy_from_yaml( 456 kube_apis.custom_objects, 457 ap_policy, 458 f"{TEST_DATA}/appprotect/{ap_policy_uds}.yaml", 459 test_namespace, 460 ) 461 wait_before_test() 462 463 create_ingress_with_ap_annotations( 464 kube_apis, src_ing_yaml, test_namespace, ap_policy, "True", "True", "127.0.0.1:514" 465 ) 466 ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) 467 468 print( 469 "--------- Run test while AppProtect module is enabled with correct policy and UDS ---------" 470 ) 471 472 ap_crd_info = read_ap_custom_resource( 473 kube_apis.custom_objects, test_namespace, "appolicies", ap_policy 474 ) 475 assert_ap_crd_info(ap_crd_info, ap_policy) 476 wait_before_test(120) 477 478 ensure_response_from_backend(appprotect_setup.req_url, ingress_host, check404=True) 479 print("----------------------- Send request ----------------------") 480 response = requests.get( 481 appprotect_setup.req_url, headers={"host": ingress_host}, verify=False, data="kic" 482 ) 483 print(response.text) 484 485 # Restore default dataguard-alarm policy 486 delete_and_create_ap_policy_from_yaml( 487 kube_apis.custom_objects, 488 ap_policy, 489 f"{TEST_DATA}/appprotect/{ap_policy}.yaml", 490 test_namespace, 491 ) 492 delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) 493 494 assert_invalid_responses(response)