Skip to content

feat(egress): refactored credential injection test to use Vault#951

Merged
averevki merged 1 commit intoKuadrant:mainfrom
averevki:egress-vault-tests
Apr 30, 2026
Merged

feat(egress): refactored credential injection test to use Vault#951
averevki merged 1 commit intoKuadrant:mainfrom
averevki:egress-vault-tests

Conversation

@averevki
Copy link
Copy Markdown
Contributor

@averevki averevki commented Apr 24, 2026

Note

Requires Kuadrant/helm-charts-olm#86 to be merged first (Vault deployment in tools-vault namespace).

Closes #949

Description

  • Rewrites the egress credential injection test to use HashiCorp Vault with Kubernetes auth, replicating the credential-injection user guide
  • Adds hvac Python client for Vault management instead of kubectl exec into the Vault pod
  • Introduces ValueOrSelector and NamedValueOrSelector dataclasses matching Authorino's API spec for typed metadata HTTP configuration
  • Extends fetch_service_ip to support cross-namespace service discovery for Vault in tools-vault namespace

Changes

New Features

  • testsuite/kubernetes/vault.py — New Vault class using hvac library for managing policies, Kubernetes auth roles, and KV v2 secrets
  • ValueOrSelector dataclass wrapping Value | ValueFrom | CelExpression to match Authorino's ValueOrSelector spec
  • NamedValueOrSelector extending ValueOrSelector with a name field, serializing as {name: {value/selector/expression}}
  • CelPredicate added to the Rule type union for CEL-based pattern matching
  • PlainResponse.plain now accepts CelExpression in addition to ABCValue

Refactoring

  • MetadataSection.add_http() — Extended with keyword-only parameters: url_expression: str, content_type: Literal[...], body: ValueOrSelector, headers: list[NamedValueOrSelector]; endpoint and method are now optional
  • fetch_service_ip() — Added optional namespace parameter for cross-namespace LoadBalancer lookups
  • Shared fixtures (mockserver_expectation, service1_api_secret, sa_token_secret, authorization) moved from conftest.py into their respective test files to
    decouple the Vault-based and K8s-Secret-based credential injection tests

Tests

  • test_credential_injection.py — Rewritten to use Vault Kubernetes auth flow: creates a ServiceAccount, configures Vault policy/role/secret, builds AuthPolicy with
    vault_login + vault_secret metadata steps, and validates credential injection via MockServer
  • Three test cases: successful injection with valid SA token, rejection with invalid token, rejection without token
  • test_credential_injection_by_destination.py — Now self-contained with its own fixtures (previously shared from conftest)

Configuration

  • config/settings.local.yaml.tpl — Added vault.url and vault.token settings template
  • testsuite/config/__init__.py — Added DefaultValueValidator entries for vault.url (auto-fetched from tools-vault namespace) and vault.token (defaults to
    "root")
  • pyproject.toml — Added hvac to main dependencies, types-hvac to dev dependencies

Verification steps

poetry run pytest -vv -n2 testsuite/tests/singlecluster/egress/credentials_injection/

Summary by CodeRabbit

  • New Features

    • Authorization rules now accept CEL predicates for richer policy expressions
    • HTTP metadata configuration accepts URL expressions, custom headers, request bodies and content-type options
    • Vault integration for credential management with a commented local template including dev-mode defaults
  • Tests

    • Expanded credential-injection tests covering Vault-backed flows, namespace/token failure cases and metadata-based secret fetching
  • Chores

    • Added a Vault client runtime dependency and corresponding dev typing stubs

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Adds Vault integration for credential injection: config template, hvac dependency, Vault test utility, dynaconf defaults, authorization model extensions for CEL/value selectors, namespace-aware service IP lookup, and Vault-backed egress credential-injection tests (with related test refactors).

Changes

