Skip to content

feat: add X-Trusted-Proxy header validation for event streams#1604

Open
mkanoor wants to merge 2 commits into
ansible:mainfrom
mkanoor:x_trusted_proxy
Open

feat: add X-Trusted-Proxy header validation for event streams#1604
mkanoor wants to merge 2 commits into
ansible:mainfrom
mkanoor:x_trusted_proxy

Conversation

@mkanoor

@mkanoor mkanoor commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Add cryptographic validation of X-Trusted-Proxy header to ensure external event stream requests originate from trusted Gateway/Envoy Proxy. This prevents spoofing of forwarded headers and strengthens request authentication.

Key Changes

  • Add EVENT_STREAM_REQUIRE_TRUSTED_PROXY setting (default: True, secure by default)
  • Implement _validate_trusted_proxy_header() method using django-ansible-base
  • Validate header signature and timestamp (1000ms window, configurable via trusted_header_timeout)
  • Add comprehensive test suite (6 tests) for validation scenarios:
    • Valid trusted proxy header
    • Missing header
    • Invalid header value
    • Expired header (> 1000ms)
    • Wrong signature
    • Malformed header
  • Add autouse fixture to disable validation for non-proxy tests
  • Configure docker-compose files for local development (default: False)

Security Improvements

  • Change mTLS authentication error to generic "Authentication failed"
  • Log detailed Subject mismatch info internally without leaking in response
  • Prevent information disclosure about authentication methods and credentials

Testing

  • ✅ 70 event stream integration tests passing
  • ✅ 20 mTLS authentication unit tests passing
  • ✅ All tests pass black/isort/flake8 checks
  • ✅ All pre-commit hooks passing

Local Development

Developers can disable validation for local testing:

export EDA_EVENT_STREAM_REQUIRE_TRUSTED_PROXY=False

Or use docker-compose which sets this to False by default.

Production deployments use the default value of True (validation enabled).

Implementation Details

The X-Trusted-Proxy header format is: {timestamp_ns}-{hex_signature}

  • Signed with RSA private key (same key used for JWT)
  • Validated against public key from ANSIBLE_BASE_JWT_KEY
  • Valid only within 1000ms (configurable)
  • Proves request is from trusted proxy, enabling X-Forwarded-For/X-Envoy-External-Address to be trusted for client IP resolution

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added configurable trusted-proxy enforcement for external event stream POST requests via EVENT_STREAM_REQUIRE_TRUSTED_PROXY (on by default), returning Invalid request when the header is missing/invalid.
  • Bug Fixes
    • mTLS authentication now returns a generic "Authentication failed" message instead of exposing subject-mismatch details.
  • Tests
    • Added integration tests covering success, missing-header behavior, and multiple invalid/expired header cases; updated mTLS unit test expectations; added freezegun for time-based checks.
  • Chores
    • Updated Docker Compose environment defaults for local/stage usage.

@mkanoor mkanoor requested a review from a team as a code owner June 22, 2026 22:07
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds trusted-proxy validation for external event stream POSTs, introduces a new default setting and Docker Compose environment values, changes mTLS failure messages to a generic string, and updates unit and integration tests.

Changes

Trusted proxy and authentication changes

Layer / File(s) Summary
Setting and mTLS message
src/aap_eda/settings/defaults.py, src/aap_eda/api/event_stream_authentication.py, tests/unit/test_mtls_authentication.py
Adds EVENT_STREAM_REQUIRE_TRUSTED_PROXY = True. MTLSAuthentication.authenticate now raises AuthenticationFailed("Authentication failed") on subject-validation mismatch, and unit tests assert the generic message.
Trusted proxy validation in POST
src/aap_eda/api/views/external_event_stream.py
Imports validate_x_trusted_proxy_header, adds _validate_trusted_proxy_header(request), and calls it from post() before secret resolution and authentication.
Integration tests and runtime defaults
tests/integration/api/conftest.py, tests/integration/api/test_event_stream_trusted_proxy.py, tools/docker/docker-compose-dev.yaml, tools/docker/docker-compose-mac.yml, tools/docker/docker-compose-mac-pg-mtls.yml, tools/docker/docker-compose-stage.yaml, pyproject.toml
Adds an autouse test override for EVENT_STREAM_REQUIRE_TRUSTED_PROXY, a new integration test module covering valid and failing header cases, EDA_EVENT_STREAM_REQUIRE_TRUSTED_PROXY defaults in shared Docker Compose environment anchors, and the freezegun test dependency.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding X-Trusted-Proxy validation for event streams.
Description check ✅ Passed The description covers what changed, why, how it works, testing, dependencies, and local development details.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/integration/api/conftest.py (1)

