Skip to content

Commit

Permalink
Added identical hostnames test for AuthPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
trepel committed Jun 17, 2024
1 parent 1578c7e commit cc7e5d5
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 10 deletions.
23 changes: 13 additions & 10 deletions testsuite/policy/authorization/auth_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from functools import cached_property
from typing import Dict

import openshift_client as oc

from testsuite.utils import asdict
from testsuite.openshift import OpenShiftObject, modify
from testsuite.openshift.client import OpenShiftClient
Expand Down Expand Up @@ -75,14 +73,19 @@ def remove_all_hosts(self):
self.model.spec.hosts = []

def wait_for_ready(self):
"""Waits until authorization object reports ready status"""
with oc.timeout(90):
success, _, _ = self.self_selector().until_all(
success_func=lambda obj: len(obj.model.status.conditions) > 0
and all(x.status == "True" for x in obj.model.status.conditions)
)
assert success, f"{self.kind()} did not get ready in time"
self.refresh()
"""Waits until authorization object reports Enforced=True status"""
success = self.wait_until(
lambda obj: all(x.status == "True" for x in obj.model.status.conditions)
and any(x.type == "Enforced" for x in obj.model.status.conditions)
)
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"

@modify
def add_rule(self, when: list[Rule]):
Expand Down
Empty file.
23 changes: 23 additions & 0 deletions testsuite/tests/kuadrant/identical_hostname/conftest.py
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
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 created via fixture
- 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 = retry_client.get("/anything/get")
assert response.status_code == 403
Original file line number Diff line number Diff line change
@@ -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.authorization.auth_policy import AuthPolicy
from testsuite.utils import has_condition

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 enforced 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

0 comments on commit cc7e5d5

Please sign in to comment.