Skip to content

LCORE-261: Add RH identity header auth#814

Merged
tisnik merged 1 commit intolightspeed-core:mainfrom
major:rh-identity-auth
Nov 19, 2025
Merged

LCORE-261: Add RH identity header auth#814
tisnik merged 1 commit intolightspeed-core:mainfrom
major:rh-identity-auth

Conversation

@major
Copy link
Contributor

@major major commented Nov 19, 2025

Description

Add support for receiving a base64-encoded JSON blob passed in by a load balancer in Red Hat Insights deployments. Handle authentication for users and systems with the ability to check that certain entitlements are in place for various products.

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement

Related Tickets & Documents

  • Related Issue # LSCORE-261

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

Testing this in production requires putting the deployment behind a load balancer in the Red Hat Insights environment, but you can also test it by providing a base64-encoded JSON string in the x-rh-identity header.

Summary by CodeRabbit

  • New Features

    • Added Red Hat Identity authentication with entitlement-based access control and configuration support.
    • App now exposes RH Identity configuration option in runtime settings and dumped configuration.
  • Examples

    • Added a ready-to-use example showing single, multiple (ALL), and no-entitlement validation options.
  • Tests

    • Added comprehensive unit and integration tests covering RH Identity parsing, validation, and dependency behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

Adds Red Hat Identity authentication: a FastAPI dependency that decodes and validates x-rh-identity headers (User/System types) with optional entitlement checks, plus configuration models, example configs, unit and integration tests.

Changes

Cohort / File(s) Summary
Core RH Identity Authentication
src/authentication/rh_identity.py
New module providing RHIdentityData (parses/validates x-rh-identity JSON, entitlement checks) and RHIdentityAuthDependency (FastAPI dependency that decodes header, validates structure/entitlements, returns auth tuple)
Authentication Integration
src/authentication/__init__.py
Imports rh_identity and extends get_auth_dependency to handle AUTH_MOD_RH_IDENTITY, constructing RHIdentityAuthDependency with configured entitlements
Configuration & Constants
src/constants.py, src/models/config.py
Adds AUTH_MOD_RH_IDENTITY to constants and supported modules; adds RHIdentityConfiguration and rh_identity_config field plus accessor and validation in AuthenticationConfiguration
Example Config
examples/lightspeed-stack-rh-identity.yaml
New example YAML showing RH Identity authentication options (single entitlement, multiple ALL, or no validation)
Test Config
tests/configuration/rh-identity-config.yaml
New test YAML enabling auth module rh-identity with required_entitlements: ["rhel"]
Unit Tests
tests/unit/authentication/test_rh_identity.py, tests/unit/models/config/test_dump_configuration.py
New unit tests covering RHIdentityData and RHIdentityAuthDependency behavior, entitlement validation, error paths; added serialization expectation for rh_identity_config: None
Integration Tests
tests/integration/test_rh_identity_integration.py
Integration tests with fixtures and helpers to send base64-encoded x-rh-identity headers and verify successful User/System authentication

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant App as FastAPI App
    participant Dep as RHIdentityAuthDependency
    participant Data as RHIdentityData

    Client->>App: Request with x-rh-identity header
    App->>Dep: __call__(request)

    alt Header missing
        Dep-->>App: HTTPException(401)
        App-->>Client: 401 Unauthorized
    else
        Dep->>Dep: Base64 decode header
        alt Invalid base64
            Dep-->>App: HTTPException(400)
            App-->>Client: 400 Bad Request
        else
            Dep->>Dep: Parse JSON
            alt Invalid JSON
                Dep-->>App: HTTPException(400)
                App-->>Client: 400 Bad Request
            else
                Dep->>Data: RHIdentityData(decoded_json, required_entitlements)
                Data->>Data: _validate_structure()
                alt Structure invalid
                    Data-->>Dep: HTTPException(400)
                    Dep-->>App: 400 Bad Request
                else
                    opt required_entitlements present
                        Data->>Data: validate_entitlements()
                        alt Missing entitlements
                            Data-->>Dep: HTTPException(403)
                            Dep-->>App: 403 Forbidden
                        end
                    end
                    Dep->>Data: get_user_id(), get_username()
                    Dep-->>App: (user_id, username, skip_userid_check, NO_USER_TOKEN)
                    App-->>Client: proceed to handler (200/404)
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Key areas to focus:
    • src/authentication/rh_identity.py — structure validation, entitlement logic, error mappings (401/400/403)
    • src/models/config.py — config accessor and validation branching for RH Identity
    • Tests — ensure coverage aligns with edge cases and integration wiring in __init__ is correct

Suggested reviewers

  • tisnik
  • umago

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'LCORE-261: Add RH identity header auth' accurately describes the main change: adding Red Hat Identity header authentication support as a new feature.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@openshift-ci
Copy link

openshift-ci bot commented Nov 19, 2025

Hi @major. Thanks for your PR.

I'm waiting for a github.com member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@major
Copy link
Contributor Author

major commented Nov 19, 2025

/ok-to-test

Copy link
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