21-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use exact module matching for fixture bypass

Line 21 uses substring matching on request.node.nodeid, which is brittle and can accidentally bypass the override for unrelated tests. Match the test file path explicitly.

Suggested change
-    if "test_event_stream_trusted_proxy" in request.node.nodeid:
+    test_file = request.node.nodeid.split("::", 1)[0]
+    if test_file.endswith("test_event_stream_trusted_proxy.py"):
         yield
         return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/api/conftest.py` around lines 21 - 23, The fixture bypass
at lines 21-23 uses substring matching with the `in` operator on
`request.node.nodeid`, which is too broad and can accidentally match unrelated
tests. Replace the substring matching logic with an explicit check against the
full test file path or module name to ensure only the intended test
(test_event_stream_trusted_proxy) bypasses the fixture override. Use exact
matching on the nodeid by comparing against the complete test path rather than
checking if the test name appears anywhere in the nodeid string.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/integration/api/conftest.py`:
- Around line 21-23: The fixture bypass at lines 21-23 uses substring matching
with the `in` operator on `request.node.nodeid`, which is too broad and can
accidentally match unrelated tests. Replace the substring matching logic with an
explicit check against the full test file path or module name to ensure only the
intended test (test_event_stream_trusted_proxy) bypasses the fixture override.
Use exact matching on the nodeid by comparing against the complete test path
rather than checking if the test name appears anywhere in the nodeid string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 2fd1b541-1a43-4437-9828-26750137e2b7

📥 Commits

Reviewing files that changed from the base of the PR and between bfad51e and b17b7d5.

📒 Files selected for processing (8)
  • src/aap_eda/api/event_stream_authentication.py
  • src/aap_eda/api/views/external_event_stream.py
  • src/aap_eda/settings/defaults.py
  • tests/integration/api/conftest.py
  • tests/integration/api/test_event_stream_trusted_proxy.py
  • tests/unit/test_mtls_authentication.py
  • tools/docker/docker-compose-mac-pg-mtls.yml
  • tools/docker/docker-compose-mac.yml

@mkanoor mkanoor force-pushed the x_trusted_proxy branch 3 times, most recently from 4e628a2 to 3211aaf Compare June 22, 2026 22:32
@codecov-commenter

codecov-commenter commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.49%. Comparing base (d83bd9b) to head (890cba5).

@@            Coverage Diff             @@
##             main    #1604      +/-   ##
==========================================
+ Coverage   92.48%   92.49%   +0.01%     
==========================================
  Files         244      244              
  Lines       11248    11264      +16     
==========================================
+ Hits        10403    10419      +16     
  Misses        845      845              
Flag Coverage Δ
unit-int-tests-3.11 92.49% <100.00%> (+0.01%) ⬆️
unit-int-tests-3.12 92.49% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/aap_eda/api/event_stream_authentication.py 92.25% <100.00%> (-0.05%) ⬇️
src/aap_eda/api/views/external_event_stream.py 90.79% <100.00%> (+1.00%) ⬆️
src/aap_eda/settings/defaults.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mkanoor

