<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="5" skipped="1" tests="41" time="253.752" timestamp="2026-06-02T02:11:22.804400+00:00" hostname="maas-group-test-xcr7r-e2e-maas-openshift-pod"><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_create_api_key" time="0.122" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_list_api_keys" time="0.187" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_revoke_api_key" time="0.116" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyAuthorization" name="test_admin_manage_other_users_keys" time="0.151" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyAuthorization" name="test_non_admin_cannot_access_other_users_keys" time="0.101" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_own_keys" time="0.269" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_other_user_forbidden" time="0.052" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_admin_can_revoke_any_user" time="0.103" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_within_expiration_limit" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_at_expiration_limit" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_exceeds_expiration_limit" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_without_expiration" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_with_short_expiration" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_api_key_model_access_success" time="0.105"><failure message="AssertionError: Expected 200, got 403: &#10;assert 403 == 200&#10; +  where 403 = &lt;Response [403]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyModelInference object at 0x7f3ddfb173d0&gt;
model_completions_url = 'https://maas.apps.70c1295a-0a72-4501-89fb-a280a599275a.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
api_key_headers = {'Authorization': 'Bearer sk-oai-1TiIDlmZtkl9J9Va6_af3OD4F1rjR4ekW7t6Bx4AgeNURglimDoCrQfZzpLjg', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_api_key_model_access_success(
        self,
        model_completions_url: str,
        api_key_headers: dict,
        inference_model_name: str,
    ):
        """Test 11: Valid API key can access model endpoint - verify 200 response.
    
        Subscription is bound on the key at mint (see conftest ``api_key`` fixture).
        """
        r = requests.post(
            model_completions_url,
            headers=api_key_headers,
            json={
                "model": inference_model_name,
                "prompt": "Hello world",
                "max_tokens": 10,
            },
            timeout=60,
            verify=TLS_VERIFY,
        )
    
&gt;       assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}"
E       AssertionError: Expected 200, got 403: 
E       assert 403 == 200
E        +  where 403 = &lt;Response [403]&gt;.status_code

test/e2e/tests/test_api_keys.py:541: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_invalid_api_key_rejected" time="0.025" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_no_auth_header_rejected" time="0.019"><failure message="AssertionError: Expected 401 for missing auth, got 403: &#10;assert 403 == 401&#10; +  where 403 = &lt;Response [403]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyModelInference object at 0x7f3ddfb17d90&gt;
model_completions_url = 'https://maas.apps.70c1295a-0a72-4501-89fb-a280a599275a.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
inference_model_name = 'facebook/opt-125m'

    def test_no_auth_header_rejected(
        self,
        model_completions_url: str,
        inference_model_name: str,
    ):
        """Test 13: Missing Authorization header should be rejected with 401."""
        no_auth_headers = {"Content-Type": "application/json"}
    
        r = requests.post(
            model_completions_url,
            headers=no_auth_headers,
            json={
                "model": inference_model_name,
                "prompt": "Test",
                "max_tokens": 5,
            },
            timeout=30,
            verify=TLS_VERIFY,
        )
    
&gt;       assert r.status_code == 401, f"Expected 401 for missing auth, got {r.status_code}: {r.text}"
E       AssertionError: Expected 401 for missing auth, got 403: 
E       assert 403 == 401
E        +  where 403 = &lt;Response [403]&gt;.status_code