LGTM in overall, nice work

There are some tests issues, please look at CI tasks logs etc.

@openshift-ci
Copy link

openshift-ci bot commented Nov 19, 2025

@major: Cannot trigger testing until a trusted user reviews the PR and leaves an /ok-to-test message.

Details

In response to this:

/ok-to-test

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@tisnik
Copy link
Contributor

tisnik commented Nov 19, 2025

/ok-to-test

Add support for receiving a base64-encoded JSON blob passed in by a load
balancer in Red Hat Insights deployments. Handle authentication for
users and systems with the ability to check that certain entitlements
are in place for various products.

Signed-off-by: Major Hayden <major@redhat.com>
Copy link
Contributor

@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 (4)
tests/unit/models/config/test_dump_configuration.py (1)

145-152: Snapshot updates for rh_identity_config look correct; consider future-proofing expectations

Including "rh_identity_config": None in both expected authentication snapshots matches the new AuthenticationConfiguration default and guards that the field is present in dumps even when using the "noop" module. The trade-off is that any future field change in AuthenticationConfiguration will require touching these large literals; if that becomes painful, you could factor a small helper/fixture that builds the expected authentication dict (and reuse it across tests) to centralize shape changes.

Also applies to: 444-451

src/models/config.py (1)

412-417: RH Identity config model is sound; consider tightening required_entitlements validation

The new RHIdentityConfiguration plus rh_identity_config field, the extra check in check_authentication_model, and the rh_identity_configuration accessor follow the same pattern as the existing JWK support and give you a clear, validated path to the RH Identity settings. That’s a solid design.

One refinement you might want to add: validate that required_entitlements, when present, is a non-empty list of non-empty strings. Right now an explicit empty list or a list with empty/whitespace values would be accepted and likely behave the same as omitting entitlements, which can be a surprising misconfiguration for operators.

For example:

 class RHIdentityConfiguration(ConfigurationBase):
     """Red Hat Identity authentication configuration."""

-    required_entitlements: Optional[list[str]] = None
+    required_entitlements: Optional[list[str]] = None
+
+    @model_validator(mode="after")
+    def check_required_entitlements(self) -> Self:
+        """Validate RH Identity entitlement configuration."""
+        if self.required_entitlements is not None:
+            if not self.required_entitlements:
+                raise ValueError(
+                    "required_entitlements must be omitted or a non-empty list"
+                )
+            if not all(isinstance(e, str) and e.strip() for e in self.required_entitlements):
+                raise ValueError(
+                    "Each entitlement must be a non-empty string"
+                )
+        return self

This keeps the current semantics (omit the field → no entitlement check; non-empty list → enforce ALL) while failing fast on ambiguous configs.

Also applies to: 426-427, 444-450, 464-473

src/authentication/rh_identity.py (1)

177-191: virtual_path is currently unused; either wire it or drop it

RHIdentityAuthDependency.__init__ accepts and stores virtual_path, but nothing in this class uses it. That’s a potential source of confusion for callers and reviewers (it’s also not exercised in tests).

If there is no near-term plan to use it, consider removing the parameter/field for now; otherwise, add a short docstring note or TODO describing the intended future use (e.g. for downstream authz) so it’s clear why it exists.

tests/unit/authentication/test_rh_identity.py (1)

225-347: Auth dependency tests are realistic and well-structured

The TestRHIdentityAuthDependency tests nicely cover:

  • Happy paths for User and System identities.
  • Missing header, invalid base64, and invalid JSON.
  • Entitlement gating (both allowed and denied).
  • Behavior when entitlements is empty.

Using Mock(spec=Request) + a simple headers dict keeps the tests fast while still emulating FastAPI’s access pattern.

Once the small robustness tweaks in the implementation are made, these tests should still pass unchanged.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2aebf9e and 4818576.

📒 Files selected for processing (9)
  • examples/lightspeed-stack-rh-identity.yaml (1 hunks)
  • src/authentication/__init__.py (2 hunks)
  • src/authentication/rh_identity.py (1 hunks)
  • src/constants.py (1 hunks)
  • src/models/config.py (4 hunks)
  • tests/configuration/rh-identity-config.yaml (1 hunks)
  • tests/integration/test_rh_identity_integration.py (1 hunks)
  • tests/unit/authentication/test_rh_identity.py (1 hunks)
  • tests/unit/models/config/test_dump_configuration.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/configuration/rh-identity-config.yaml
  • tests/integration/test_rh_identity_integration.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/authentication/rh_identity.py (1)
src/authentication/interface.py (1)
  • AuthInterface (34-39)
tests/unit/authentication/test_rh_identity.py (1)
src/authentication/rh_identity.py (7)
  • RHIdentityAuthDependency (170-250)
  • RHIdentityData (20-167)
  • get_user_id (104-114)
  • get_username (116-126)
  • has_entitlement (128-139)
  • has_entitlements (141-150)
  • validate_entitlements (152-167)
src/authentication/__init__.py (3)
src/configuration.py (2)
  • configuration (73-77)
  • authentication_configuration (108-113)