mkanoor commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/docker/docker-compose-stage.yaml`:
- Line 38: The stage Compose default for EDA_EVENT_STREAM_REQUIRE_TRUSTED_PROXY
is disabling trusted-proxy validation by default, which conflicts with the
secure default used by EVENT_STREAM_REQUIRE_TRUSTED_PROXY in the settings and
the X-Trusted-Proxy check in external_event_stream. Update the default in
docker-compose-stage.yaml to keep enforcement enabled unless explicitly
overridden, so the stage stack preserves the anti-spoofing behavior while still
allowing local development to opt out.
🪄 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: Enterprise

Run ID: ee2ab950-836f-47cb-8bbe-048d9534d8c5

📥 Commits

Reviewing files that changed from the base of the PR and between b17b7d5 and e8fb9ec.

📒 Files selected for processing (10)
  • src/aap_eda/api/event_stream_authentication.py
  • src/aap_eda/api/views/external_event_stream.py
  • src/aap_eda/settings/defaults.py
  • tests/integration/api/conftest.py
  • tests/integration/api/test_event_stream_trusted_proxy.py
  • tests/unit/test_mtls_authentication.py
  • tools/docker/docker-compose-dev.yaml
  • tools/docker/docker-compose-mac-pg-mtls.yml
  • tools/docker/docker-compose-mac.yml
  • tools/docker/docker-compose-stage.yaml
✅ Files skipped from review due to trivial changes (1)
  • tools/docker/docker-compose-mac-pg-mtls.yml
🚧 Files skipped from review as they are similar to previous changes (7)
  • tests/integration/api/conftest.py
  • src/aap_eda/settings/defaults.py
  • src/aap_eda/api/event_stream_authentication.py
  • tools/docker/docker-compose-mac.yml
  • src/aap_eda/api/views/external_event_stream.py
  • tests/unit/test_mtls_authentication.py
  • tests/integration/api/test_event_stream_trusted_proxy.py

Comment thread tools/docker/docker-compose-stage.yaml Outdated
@wfealdel

Copy link
Copy Markdown
Contributor

/run-atf-tests

@wfealdel wfealdel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clean PR overall — well-structured with good secure-by-default design. See inline comments

Comment thread src/aap_eda/api/views/external_event_stream.py
Comment thread tests/integration/api/test_event_stream_trusted_proxy.py
# Default: True (secure by default - requires Gateway/Envoy Proxy)
# Set to False for local development without proxy:
# export EDA_EVENT_STREAM_REQUIRE_TRUSTED_PROXY=False
EVENT_STREAM_REQUIRE_TRUSTED_PROXY: bool = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: Are the following documented elsewhere?

  1. The upstream trusted_header_timeout (default 1000ms) — in cross-region deployments this window could cause legitimate request rejections. Deployers would benefit from knowing the tuning knob exists.
  2. Key rotation procedures for ANSIBLE_BASE_JWT_KEY — during rotation there's a window where Gateway and EDA may have mismatched keys, rejecting all event stream requests.

These seem out of scope for this PR, just want to confirm they're covered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@wfealdel This is definitely an issue but it might have to be in a different PR, how the ANSIBLE_BASE_JWT_KEY is rotated or they might go with different headers for establishing trust between services.

@aap-pde-ci-bot

Copy link
Copy Markdown

✅ Test Results - PASSED

Summary

Metric Count
Total Tests 65
✅ Passed 49
❌ Failed 0
⚠️ Errors 0
⏭️ Skipped 16
⏱️ Duration 251.35s

Pass Rate: 75.4%

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
tests/integration/api/test_event_stream_trusted_proxy.py (1)

66-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared event-stream setup into a fixture/helper.

The same credential + event stream + payload setup is duplicated in every test, which makes future contract updates error-prone. Please centralize this in a fixture/helper and keep each test focused on the header variant only.
As per path instructions, this is a maintainability-focused issue (“Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.”).

Also applies to: 118-140, 167-190, 215-238, 264-287, 330-353, 384-407

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/api/test_event_stream_trusted_proxy.py` around lines 66 -
89, Centralize the repeated event-stream setup used across the trusted proxy
tests by moving the shared credential, event stream, and payload creation logic
into a fixture or helper, rather than duplicating the same
create_event_stream_credential, create_event_stream, get_default_test_org, and
payload preparation steps in each test. Keep the individual test cases focused
only on the header-specific behavior they validate, and have them consume the
shared setup object/data from the new fixture/helper so future contract changes
only need to be updated in one place.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/integration/api/test_event_stream_trusted_proxy.py`:
- Line 295: The expiry check in the trusted proxy integration test relies on a
real wall-clock delay via time.sleep, which makes the suite slow and flaky.
Update the test around the expiry validation logic in
test_event_stream_trusted_proxy to use a deterministic mechanism instead, such
as overriding the timeout/expiry source or mocking time progression, so the
assertion can run immediately and reliably without sleeping.

---

Nitpick comments:
In `@tests/integration/api/test_event_stream_trusted_proxy.py`:
- Around line 66-89: Centralize the repeated event-stream setup used across the
trusted proxy tests by moving the shared credential, event stream, and payload
creation logic into a fixture or helper, rather than duplicating the same
create_event_stream_credential, create_event_stream, get_default_test_org, and
payload preparation steps in each test. Keep the individual test cases focused
only on the header-specific behavior they validate, and have them consume the
shared setup object/data from the new fixture/helper so future contract changes
only need to be updated in one place.
🪄 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: Enterprise

Run ID: 3b61678d-1e2f-4dc0-953e-2ed3f4e1bb08

📥 Commits

Reviewing files that changed from the base of the PR and between e8fb9ec and 9489d48.

📒 Files selected for processing (10)
  • src/aap_eda/api/event_stream_authentication.py
  • src/aap_eda/api/views/external_event_stream.py
  • src/aap_eda/settings/defaults.py
  • tests/integration/api/conftest.py
  • tests/integration/api/test_event_stream_trusted_proxy.py
  • tests/unit/test_mtls_authentication.py
  • tools/docker/docker-compose-dev.yaml
  • tools/docker/docker-compose-mac-pg-mtls.yml
  • tools/docker/docker-compose-mac.yml
  • tools/docker/docker-compose-stage.yaml
🚧 Files skipped from review as they are similar to previous changes (9)
  • tools/docker/docker-compose-dev.yaml
  • src/aap_eda/api/event_stream_authentication.py
  • tests/integration/api/conftest.py
  • tests/unit/test_mtls_authentication.py
  • tools/docker/docker-compose-stage.yaml
  • tools/docker/docker-compose-mac-pg-mtls.yml
  • tools/docker/docker-compose-mac.yml
  • src/aap_eda/settings/defaults.py
  • src/aap_eda/api/views/external_event_stream.py

Comment thread tests/integration/api/test_event_stream_trusted_proxy.py Outdated
@mkanoor mkanoor force-pushed the x_trusted_proxy branch 2 times, most recently from 4b66a12 to d808720 Compare June 25, 2026 01:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pyproject.toml (1)

101-101: 📐 Maintainability & Code Quality | 🔵 Trivial

Refine test dependency version constraint for reproducibility.

At Line 101, freezegun = "*" removes version constraints from the dependency manifest. Although poetry.lock currently pins the version to 1.5.5, the manifest's wildcard resolution relies on the lockfile remaining in sync. If poetry.lock is regenerated, the setup may arbitrarily upgrade to an incompatible upstream version, risking CI instability. Update to a specific version or compatible range (e.g., freezegun = "^1.5.5") to ensure deterministic resolution across all environments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` at line 101, The test dependency declaration for freezegun is
currently unconstrained, which can lead to non-deterministic installs if the
lockfile changes. Update the dependency entry in the manifest to a fixed version
or a compatible range such as the one already pinned by the lockfile, and keep
it aligned with poetry.lock so resolution stays reproducible across
environments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pyproject.toml`:
- Line 101: The test dependency declaration for freezegun is currently
unconstrained, which can lead to non-deterministic installs if the lockfile
changes. Update the dependency entry in the manifest to a fixed version or a
compatible range such as the one already pinned by the lockfile, and keep it
aligned with poetry.lock so resolution stays reproducible across environments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 9a2f8842-b1f0-4875-97e6-18ba9778ec68

📥 Commits

Reviewing files that changed from the base of the PR and between 9489d48 and d808720.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • pyproject.toml
  • src/aap_eda/api/event_stream_authentication.py
  • src/aap_eda/api/views/external_event_stream.py
  • src/aap_eda/settings/defaults.py
  • tests/integration/api/conftest.py
  • tests/integration/api/test_event_stream_trusted_proxy.py
  • tests/unit/test_mtls_authentication.py
  • tools/docker/docker-compose-dev.yaml
  • tools/docker/docker-compose-mac-pg-mtls.yml
  • tools/docker/docker-compose-mac.yml
  • tools/docker/docker-compose-stage.yaml
✅ Files skipped from review due to trivial changes (2)
  • tools/docker/docker-compose-mac.yml
  • tools/docker/docker-compose-mac-pg-mtls.yml
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/unit/test_mtls_authentication.py
  • tools/docker/docker-compose-dev.yaml
  • tests/integration/api/conftest.py
  • src/aap_eda/api/event_stream_authentication.py
  • tests/integration/api/test_event_stream_trusted_proxy.py
  • src/aap_eda/settings/defaults.py
  • tools/docker/docker-compose-stage.yaml
  • src/aap_eda/api/views/external_event_stream.py

Add cryptographic validation of X-Trusted-Proxy header to ensure external
event stream requests originate from trusted Gateway/Envoy Proxy. This
prevents spoofing of forwarded headers and strengthens request authentication.

Key Changes:
- Add EVENT_STREAM_REQUIRE_TRUSTED_PROXY setting (default: True, secure by default)
- Implement _validate_trusted_proxy_header() method using django-ansible-base
- Validate header signature and timestamp (1000ms window)
- Add comprehensive test suite (6 tests) for validation scenarios
- Add autouse fixture to disable validation for non-proxy tests
- Configure docker-compose files for local development (default: False)

Security Improvements:
- Change mTLS authentication error to generic "Authentication failed"
- Log detailed Subject mismatch info internally without leaking in response
- Prevent information disclosure about authentication methods and credentials

Testing:
- 70 event stream integration tests passing
- 20 mTLS authentication unit tests passing
- All tests pass black/isort/flake8 checks

Local Development:
- Set EDA_EVENT_STREAM_REQUIRE_TRUSTED_PROXY=False for direct testing
- Production deployments use default True (validation enabled)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants