From ce5218c27e034c4b2d6144aae3460a9f058f6eb6 Mon Sep 17 00:00:00 2001 From: Rafal Wegrzycki Date: Tue, 7 Sep 2021 17:35:12 +0200 Subject: [PATCH] AP: fix watch-namespace for NAP resources --- internal/k8s/controller.go | 2 +- tests/suite/resources_utils.py | 5 +- .../suite/test_app_protect_watch_namespace.py | 204 ++++++++++++++++++ 3 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 tests/suite/test_app_protect_watch_namespace.py diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index b4ba04146c..09fbbdcd8a 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -244,7 +244,7 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc lbc.addPodHandler() if lbc.appProtectEnabled { - lbc.dynInformerFactory = dynamicinformer.NewDynamicSharedInformerFactory(lbc.dynClient, 0) + lbc.dynInformerFactory = dynamicinformer.NewFilteredDynamicSharedInformerFactory(lbc.dynClient, 0, lbc.namespace, nil) lbc.addAppProtectPolicyHandler(createAppProtectPolicyHandlers(lbc)) lbc.addAppProtectLogConfHandler(createAppProtectLogConfHandlers(lbc)) diff --git a/tests/suite/resources_utils.py b/tests/suite/resources_utils.py index 44fe8474f8..6bae1a0f9c 100644 --- a/tests/suite/resources_utils.py +++ b/tests/suite/resources_utils.py @@ -1082,7 +1082,10 @@ def create_ingress_with_ap_annotations( :return: """ print("Load ingress yaml and set AppProtect annotations") - policy = f"{namespace}/{policy_name}" + if "/" in policy_name: + policy = policy_name + else: + policy = f"{namespace}/{policy_name}" logconf = f"{namespace}/logconf" with open(yaml_manifest) as f: diff --git a/tests/suite/test_app_protect_watch_namespace.py b/tests/suite/test_app_protect_watch_namespace.py new file mode 100644 index 0000000000..3971e6d665 --- /dev/null +++ b/tests/suite/test_app_protect_watch_namespace.py @@ -0,0 +1,204 @@ +import requests +import pytest +import time + +from settings import TEST_DATA, DEPLOYMENTS +from suite.ap_resources_utils import ( + create_ap_logconf_from_yaml, + create_ap_policy_from_yaml, + delete_ap_policy, + delete_ap_logconf, +) +from suite.resources_utils import ( + wait_before_test, + create_example_app, + wait_until_all_pods_are_ready, + create_items_from_yaml, + delete_items_from_yaml, + delete_common_app, + delete_namespace, + ensure_connection_to_public_endpoint, + create_ingress_with_ap_annotations, + create_namespace_with_name_from_yaml, + ensure_response_from_backend, + wait_before_test, +) +from suite.yaml_utils import get_first_ingress_host_from_yaml + +# This test shows that a policy outside of the namespace test_namespace is not picked up by IC. + +timestamp = round(time.time() * 1000) +test_namespace = f"test-namespace-{str(timestamp)}" +policy_namespace = f"policy-test-namespace-{str(timestamp)}" +valid_resp_body = "Server name:" +invalid_resp_body = "The requested URL was rejected. Please consult with your administrator." +reload_times = {} + + +class BackendSetup: + """ + Encapsulate the example details. + + Attributes: + req_url (str): + ingress_host (str): + """ + + def __init__(self, req_url, req_url_2, metrics_url, ingress_host): + self.req_url = req_url + self.req_url_2 = req_url_2 + self.metrics_url = metrics_url + self.ingress_host = ingress_host + + +@pytest.fixture(scope="class") +def backend_setup(request, kube_apis, ingress_controller_endpoint) -> BackendSetup: + """ + Deploy a simple application and AppProtect manifests. + + :param request: pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: public endpoint + :param test_namespace: + :return: BackendSetup + """ + policy = "file-block" + + create_namespace_with_name_from_yaml(kube_apis.v1, test_namespace, f"{TEST_DATA}/common/ns.yaml") + print("------------------------- Deploy backend application -------------------------") + + create_example_app(kube_apis, "simple", test_namespace) + req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend1" + req_url_2 = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend2" + metrics_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, + ingress_controller_endpoint.port, + ingress_controller_endpoint.port_ssl, + ) + + print("------------------------- Deploy Secret -----------------------------") + src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" + create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + + print("------------------------- Deploy logconf -----------------------------") + src_log_yaml = f"{TEST_DATA}/appprotect/logconf.yaml" + log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace) + + print(f"------------------------- Deploy namespace: {policy_namespace} ---------------------------") + create_namespace_with_name_from_yaml(kube_apis.v1, policy_namespace, f"{TEST_DATA}/common/ns.yaml") + + print(f"------------------------- Deploy appolicy: {policy} ---------------------------") + src_pol_yaml = f"{TEST_DATA}/appprotect/{policy}.yaml" + pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, policy_namespace) + + print("------------------------- Deploy ingress -----------------------------") + ingress_host = {} + src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" + create_ingress_with_ap_annotations( + kube_apis, src_ing_yaml, test_namespace, f"{policy_namespace}/{policy}", "True", "True", "127.0.0.1:514" + ) + ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) + wait_before_test() + + def fin(): + print("Clean up:") + src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + delete_ap_policy(kube_apis.custom_objects, pol_name, policy_namespace) + delete_namespace(kube_apis.v1, policy_namespace) + delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) + delete_common_app(kube_apis, "simple", test_namespace) + src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" + delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + delete_namespace(kube_apis.v1, test_namespace) + + request.addfinalizer(fin) + + return BackendSetup(req_url, req_url_2, metrics_url, ingress_host) + +# the first case does not set "-watch-namespace" so the policy is configured on the ingress. +# This causes the traffic to be blocked + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect +@pytest.mark.parametrize( + "crd_ingress_controller_with_ap", + [ + { + "extra_args": [ + f"-enable-custom-resources", + f"-enable-app-protect", + f"-enable-prometheus-metrics" + ] + } + ], + indirect=True, +) +class TestAppProtectWatchNamespaceDisabled: + def test_responses( + self, request, kube_apis, crd_ingress_controller_with_ap, backend_setup + ): + """ + Test file_block AppProtect policy without -watch-namespace + """ + print("------------- Run test for AP policy: file-block --------------") + print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") + + ensure_response_from_backend( + backend_setup.req_url, backend_setup.ingress_host, check404=True + ) + + print("----------------------- Send request ----------------------") + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + + print(resp.text) + + assert invalid_resp_body in resp.text + assert resp.status_code == 200 + +# In this test case the "-watch-namespace" param is set so the policy in policy_namespace +# Is not configured on the ingress -> NAP uses the default policy which will not block the same request. + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect +@pytest.mark.parametrize( + "crd_ingress_controller_with_ap", + [ + { + "extra_args": [ + f"-enable-custom-resources", + f"-enable-app-protect", + f"-enable-prometheus-metrics", + f"-watch-namespace={test_namespace}" + ] + } + ], + indirect=True, +) +class TestAppProtectWatchNamespaceEnabled: + def test_responses( + self, request, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace + ): + """ + Test file-block AppProtect policy with -watch-namespace + """ + print("------------- Run test for AP policy: file-block --------------") + print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") + + ensure_response_from_backend( + backend_setup.req_url, backend_setup.ingress_host, check404=True + ) + + print("----------------------- Send request ----------------------") + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + + print(resp.text) + + assert valid_resp_body in resp.text + assert resp.status_code == 200 \ No newline at end of file