Cohort / File(s) Summary
Configuration & Dependencies
config/settings.local.yaml.tpl, pyproject.toml
Adds commented vault settings to local template; adds hvac to runtime deps and types-hvac to dev deps.
Dynaconf Defaults & Service Discovery
testsuite/config/__init__.py, testsuite/config/tools.py
Registers default validators for vault.url and vault.token (defaults to dev root); fetch_service_ip() gains optional namespace parameter to query services in a specified project.
Authorization Model & Sections
testsuite/kuadrant/policy/authorization/__init__.py, testsuite/kuadrant/policy/authorization/sections.py
Adds CelPredicate to Rule; introduces ValueOrSelector and NamedValueOrSelector; widens PlainResponse.plain to accept CelExpression; MetadataSection.add_http() accepts either endpoint or url_expression, optional content type, body (ValueOrSelector) and custom headers (NamedValueOrSelector) with validation.
Vault Test Utility
testsuite/kubernetes/vault.py
New Vault class wrapping hvac.Client with methods to create/delete policies and roles, and store/delete KV v2 secrets (mount/secret path handling).
Test changes — conftest
testsuite/tests/singlecluster/egress/credentials_injection/conftest.py
Removes Secret-based credential fixtures and constants; retains mockserver and shared infra fixtures; refactors to support Vault-backed flows.
Test changes — Vault-backed tests
testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py
Replaces Secret-based injection with Vault-backed flow: per-run Vault policy/role/secret provisioning, K8s SA token login to Vault, secret fetch and header injection; expands test cases (success, unauthorized namespace, invalid token, missing token).
Test changes — destination-based secret injection
testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection_by_destination.py
Adds service-specific Secret provisioning and metadata.http-based secret fetch that decodes stored api_key into outbound Authorization header; adds MockServer expectation validating exact bearer token.

Sequence Diagram(s)

sequenceDiagram
    participant Workload as Workload
    participant Authorino as Authorino
    participant Vault as Vault
    participant Egress as Egress_Gateway
    participant Backend as MockServer

    Workload->>Authorino: Request (includes K8s SA token)
    Authorino->>Vault: POST /v1/auth/kubernetes/login (SA token) [metadata.http p0]
    Vault-->>Authorino: Vault client token + auth info
    Authorino->>Vault: GET /v1/secret/data/egress/<ns>/<sa> (using client token) [metadata.http p1]
    Vault-->>Authorino: Secret { api_key: "..." }
    Authorino->>Authorino: Authorization check (patternMatching)
    Authorino->>Egress_Gateway: Apply response directives (inject Authorization header)
    Egress_Gateway->>Backend: Outbound request (Authorization: Bearer <api_key>)
    Backend-->>Egress_Gateway: 200 OK
    Egress_Gateway-->>Workload: Response
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

Suggested Reviewers

  • trepel
  • zkraus
  • silvi-t

Poem

🐰 I dug a tunnel to Vault tonight,
Tokens warmed my paws in moonlight.
Policies sown and secrets kept,
Egress hops while back I stepped.
Hooray — credentials snug and bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: refactoring credential injection tests to use Vault instead of Kubernetes Secrets.
Description check ✅ Passed The PR description is comprehensive, covering the rationale, changes, new features, refactoring, tests, configuration, and verification steps. All major sections from the template are addressed.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #949: creates a Vault class for policy/role/secret management, rewrites tests to use Vault Kubernetes auth flow, validates three scenarios (authorised/unauthorised/no-token), and implements the AuthPolicy with vault_login and vault_secret metadata steps.
Out of Scope Changes check ✅ Passed All changes are scoped to the credential injection test refactoring objective. Dependency additions (hvac, types-hvac), configuration templates, new dataclasses (ValueOrSelector, NamedValueOrSelector), and utility extensions (fetch_service_ip, MetadataSection.add_http) directly support the Vault-based credential injection implementation.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@averevki averevki self-assigned this Apr 24, 2026
@averevki averevki added Test case New test case Service Protection Issues for Service Protection tests labels Apr 24, 2026
@averevki averevki moved this to Ready For Review in Kuadrant Apr 24, 2026
@averevki averevki requested review from a team and maksymvavilov April 24, 2026 11:25
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
testsuite/kuadrant/policy/authorization/sections.py (1)

202-234: ⚠️ Potential issue | 🟡 Minor

Consider validating that at least one of endpoint or url_expression is provided.

With both endpoint and url_expression now optional and no mutual-exclusivity/presence check, callers can construct an http metadata entry with neither url nor urlExpression, or with both set simultaneously. Authorino will reject it server-side, but surfacing the error at construction time makes failures easier to localise.

🛡️ Suggested validation
         """Set metadata http external auth feature"""
