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          )