test/e2e/tests/test_api_keys.py:597: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_revoked_api_key_rejected" time="2.120" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_api_key_chat_completions" time="0.022"><skipped type="pytest.skip" message="Chat completions returned 403">/workspace/source/test/e2e/tests/test_api_keys.py:693: Chat completions returned 403</skipped></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_double_revoke_returns_404" time="0.113" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_nonexistent_key_returns_404" time="0.032" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_then_create_new_key_works" time="0.142"><failure message="AssertionError: New key B should work, got 403: &#10;assert 403 == 200&#10; +  where 403 = &lt;Response [403]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyRevocationE2E object at 0x7f3ddfac8a30&gt;
api_keys_base_url = 'https://maas.apps.70c1295a-0a72-4501-89fb-a280a599275a.prod.konfluxeaas.com/maas-api/v1/api-keys'
model_completions_url = 'https://maas.apps.70c1295a-0a72-4501-89fb-a280a599275a.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
headers = {'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyZ1JnRjdOQlh6cXF1RHhETVAwS1ZVVHIyVUQzbTN4X0Rlc281M1lLcmcifQ.e...3dYR3GKGY1_yPh9OAqlkauC-h2Vio8ghDBkieOh5yjJkYychEvO-mWriAFwTV5wzAzJVOtNtGM6aQ2a4g', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_revoke_then_create_new_key_works(
        self,
        api_keys_base_url: str,
        model_completions_url: str,
        headers: dict,
        inference_model_name: str,
    ):
        """After revoking a key, a newly created key should still work for inference."""
        designated = SIMULATOR_SUBSCRIPTION
    
        # Create key A
        r_a = requests.post(
            api_keys_base_url,
            headers=headers,
            json={"name": "test-revoke-remint-a", "subscription": designated},
            timeout=30,
            verify=TLS_VERIFY,
        )
        assert r_a.status_code in (200, 201), f"Failed to create key A: {r_a.text}"
        key_a = r_a.json()["key"]
        key_a_id = r_a.json()["id"]
    
        # Revoke key A
        r_revoke = requests.delete(f"{api_keys_base_url}/{key_a_id}", headers=headers, timeout=30, verify=TLS_VERIFY)
        assert r_revoke.status_code == 200
    
        # Create key B (new key after revocation)
        r_b = requests.post(
            api_keys_base_url,
            headers=headers,
            json={"name": "test-revoke-remint-b", "subscription": designated},
            timeout=30,
            verify=TLS_VERIFY,
        )
        assert r_b.status_code in (200, 201), f"Failed to create key B: {r_b.text}"
        key_b = r_b.json()["key"]
    
        # Poll until revoked key A is rejected (revocation may take time to propagate)
        max_wait = 30
        poll_interval = 0.5
        deadline = time.monotonic() + max_wait
        while True:
            remaining = deadline - time.monotonic()
            if remaining &lt;= 0:
                break
            r_a_inf = requests.post(
                model_completions_url,
                headers={"Authorization": f"Bearer {key_a}", "Content-Type": "application/json"},
                json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
                timeout=min(remaining, 10),
                verify=TLS_VERIFY,
            )
            if r_a_inf.status_code == 403:
                break
            time.sleep(poll_interval)
        assert r_a_inf.status_code == 403, (
            f"Revoked key A should be rejected within {max_wait}s, got {r_a_inf.status_code}"
        )
    
        # Key B should work (200)
        r_b_inf = requests.post(
            model_completions_url,
            headers={"Authorization": f"Bearer {key_b}", "Content-Type": "application/json"},
            json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
            timeout=60,
            verify=TLS_VERIFY,
        )
&gt;       assert r_b_inf.status_code == 200, f"New key B should work, got {r_b_inf.status_code}: {r_b_inf.text}"
E       AssertionError: New key B should work, got 403: 
E       assert 403 == 200
E        +  where 403 = &lt;Response [403]&gt;.status_code

test/e2e/tests/test_api_keys.py:793: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_individual_revoke_multiple_keys" time="0.201" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_keys_rejected_at_gateway" time="0.121"><failure message="AssertionError: Key should work before revoke, got 403: &#10;assert 403 == 200&#10; +  where 403 = &lt;Response [403]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyRevocationE2E object at 0x7f3ddfae40a0&gt;
api_keys_base_url = 'https://maas.apps.70c1295a-0a72-4501-89fb-a280a599275a.prod.konfluxeaas.com/maas-api/v1/api-keys'
model_completions_url = 'https://maas.apps.70c1295a-0a72-4501-89fb-a280a599275a.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
headers = {'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyZ1JnRjdOQlh6cXF1RHhETVAwS1ZVVHIyVUQzbTN4X0Rlc281M1lLcmcifQ.e...3dYR3GKGY1_yPh9OAqlkauC-h2Vio8ghDBkieOh5yjJkYychEvO-mWriAFwTV5wzAzJVOtNtGM6aQ2a4g', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_revoke_keys_rejected_at_gateway(
        self,
        api_keys_base_url: str,
        model_completions_url: str,
        headers: dict,
        inference_model_name: str,
    ):
        """After individually revoking keys, they should be rejected at the gateway."""
        designated = SIMULATOR_SUBSCRIPTION
    
        # Create 3 keys, capturing plaintext and IDs
        keys = []
        for i in range(3):
            r = requests.post(
                api_keys_base_url,
                headers=headers,
                json={"name": f"test-revoke-gw-{i}", "subscription": designated},
                timeout=30,
                verify=TLS_VERIFY,
            )
            assert r.status_code in (200, 201), f"Failed to create key {i}: {r.text}"
            keys.append({"id": r.json()["id"], "key": r.json()["key"]})
    
        # Smoke-test: verify at least one key works before revocation
        r_smoke = requests.post(
            model_completions_url,
            headers={"Authorization": f"Bearer {keys[0]['key']}", "Content-Type": "application/json"},
            json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
            timeout=60,
            verify=TLS_VERIFY,
        )
&gt;       assert r_smoke.status_code == 200, (
            f"Key should work before revoke, got {r_smoke.status_code}: {r_smoke.text}"
        )
E       AssertionError: Key should work before revoke, got 403: 
E       assert 403 == 200
E        +  where 403 = &lt;Response [403]&gt;.status_code

test/e2e/tests/test_api_keys.py:851: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_cronjob_exists_and_configured" time="0.109" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_cleanup_networkpolicy_exists" time="0.129" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_create_ephemeral_key" time="0.110" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_trigger_cleanup_preserves_active_keys" time="0.443" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_active_subscription" time="9.257" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_degraded_subscription" time="19.154" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_failed_subscription" time="19.253" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_pending_subscription" time="19.271" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_reject_key_for_unreconciled_subscription" time="22.251" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionFilter" name="test_search_filters_by_subscription" time="10.191" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionFilter" name="test_search_without_subscription_returns_all" time="0.186" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSAPIWatchNamespace" name="test_subscription_in_subscription_namespace_visible_to_api" time="8.527" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSAPIWatchNamespace" name="test_subscription_in_another_namespace_not_visible_to_api" time="22.764" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSControllerWatchNamespace" name="test_authpolicy_and_subscription_in_maas_subscription_namespace" time="23.971" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSControllerWatchNamespace" name="test_authpolicy_and_subscription_in_another_namespace" time="30.240" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestModelRef" name="test_auth_policy_model_ref" time="31.541" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestModelRef" name="test_subscription_model_ref" time="31.556" /><testcase classname="test.e2e.tests.test_negative_security.TestHeaderSpoofing" name="test_injected_identity_headers_ignored" time="0.064"><failure message="AssertionError: Expected 200 (spoofed headers stripped, real identity used), got 403: &#10;assert 403 == 200&#10; +  where 403 = &lt;Response [403]&gt;.status_code">self = &lt;test_negative_security.TestHeaderSpoofing object at 0x7f3ddffe9700&gt;

    def test_injected_identity_headers_ignored(self):
        """Client injects X-MaaS-Username/Group/Key-Id — platform ignores them.
    
        Validates that Authorino strips attacker-controlled identity headers.
        The request should succeed (200) using the real key-derived identity,
        proving the spoofed headers had no effect on authorization.
        """
        api_key = _create_api_key(_get_cluster_token(), subscription=SIMULATOR_SUBSCRIPTION)
    
        spoofed_headers = {
            "X-MaaS-Username": "cluster-admin",
            "X-MaaS-Group": "system:cluster-admins,system:masters",
            "X-MaaS-Key-Id": "fake-key-id-00000",
        }
    
        r = _inference(api_key, extra_headers=spoofed_headers)
    
        # Request succeeds with the REAL identity (API key owner), not the spoofed one.
        # If spoofed headers were honored, the test user would gain cluster-admin access.
        log.info("Spoofed identity headers -&gt; %s", r.status_code)
&gt;       assert r.status_code == 200, (
            f"Expected 200 (spoofed headers stripped, real identity used), "
            f"got {r.status_code}: {r.text[:500]}"
        )
E       AssertionError: Expected 200 (spoofed headers stripped, real identity used), got 403: 
E       assert 403 == 200
E        +  where 403 = &lt;Response [403]&gt;.status_code

test/e2e/tests/test_negative_security.py:95: AssertionError</failure></testcase></testsuite></testsuites>