+        if not endpoint and not url_expression:
+            raise ValueError("add_http requires either `endpoint` or `url_expression`")
+        if endpoint and url_expression:
+            raise ValueError("add_http accepts only one of `endpoint` or `url_expression`")
         http_config: dict = {"method": method, "headers": {"Accept": {"value": "application/json"}}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsuite/kuadrant/policy/authorization/sections.py` around lines 202 - 234,
In add_http, ensure callers supply exactly one of endpoint or url_expression:
add a validation at the start of the add_http method (before building
http_config) that raises a ValueError if neither endpoint nor url_expression is
provided, and optionally also raise if both are provided (to enforce mutual
exclusivity) so you don't construct an invalid "http" metadata object; reference
the add_http function and the endpoint and url_expression parameters and perform
this check before calling self.add_item.
🧹 Nitpick comments (5)
testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py (4)

115-127: Fragile manual JSON assembly in CEL; consider a readable alternative.

The double-escaped JSON literal ('"{\\"jwt\\": \\"" + ... + "\\", \\"role\\": \\"..."}}"') is hard to read and fragile — any future token whose value contains a double quote or backslash would produce invalid JSON. Since Authorino supports JSON body types via content_type="application/json" with a structured body, prefer a dict-shaped payload if the API allows it, or at minimum extract the CEL into a named helper/constant for readability.

Also, substring(7) implicitly assumes the incoming Authorization header starts with exactly Bearer (7 chars). That matches the Kubernetes identity credentials convention used here, but a brief comment would save future readers a lookup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`
around lines 115 - 127, Replace the fragile manually-escaped JSON CEL expression
passed to auth.metadata.add_http with a structured body using
ValueOrSelector/CelExpression that builds a map/object (e.g., {"jwt": <token>,
"role": <role>}) instead of concatenating JSON strings; locate the call to
auth.metadata.add_http and change the body argument from the current
CelExpression string concat to a map-shaped CEL expression (using
ValueOrSelector with CelExpression that returns a map) or move the long CEL into
a named constant for readability, and add a short inline comment next to the
substring(7) usage explaining it assumes a "Bearer " prefix on
request.headers.authorization to make the intent explicit.

20-20: Consider adding an issue marker linking to #949.

The PR closes issue #949; adding pytest.mark.issue("https://github.com/Kuadrant/testsuite/issues/949") (or the Kuadrant repo URL) to pytestmark helps with traceability in Report Portal.

As per coding guidelines: "Common markers include: @pytest.mark.authorino, ... @pytest.mark.issue('https://github.com/...'), ...".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`
at line 20, Add the issue marker to the existing pytestmark declaration so the
test is traceable to issue `#949`; update the pytestmark list (the pytestmark
variable in this file) to include
pytest.mark.issue("https://github.com/Kuadrant/testsuite/issues/949") alongside
pytest.mark.kuadrant_only and pytest.mark.egress_gateway.

154-170: Consider also covering the "authenticated SA but no Vault role binding" case.

The linked issue explicitly lists a 403 scenario for a workload whose ServiceAccount token is valid at the K8s TokenReview stage but has no Vault role binding. The current three tests cover valid/invalid/missing SA tokens (all rejected at Kubernetes identity stage for the negative paths), but none exercise the Vault-side authorization failure. A fourth test using a SA that is not bound to the Vault role would close that gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`
around lines 154 - 170, Add a new test that exercises the Vault-side
authorization failure: create a test function (e.g.,
test_egress_authenticated_no_vault_binding_returns_403) that sends a request
using a ServiceAccount token that is valid for Kubernetes TokenReview but
intentionally has no Vault role binding (use or add a fixture like
sa_token_unbound_to_vault or construct such a token), call
client.get(mockserver_expectation, headers={"Authorization": f"Bearer
{sa_token_unbound_to_vault}"}), and assert the response.status_code == 403 and
that the backend Authorization header was not overwritten; mirror assertions and
structure from test_egress_credential_injection_via_vault to keep consistency.

22-23: S105 is a false positive here.

K8S_TOKEN_AUDIENCE holds an audience URL, not a credential. If Ruff noise becomes an issue, suppress with # noqa: S105 or rename; otherwise safe to ignore.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`
around lines 22 - 23, S105 is a false positive for K8S_TOKEN_AUDIENCE because it
stores an audience URL, not a secret; fix by either appending a Ruff suppression
or renaming the identifier: add "# noqa: S105" to the K8S_TOKEN_AUDIENCE
definition or rename the constant to a clearly non-secret name such as
K8S_TOKEN_AUDIENCE_URL (update all references) so the linter no longer flags it.
testsuite/kubernetes/vault.py (1)