src/models/config.py (1)
  • rh_identity_configuration (465-473)
src/authentication/rh_identity.py (1)
  • RHIdentityAuthDependency (170-250)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e_tests (azure)
  • GitHub Check: e2e_tests (ci)
🔇 Additional comments (5)
examples/lightspeed-stack-rh-identity.yaml (1)

1-23: Example config correctly reflects RH Identity wiring

The example uses module: "rh-identity" and rh_identity_config.required_entitlements in a way that matches the new AuthenticationConfiguration / RHIdentityConfiguration model and the "rh-identity" constant, and the three options are documented clearly for operators.

src/constants.py (1)

93-113: RH Identity auth module constant is wired correctly

Defining AUTH_MOD_RH_IDENTITY = "rh-identity" and adding it to SUPPORTED_AUTHENTICATION_MODULES cleanly integrates the new module into existing auth validation and matches the example configuration’s module: "rh-identity" value.

src/authentication/__init__.py (1)

7-8: RH Identity auth dependency is correctly integrated

Importing rh_identity and adding a case constants.AUTH_MOD_RH_IDENTITY branch that pulls configuration.authentication_configuration.rh_identity_configuration and passes required_entitlements into RHIdentityAuthDependency matches the new config model and keeps module-specific validation in AuthenticationConfiguration. This should behave consistently with the existing JWK and K8s branches.

Also applies to: 49-56

src/authentication/rh_identity.py (1)

192-250: Main auth flow looks solid and aligns with tests

The __call__ implementation:

  • Cleanly separates header extraction, base64 decoding, JSON parsing, and structural validation.
  • Uses appropriate status codes (401 for missing header, 400 for format issues, 403 for entitlement failures).
  • Delegates ID/username extraction and entitlement logic to RHIdentityData, keeping this dependency focused.

Given the above hardening in _validate_structure/entitlement handling, this flow looks ready to integrate with FastAPI endpoints.

tests/unit/authentication/test_rh_identity.py (1)

17-213: Good coverage of identity parsing and validation edge cases

The TestRHIdentityData suite thoroughly exercises:

  • User vs System extraction (get_user_id / get_username).
  • Entitlement presence (single and multiple services).
  • Entitlement validation behavior for various required sets.
  • Structural validation for a wide range of missing fields and an unsupported type.

This gives strong confidence that changes in RHIdentityData will be caught by tests. No issues from a correctness standpoint.

Comment on lines +46 to +52
def _validate_structure(self) -> None:
"""Validate the identity data structure.

Raises:
HTTPException: 400 if required fields are missing
"""
if (
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden structure/entitlement shape validation to avoid 500s on malformed payloads

_validate_structure and the entitlement helpers assume certain subfields are dicts. For some malformed but valid JSON payloads, this can raise TypeError instead of the intended 4xx:

  • identity present but not a mapping (e.g. {"identity": 1} or {"identity": true}) → "type" not in identity may raise at runtime.
  • Similarly, if identity["user"] or identity["system"] is present but not a dict, "user_id" not in user / "cn" not in system can raise.
  • If entitlements is present but not a dict, entitlements.get(...) in has_entitlement will raise.

That turns bad input into a 500 rather than a clean 400/403, and it bypasses your otherwise precise error messages.

A minimal hardening that preserves current behavior would be:

 def _validate_structure(self) -> None:
@@
-        identity = self.identity_data["identity"]
+        identity = self.identity_data["identity"]
+        if not isinstance(identity, dict):
+            raise HTTPException(
+                status_code=400,
+                detail="Invalid 'identity' field format",
+            )
@@
-        if identity_type == "User":
-            if "user" not in identity:
+        if identity_type == "User":
+            user = identity.get("user")
+            if not isinstance(user, dict):
                 raise HTTPException(
-                    status_code=400, detail="Missing 'user' field for User type"
+                    status_code=400,
+                    detail="Missing 'user' field for User type",
                 )
-            user = identity["user"]
@@
-        elif identity_type == "System":
-            if "system" not in identity:
+        elif identity_type == "System":
+            system = identity.get("system")
+            if not isinstance(system, dict):
                 raise HTTPException(
-                    status_code=400, detail="Missing 'system' field for System type"
+                    status_code=400,
+                    detail="Missing 'system' field for System type",
                 )
-            system = identity["system"]
@@
-        entitlements = self.identity_data.get("entitlements", {})
-        service_entitlement = entitlements.get(service, {})
+        entitlements = self.identity_data.get("entitlements") or {}
+        if not isinstance(entitlements, dict):
+            return False
+        service_entitlement = entitlements.get(service, {})

This keeps your current contract (still 400/403 for bad input) but avoids accidental 500s on weird-but-possible payloads.

Also applies to: 58-95, 128-167

@tisnik
Copy link
Contributor

tisnik commented Nov 19, 2025

/ok-to-test

@tisnik tisnik merged commit d5ecd53 into lightspeed-core:main Nov 19, 2025
21 of 23 checks passed
@major major deleted the rh-identity-auth branch November 19, 2025 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants