From e0433e6363ccd74df9a9f52cacba00c6c17f51ff Mon Sep 17 00:00:00 2001 From: Tomas Repel Date: Fri, 21 Jun 2024 12:10:05 +0200 Subject: [PATCH] Added identical hostnames test for AuthPolicy --- testsuite/policy/__init__.py | 7 ++ .../kuadrant/identical_hostnames/__init__.py | 0 .../kuadrant/identical_hostnames/conftest.py | 23 ++++++ ...dentical_hostnames_auth_on_gw_and_route.py | 58 ++++++++++++++ ...test_identical_hostnames_auth_on_routes.py | 79 +++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 testsuite/tests/kuadrant/identical_hostnames/__init__.py create mode 100644 testsuite/tests/kuadrant/identical_hostnames/conftest.py create mode 100644 testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_gw_and_route.py create mode 100644 testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_routes.py diff --git a/testsuite/policy/__init__.py b/testsuite/policy/__init__.py index 4cfb831e..f771420b 100644 --- a/testsuite/policy/__init__.py +++ b/testsuite/policy/__init__.py @@ -23,3 +23,10 @@ def wait_for_ready(self): """Wait for a Policy to be Enforced""" success = self.wait_until(has_condition("Enforced", "True")) assert success, f"{self.kind()} did not get ready in time" + + def wait_for_accepted(self): + """Waits until authorization object reports Accepted=True status""" + success = self.wait_until( + lambda obj: any(x.status == "True" and x.type == "Accepted" for x in obj.model.status.conditions) + ) + assert success, f"{self.kind()} did not get accepted in time" diff --git a/testsuite/tests/kuadrant/identical_hostnames/__init__.py b/testsuite/tests/kuadrant/identical_hostnames/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/kuadrant/identical_hostnames/conftest.py b/testsuite/tests/kuadrant/identical_hostnames/conftest.py new file mode 100644 index 00000000..0d16da8f --- /dev/null +++ b/testsuite/tests/kuadrant/identical_hostnames/conftest.py @@ -0,0 +1,23 @@ +"""Conftest for "identical hostname" tests""" + +import pytest + +from testsuite.gateway import GatewayRoute +from testsuite.gateway.gateway_api.route import HTTPRoute + + +@pytest.fixture(scope="module", autouse=True) +def route2(request, gateway, blame, hostname, backend, module_label) -> GatewayRoute: + """HTTPRoute object serving as a 2nd route declaring identical hostname but different path""" + route = HTTPRoute.create_instance(gateway.openshift, blame("route"), gateway, {"app": module_label}) + route.add_hostname(hostname.hostname) + route.add_backend(backend, "/anything/") + request.addfinalizer(route.delete) + route.commit() + return route + + +@pytest.fixture(scope="module") +def authorization_name2(blame): + """Name of the 2nd Authorization resource""" + return blame("authz2") diff --git a/testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_gw_and_route.py b/testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_gw_and_route.py new file mode 100644 index 00000000..7e3aeced --- /dev/null +++ b/testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_gw_and_route.py @@ -0,0 +1,58 @@ +""" +Tests behavior of using one HTTPRoute declaring the same hostname as parent Gateway related to AuthPolicy. +https://github.com/Kuadrant/kuadrant-operator/blob/main/doc/auth.md#limitation-multiple-network-resources-with-identical-hostnames +(second topology mentioned there) +""" + +import pytest + +from testsuite.policy.authorization.auth_policy import AuthPolicy + +pytestmark = [pytest.mark.kuadrant_only] + + +@pytest.fixture(scope="class", autouse=True) +def authorization2(request, gateway, authorization_name2, openshift, label): + """2nd Authorization object""" + auth_policy = AuthPolicy.create_instance(openshift, authorization_name2, gateway, labels={"testRun": label}) + auth_policy.authorization.add_opa_policy("rego", "allow = false") + request.addfinalizer(auth_policy.delete) + auth_policy.commit() + auth_policy.wait_for_ready() + return auth_policy + + +def test_identical_hostnames_auth_on_gw_and_route_ignored(client, authorization, hostname): + """ + Tests that Gateway-attached AuthPolicy is ignored on 'route2' if both 'route' and 'route2' declare + identical hostname and there is another AuthPolicy already successfully enforced on 'route'. + Setup: + - Two HTTPRoutes declaring identical hostnames but different paths ('/' and '/anything/') + - Empty AuthPolicy enforced on the '/' HTTPRoute + - 'deny-all' AuthPolicy (created after Empty AuthPolicy) enforced on the Gateway + Test: + - Send a request via 'route' and assert that response status code is 200 + - Send a request via 'route2' and assert that response status code is 200 + - Delete the Empty AuthPolicy + - Send a request via both routes + - Assert that both response status codes are 403 (Forbidden) + """ + + # Access via 'route' is allowed due to Empty AuthPolicy + response = client.get("/get") + assert response.status_code == 200 + + # Despite 'deny-all' Gateway AuthPolicy reporting being successfully enforced + # it is still allowed to access the resources via 'route2' + response = client.get("/anything/get") + assert response.status_code == 200 + + # Deletion of Empty AuthPolicy should make the 'deny-all' Gateway AuthPolicy effectively enforced. + # It might take some time hence the use of retry client. + authorization.delete() + with hostname.client(retry_codes={200}) as retry_client: + response = retry_client.get("/get") + assert response.status_code == 403 + + response = client.get("/anything/get") + assert response.status_code == 403 diff --git a/testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_routes.py b/testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_routes.py new file mode 100644 index 00000000..ea705a17 --- /dev/null +++ b/testsuite/tests/kuadrant/identical_hostnames/test_identical_hostnames_auth_on_routes.py @@ -0,0 +1,79 @@ +""" +Tests behavior of using two HTTPRoutes declaring the same hostname related to AuthPolicy. +https://github.com/Kuadrant/kuadrant-operator/blob/main/doc/auth.md#limitation-multiple-network-resources-with-identical-hostnames +(the first topology mentioned there) +""" + +import pytest + +from testsuite.policy import has_condition +from testsuite.policy.authorization.auth_policy import AuthPolicy + +pytestmark = [pytest.mark.kuadrant_only] + + +@pytest.fixture(scope="class") +def authorization2(request, route2, authorization_name2, openshift, label): + """2nd Authorization object""" + auth_policy = AuthPolicy.create_instance(openshift, authorization_name2, route2, labels={"testRun": label}) + auth_policy.authorization.add_opa_policy("rego", "allow = false") + request.addfinalizer(auth_policy.delete) + auth_policy.commit() + auth_policy.wait_for_accepted() + return auth_policy + + +def test_identical_hostnames_auth_on_routes_rejected(client, authorization, authorization2): + """ + Tests that 2nd AuthPolicy is rejected on 'route2' declaring identical hostname as 'route' with another + AuthPolicy already successfully enforced on 'route'. + Setup: + - Two HTTPRoutes declaring identical hostnames but different paths ('/' and '/anything/') + - Empty AuthPolicy enforced on the '/' HTTPRoute + - 'deny-all' AuthPolicy (created after Empty AuthPolicy) accepted on the '/anything/' HTTPRoute + Test: + - Assert that 'deny-all' AuthPolicy reports an error + - Send a request via 'route' and assert that response status code is 200 + - Send a request via 'route2' and assert that response status code is 200 + - Delete the Empty AuthPolicy + - Change 'deny-all' AuthPolicy to trigger its reconciliation + - Send a request via both routes + - Assert that access via 'route' is 200 (OK) + - Assert that access via 'route2 is 403 (Forbidden) + """ + assert authorization2.wait_until( + has_condition( + "Enforced", + "False", + "Unknown", + "AuthPolicy has encountered some issues: AuthScheme is not ready yet", + ), + timelimit=20, + ), ( + f"AuthPolicy did not reach expected status (Enforced False), " + f"instead it was: {authorization2.refresh().model.status.conditions}" + ) + + response = client.get("/get") + assert response.status_code == 200 + + response = client.get("/anything/get") + assert response.status_code == 200 + + # Deletion of Empty AuthPolicy should allow for 'deny-all' AuthPolicy to be enforced successfully. + authorization.delete() + + # 2nd AuthPolicy only recovers from the "AuthScheme is not ready yet" error if reconciliation is explicitly + # triggered, e.g. by changing the AuthPolicy CR content (changing AllValues to True in this particular case) + # Reported as bug https://github.com/Kuadrant/kuadrant-operator/issues/702 + authorization2.authorization.add_opa_policy("rego", "allow = false", True) + authorization2.refresh() + authorization2.wait_for_ready() + + # Access via 'route' is still allowed + response = client.get("/get") + assert response.status_code == 200 + + # Access via 'route2' is now not allowed due to 'deny-all' AuthPolicy being enforced on 'route2' + response = client.get("/anything/get") + assert response.status_code == 403