28-28: Consider making ttl configurable.

The ttl="1h" is hardcoded inside the wrapper, which prevents callers from overriding it for longer-running tests or negative scenarios. Exposing it as a keyword argument (with the current value as default) would keep the call sites concise while preserving flexibility.

♻️ Suggested change
-    def create_role(self, name: str, sa_names: list[str], sa_namespaces: list[str], policies: list[str]):
+    def create_role(
+        self, name: str, sa_names: list[str], sa_namespaces: list[str], policies: list[str], ttl: str = "1h"
+    ):
         """Create a Kubernetes auth role binding service accounts to policies"""
         self._client.auth.kubernetes.create_role(
             name,
             bound_service_account_names=sa_names,
             bound_service_account_namespaces=sa_namespaces,
             policies=policies,
-            ttl="1h",
+            ttl=ttl,
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsuite/kubernetes/vault.py` at line 28, The hardcoded ttl="1h" in
testsuite/kubernetes/vault.py prevents callers from overriding token lifetime;
add a ttl keyword argument to the enclosing function (default="1h") and replace
the literal ttl="1h" with the parameter so callers can pass a custom ttl; update
any internal calls/tests to keep behavior unchanged by default but allow
overriding.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@testsuite/kubernetes/vault.py`:
- Around line 21-29: The method create_role in testsuite/kubernetes/vault.py
calls a non-existent helper self._client.auth.kubernetes.createq_role; change
that call to the correct hvac method name create_role so the function body uses
self._client.auth.kubernetes.create_role(...). Keep the existing argument names
(bound_service_account_names, bound_service_account_namespaces, policies, ttl)
and ensure the method name in the call matches the enclosing method create_role.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`:
- Around line 115-140: The test fixture fails because of a typo: replace the
incorrect symbol createq_role with the correct Vault.create_role call in
testsuite/kubernetes/vault.py (fix the function name where the vault fixture
invokes createq_role), ensure the module imports or class Vault exposes
create_role and update any callers to use create_role so the vault fixture and
downstream authorization fixture can initialize properly.

---

Outside diff comments:
In `@testsuite/kuadrant/policy/authorization/sections.py`:
- Around line 202-234: In add_http, ensure callers supply exactly one of
endpoint or url_expression: add a validation at the start of the add_http method
(before building http_config) that raises a ValueError if neither endpoint nor
url_expression is provided, and optionally also raise if both are provided (to
enforce mutual exclusivity) so you don't construct an invalid "http" metadata
object; reference the add_http function and the endpoint and url_expression
parameters and perform this check before calling self.add_item.

---

Nitpick comments:
In `@testsuite/kubernetes/vault.py`:
- Line 28: The hardcoded ttl="1h" in testsuite/kubernetes/vault.py prevents
callers from overriding token lifetime; add a ttl keyword argument to the
enclosing function (default="1h") and replace the literal ttl="1h" with the
parameter so callers can pass a custom ttl; update any internal calls/tests to
keep behavior unchanged by default but allow overriding.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`:
- Around line 115-127: Replace the fragile manually-escaped JSON CEL expression
passed to auth.metadata.add_http with a structured body using
ValueOrSelector/CelExpression that builds a map/object (e.g., {"jwt": <token>,
"role": <role>}) instead of concatenating JSON strings; locate the call to
auth.metadata.add_http and change the body argument from the current
CelExpression string concat to a map-shaped CEL expression (using
ValueOrSelector with CelExpression that returns a map) or move the long CEL into
a named constant for readability, and add a short inline comment next to the
substring(7) usage explaining it assumes a "Bearer " prefix on
request.headers.authorization to make the intent explicit.
- Line 20: Add the issue marker to the existing pytestmark declaration so the
test is traceable to issue `#949`; update the pytestmark list (the pytestmark
variable in this file) to include
pytest.mark.issue("https://github.com/Kuadrant/testsuite/issues/949") alongside
pytest.mark.kuadrant_only and pytest.mark.egress_gateway.
- Around line 154-170: Add a new test that exercises the Vault-side
authorization failure: create a test function (e.g.,
test_egress_authenticated_no_vault_binding_returns_403) that sends a request
using a ServiceAccount token that is valid for Kubernetes TokenReview but
intentionally has no Vault role binding (use or add a fixture like
sa_token_unbound_to_vault or construct such a token), call
client.get(mockserver_expectation, headers={"Authorization": f"Bearer
{sa_token_unbound_to_vault}"}), and assert the response.status_code == 403 and
that the backend Authorization header was not overwritten; mirror assertions and
structure from test_egress_credential_injection_via_vault to keep consistency.
- Around line 22-23: S105 is a false positive for K8S_TOKEN_AUDIENCE because it
stores an audience URL, not a secret; fix by either appending a Ruff suppression
or renaming the identifier: add "# noqa: S105" to the K8S_TOKEN_AUDIENCE
definition or rename the constant to a clearly non-secret name such as
K8S_TOKEN_AUDIENCE_URL (update all references) so the linter no longer flags it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e89ad610-0254-4e07-936f-67f5e647a4bc

📥 Commits

Reviewing files that changed from the base of the PR and between cd427e1 and efb9dc6.

📒 Files selected for processing (10)
  • config/settings.local.yaml.tpl
  • pyproject.toml
  • testsuite/config/__init__.py
  • testsuite/config/tools.py
  • testsuite/kuadrant/policy/authorization/__init__.py
  • testsuite/kuadrant/policy/authorization/sections.py
  • testsuite/kubernetes/vault.py
  • testsuite/tests/singlecluster/egress/credentials_injection/conftest.py
  • testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py
  • testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection_by_destination.py

Comment thread testsuite/kubernetes/vault.py
@averevki averevki force-pushed the egress-vault-tests branch from efb9dc6 to af684de Compare April 24, 2026 11:36
Copy link
Copy Markdown

@maksymvavilov maksymvavilov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work — this is a solid PR. The test faithfully replicates the credential-injection user guide, the Vault wrapper is clean, and the API type extensions (ValueOrSelector, NamedValueOrSelector, CelPredicate in Rule) are well-structured.

What's good:

  • AuthPolicy structure (kubernetesTokenReview → vault_login → vault_secret → vault_credential_check → response header injection) exactly matches the user guide. I traced through the CEL body escaping and it produces correct JSON.
  • Three test scenarios (valid SA token → 200, invalid → 401, missing → 401) with a strong end-to-end assertion on the injected header value.
  • Proper test isolation with blame(), per-test Vault resources, correct LIFO cleanup order, and graceful skip when Vault isn't configured.
  • The by_destination test is now self-contained, which makes sense since it uses a different credential injection approach (K8s Secrets).
  • add_http() changes are backward-compatible — existing callers all pass endpoint and method explicitly.

Left a few minor nits inline — nothing blocking.

Comment thread testsuite/kuadrant/policy/authorization/sections.py
@averevki averevki force-pushed the egress-vault-tests branch from af684de to a277d4d Compare April 27, 2026 11:10
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
testsuite/kuadrant/policy/authorization/sections.py (1)

218-219: Optional: make the XOR check more readable.

not bool(endpoint) ^ bool(url_expression) works (XOR inverted), but bool(endpoint) == bool(url_expression) reads more directly as “both missing or both provided → invalid”.

♻️ Proposed change
-        if not bool(endpoint) ^ bool(url_expression):
+        if bool(endpoint) == bool(url_expression):
             raise ValueError("Exactly one of 'endpoint' or 'url_expression' must be provided")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsuite/kuadrant/policy/authorization/sections.py` around lines 218 - 219,
The current conditional using inverted XOR (not bool(endpoint) ^
bool(url_expression)) is hard to read; replace it with a clearer equivalence
check so it's explicit when both are present or both are missing — e.g., use if
bool(endpoint) == bool(url_expression): raise ValueError("Exactly one of
'endpoint' or 'url_expression' must be provided") — referencing the same
variables endpoint and url_expression and the existing ValueError to locate the
check.
testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py (2)

130-142: Consider extracting the JSON-via-CEL body into a helper for readability.

Constructing JSON inside a Python f-string that contains a CEL string literal with multiple levels of escaping is correct but quite hard to verify by inspection (and to amend later). A small helper or a dedent/multiline raw string assembled from named pieces would make the resulting CEL output ({"jwt": "<token>", "role": "<role>"}) easier to read without adding runtime cost. The existing inline comment helps; this just goes one step further.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`
around lines 130 - 142, The CEL JSON body construction in the call to
auth.metadata.add_http (the ValueOrSelector(CelExpression(...)) argument) is
hard to read due to nested escaping; extract it into a named helper variable or
small function (e.g., build_vault_login_body or vault_login_cel_body) that
returns the CelExpression/str assembled from clear pieces (jwt expression and
vault_role) and then pass that variable to ValueOrSelector(CelExpression(...));
reference the existing symbols auth.metadata.add_http, ValueOrSelector,
CelExpression, vault.url and vault_role when implementing the helper so the
final add_http call simply uses the helper variable for the body, improving
readability without changing behavior.

20-23: Optional: add @pytest.mark.issue to track issue #949.

The PR closes issue #949 and replicates a published user-guide flow; adding @pytest.mark.issue(...) makes the traceability explicit in test output / Report Portal. Per coding guidelines, @pytest.mark.issue('https://github.com/...') is one of the documented common markers.

♻️ Proposed change
-pytestmark = [pytest.mark.kuadrant_only, pytest.mark.egress_gateway]
+pytestmark = [
+    pytest.mark.kuadrant_only,
+    pytest.mark.egress_gateway,
+    pytest.mark.issue("https://github.com/Kuadrant/testsuite/issues/949"),
+]

As per coding guidelines: "Common markers include: ... @pytest.mark.issue('https://github.com/...')".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`
around lines 20 - 23, Add the pytest issue marker for traceability by including
pytest.mark.issue pointing to the issue URL in the existing pytestmark list;
update the top-level pytestmark variable (the pytestmark list defined alongside
VAULT_API_KEY and K8S_TOKEN_AUDIENCE in test_credential_injection.py) to include
pytest.mark.issue('https://github.com/your-repo/your-org/issues/949') so the
test run and Report Portal show the linked issue.
testsuite/kubernetes/vault.py (2)

21-29: Optional: make the role TTL configurable.

ttl="1h" is hardcoded. It's fine for the current tests (each module-scoped fixture creates and destroys the role within a run), but exposing it as a parameter (with a "1h" default) makes the helper reusable for future tests with different lifetimes without touching this class.

♻️ Proposed refactor
-    def create_role(self, name: str, sa_names: list[str], sa_namespaces: list[str], policies: list[str]):
+    def create_role(
+        self,
+        name: str,
+        sa_names: list[str],
+        sa_namespaces: list[str],
+        policies: list[str],
+        ttl: str = "1h",
+    ):
         """Create a Kubernetes auth role binding service accounts to policies"""
         self._client.auth.kubernetes.create_role(
             name,
             bound_service_account_names=sa_names,
             bound_service_account_namespaces=sa_namespaces,
             policies=policies,
-            ttl="1h",
+            ttl=ttl,
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsuite/kubernetes/vault.py` around lines 21 - 29, The create_role method
currently hardcodes ttl="1h"; make the TTL configurable by adding a ttl
parameter to create_role (e.g., def create_role(self, name: str, sa_names:
list[str], sa_namespaces: list[str], policies: list[str], ttl: str = "1h")) and
pass that ttl variable into the self._client.auth.kubernetes.create_role call
instead of the literal "1h"; update any callers/tests that invoke create_role if
they need different lifetimes or rely on the new signature.

35-43: Minor: brittle path parsing if a single-segment path is ever passed.

path.partition("/") on a string without a / returns ("foo", "", ""), so secret_path becomes "" and create_or_update_secret/delete_metadata_and_all_versions receive an empty path. Current callers always pass secret/egress/<ns>/<sa>, so this is purely defensive — consider asserting the path contains a / (or extracting an explicit mount argument) to fail fast on misuse.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsuite/kubernetes/vault.py` around lines 35 - 43, Both store_secret and
delete_secret use path.partition("/") which yields an empty secret_path when the
input has no "/", making Vault calls with an empty path; update these methods
(store_secret and delete_secret) to validate the input path contains a "/"
(e.g., if "/" not in path: raise ValueError("path must be of form
'<mount>/<secret_path>'")) before splitting, or add an explicit mount parameter
and assert it's provided, so callers fail fast on misuse and the Vault client
never receives an empty secret_path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@testsuite/kuadrant/policy/authorization/sections.py`:
- Around line 218-219: The current conditional using inverted XOR (not
bool(endpoint) ^ bool(url_expression)) is hard to read; replace it with a
clearer equivalence check so it's explicit when both are present or both are
missing — e.g., use if bool(endpoint) == bool(url_expression): raise
ValueError("Exactly one of 'endpoint' or 'url_expression' must be provided") —
referencing the same variables endpoint and url_expression and the existing
ValueError to locate the check.

In `@testsuite/kubernetes/vault.py`:
- Around line 21-29: The create_role method currently hardcodes ttl="1h"; make
the TTL configurable by adding a ttl parameter to create_role (e.g., def
create_role(self, name: str, sa_names: list[str], sa_namespaces: list[str],
policies: list[str], ttl: str = "1h")) and pass that ttl variable into the
self._client.auth.kubernetes.create_role call instead of the literal "1h";
update any callers/tests that invoke create_role if they need different
lifetimes or rely on the new signature.
- Around line 35-43: Both store_secret and delete_secret use path.partition("/")
which yields an empty secret_path when the input has no "/", making Vault calls
with an empty path; update these methods (store_secret and delete_secret) to
validate the input path contains a "/" (e.g., if "/" not in path: raise
ValueError("path must be of form '<mount>/<secret_path>'")) before splitting, or
add an explicit mount parameter and assert it's provided, so callers fail fast
on misuse and the Vault client never receives an empty secret_path.

In
`@testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py`:
- Around line 130-142: The CEL JSON body construction in the call to
auth.metadata.add_http (the ValueOrSelector(CelExpression(...)) argument) is
hard to read due to nested escaping; extract it into a named helper variable or
small function (e.g., build_vault_login_body or vault_login_cel_body) that
returns the CelExpression/str assembled from clear pieces (jwt expression and
vault_role) and then pass that variable to ValueOrSelector(CelExpression(...));
reference the existing symbols auth.metadata.add_http, ValueOrSelector,
CelExpression, vault.url and vault_role when implementing the helper so the
final add_http call simply uses the helper variable for the body, improving
readability without changing behavior.
- Around line 20-23: Add the pytest issue marker for traceability by including
pytest.mark.issue pointing to the issue URL in the existing pytestmark list;
update the top-level pytestmark variable (the pytestmark list defined alongside
VAULT_API_KEY and K8S_TOKEN_AUDIENCE in test_credential_injection.py) to include
pytest.mark.issue('https://github.com/your-repo/your-org/issues/949') so the
test run and Report Portal show the linked issue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cada7a3-0f60-4d92-a578-c8155f79b026

📥 Commits

Reviewing files that changed from the base of the PR and between efb9dc6 and a277d4d.

📒 Files selected for processing (10)
  • config/settings.local.yaml.tpl
  • pyproject.toml
  • testsuite/config/__init__.py
  • testsuite/config/tools.py
  • testsuite/kuadrant/policy/authorization/__init__.py
  • testsuite/kuadrant/policy/authorization/sections.py
  • testsuite/kubernetes/vault.py
  • testsuite/tests/singlecluster/egress/credentials_injection/conftest.py
  • testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py
  • testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection_by_destination.py
✅ Files skipped from review due to trivial changes (2)
  • config/settings.local.yaml.tpl
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • testsuite/config/init.py

@averevki averevki moved this from Ready For Review to In Review in Kuadrant Apr 28, 2026
Signed-off-by: averevki <sandyverevkin@gmail.com>
@averevki averevki force-pushed the egress-vault-tests branch from a277d4d to dd261bb Compare April 28, 2026 16:19
@averevki
Copy link
Copy Markdown
Contributor Author

rebase^

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
testsuite/kuadrant/policy/authorization/sections.py (1)

206-214: Consider explicit T | None type hints for optional parameters.

The static analysis tool (Ruff RUF013) correctly identifies that PEP 484 prohibits implicit Optional. Parameters with = None defaults should have explicit type annotations.

♻️ Proposed fix for type hints
     def add_http(
         self,
         name,
-        endpoint=None,
+        endpoint: str | None = None,
         method: Literal["GET", "POST"] = "GET",
-        credentials: Credentials = None,
-        shared_secret_ref: dict[str, str] = None,
+        credentials: Credentials | None = None,
+        shared_secret_ref: dict[str, str] | None = None,
         *,
-        url_expression: str = None,
-        content_type: Literal["application/x-www-form-urlencoded", "application/json"] = None,
-        body: ValueOrSelector = None,
-        headers: list[NamedValueOrSelector] = None,
+        url_expression: str | None = None,
+        content_type: Literal["application/x-www-form-urlencoded", "application/json"] | None = None,
+        body: ValueOrSelector | None = None,
+        headers: list[NamedValueOrSelector] | None = None,
         **common_features,
     ):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsuite/kuadrant/policy/authorization/sections.py` around lines 206 - 214,
The function/method signature uses default None for several parameters without
explicit optional types; update the annotations to use explicit unions (T |
None) for each optional parameter: annotate endpoint as str | None, credentials
as Credentials | None, shared_secret_ref as dict[str, str] | None,
url_expression as str | None, content_type as
Literal["application/x-www-form-urlencoded", "application/json"] | None, body as
ValueOrSelector | None, and headers as list[NamedValueOrSelector] | None (leave
method as Literal["GET","POST"]). Ensure the signature in the function/class
(the parameter list shown) is updated accordingly to satisfy RUF013.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@testsuite/config/tools.py`:
- Line 55: Both fetch_service and fetch_service_ip declare parameters (e.g.,
namespace: str, name: str, port: int) with default None which makes their
annotated types incorrect; update the signatures to use Optional types (from
typing import Optional) for any parameter that can be None (e.g., namespace:
Optional[str] = None, name: Optional[str] = None, port: Optional[int] = None) or
give them proper non-None defaults—adjust the annotations in fetch_service and
fetch_service_ip accordingly and import Optional if needed so type hints match
the None defaults.

---

Nitpick comments:
In `@testsuite/kuadrant/policy/authorization/sections.py`:
- Around line 206-214: The function/method signature uses default None for
several parameters without explicit optional types; update the annotations to
use explicit unions (T | None) for each optional parameter: annotate endpoint as
str | None, credentials as Credentials | None, shared_secret_ref as dict[str,
str] | None, url_expression as str | None, content_type as
Literal["application/x-www-form-urlencoded", "application/json"] | None, body as
ValueOrSelector | None, and headers as list[NamedValueOrSelector] | None (leave
method as Literal["GET","POST"]). Ensure the signature in the function/class
(the parameter list shown) is updated accordingly to satisfy RUF013.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee1ea897-175a-4dc0-8ae0-67b7c1a5b388

📥 Commits

Reviewing files that changed from the base of the PR and between a277d4d and dd261bb.

📒 Files selected for processing (10)
  • config/settings.local.yaml.tpl
  • pyproject.toml
  • testsuite/config/__init__.py
  • testsuite/config/tools.py
  • testsuite/kuadrant/policy/authorization/__init__.py
  • testsuite/kuadrant/policy/authorization/sections.py
  • testsuite/kubernetes/vault.py
  • testsuite/tests/singlecluster/egress/credentials_injection/conftest.py
  • testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection.py
  • testsuite/tests/singlecluster/egress/credentials_injection/test_credential_injection_by_destination.py
✅ Files skipped from review due to trivial changes (2)
  • config/settings.local.yaml.tpl
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (3)
  • testsuite/config/init.py
  • testsuite/kubernetes/vault.py
  • testsuite/kuadrant/policy/authorization/init.py

Comment thread testsuite/config/tools.py
Copy link
Copy Markdown
Contributor

@silvi-t silvi-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran the full credential injection test suite against a cluster with Vault deployed from the helm-charts-olm. All 5 tests passed. LGTM.

@averevki averevki merged commit 4b51eec into Kuadrant:main Apr 30, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in Kuadrant Apr 30, 2026
@averevki averevki deleted the egress-vault-tests branch April 30, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Service Protection Issues for Service Protection tests Test case New test case

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Egress] Credential Injection with Vault

3 participants