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)