Skip to content

feat: add activation instance scoping to JWT tokens for WebSocket aut…#1602

Open
mkanoor wants to merge 1 commit into
ansible:mainfrom
mkanoor:activation_based_token
Open

feat: add activation instance scoping to JWT tokens for WebSocket aut…#1602
mkanoor wants to merge 1 commit into
ansible:mainfrom
mkanoor:activation_based_token

Conversation

@mkanoor

@mkanoor mkanoor commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

WebSocket Authentication

This commit adds security enhancements to ensure JWT tokens are scoped to specific activation instances, preventing token reuse across different instances. It also implements secure token refresh with scope preservation and backward- compatible legacy token support with actionable warnings.

Key changes:

  • Add activation_instance_id to JWT token payload (supports int and UUID)
  • Validate token scope in WebSocket consumer before processing messages
  • Implement secure token refresh that preserves activation_instance_id scope
  • Support backward compatibility with legacy tokens (no forced logout)
  • Add actionable warnings for legacy tokens with activation name
  • Implement token payload caching for performance

Security improvements:

  • Tokens cannot be used for different activation instances
  • Token refresh preserves scope (prevents scope escalation vulnerability)
  • Two-layer validation: middleware (signature/expiry) + consumer (scope)
  • Automatic revocation via database existence checks
  • Comprehensive error logging for audit trails

Token Refresh Security:

  • RefreshTokenSerializer extracts activation_instance_id from refresh token
  • New access tokens preserve the scope from refresh token (trusted source)
  • Prevents privilege escalation by not accepting client-provided scope
  • Legacy refresh tokens continue to work (no scope added)

Backward Compatibility:

  • Legacy tokens without activation_instance_id are accepted with warnings
  • Warnings logged once per connection to avoid log spam
  • Warnings include activation name for easy operator action
  • No event loss, no forced disconnection

Files modified:

  • src/aap_eda/services/auth.py: Token creation and validation
  • src/aap_eda/services/activation/engine/common.py: Pass activation_instance_id
  • src/aap_eda/wsapi/consumers.py: Token scope validation with async warnings
  • src/aap_eda/api/serializers/auth.py: Extract scope from refresh tokens
  • src/aap_eda/api/views/auth.py: Preserve scope in token refresh endpoint

Tests added:

  • 9 tests for auth service (token creation, validation, scope checking)
  • 14 tests for consumer validation (scope matching, legacy token warnings)
  • 3 tests for token refresh endpoint (scope preservation, legacy handling)
  • 7 tests for RefreshTokenSerializer (scope extraction, error handling)
  • Total: 33 passing tests

All code formatted with black, sorted with isort, and linted with flake8.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • JWT access/refresh tokens now optionally include an activation-instance scope claim.
    • Refreshing a token preserves any existing activation-instance scope when issuing the new access token.
    • WebSocket requests validate that the token scope matches the message’s activation context; mismatches now fail authentication (connection closes with auth error). Legacy tokens without scope are allowed with a one-time security warning.
  • Tests

    • Added/expanded integration coverage for scoped vs legacy refresh behavior (including UUID/non-UUID forms) and WebSocket authorization/scope enforcement (including malformed headers).

@mkanoor mkanoor requested a review from a team as a code owner June 22, 2026 17:12
@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

JWT tokens now optionally carry activation_instance_id, and refresh, websocket, and activation command paths preserve or enforce that scope. Integration tests were added and updated for scoped, legacy, and malformed-token cases.

Changes

JWT Activation Instance Scoping

Layer / File(s) Summary
Token claim support
src/aap_eda/services/auth.py
create_jwt_token, jwt_access_token, jwt_refresh_token, get_jwt_token, and validate_jwt_token accept optional activation-instance scope and apply it to JWT payload generation and validation.
Refresh and activation token propagation
src/aap_eda/api/serializers/auth.py, src/aap_eda/api/views/auth.py, src/aap_eda/services/activation/engine/common.py
RefreshTokenSerializer parses activation_instance_id from refresh tokens, TokenRefreshView.post forwards it into refreshed access tokens, and activation command generation scopes websocket JWTs to the current activation instance.
WebSocket consumer auth
src/aap_eda/wsapi/consumers.py
AnsibleRulebookConsumer caches parsed JWTs, validates token type and activation scope, scopes JOB and non-JOB messages, centralizes dispatch/error logging, and closes on authentication errors.
Service and API integration tests
tests/integration/services/test_auth.py, tests/integration/api/test_auth.py, tests/integration/api/test_auth_serializers.py
Integration tests cover scoped token issuance and validation, refresh-token claim preservation, serializer extraction, legacy behavior, invalid-token failures, expiration, and user population.
WebSocket integration tests
tests/integration/wsapi/test_consumer.py, tests/integration/wsapi/test_consumer_token_validation.py
WebSocket tests build JWT-scoped communicators, refactor existing consumer scenarios to connect explicitly, and add token-validation coverage for payload caching, scope enforcement, legacy warnings, and malformed headers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: activation-instance scoping for JWTs used by WebSocket auth.
Description check ✅ Passed The description covers what changed, why, how it works, compatibility, and testing; only an issue link and explicit breaking-change section are missing.
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.
✨ 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/test_auth_serializers.py (1)

91-101: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Make the expired-token test deterministic without wall-clock sleep.

Line 100 adds time.sleep(1), which slows the suite and can still be timing-sensitive. Patch to an already-expired lifetime (e.g., -1) and remove the sleep.

Proposed change
         with patch(
             "aap_eda.services.auth.settings.JWT_REFRESH_TOKEN_LIFETIME_DAYS",
-            "0",
+            "-1",
         ):
             _, refresh_token = create_jwt_token()
-
-        import time
-
-        time.sleep(1)
🤖 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_auth_serializers.py` around lines 91 - 101, The
test for expired tokens uses time.sleep(1) to wait for expiration, which is slow
and timing-sensitive. Remove the import and time.sleep(1) call entirely, and
change the patched value for JWT_REFRESH_TOKEN_LIFETIME_DAYS from "0" to "-1" to
create a token that is already expired by definition rather than relying on
wall-clock time to pass. This makes the test deterministic and eliminates the
unnecessary delay.
🤖 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/test_auth_serializers.py`:
- Around line 91-101: The test for expired tokens uses time.sleep(1) to wait for
expiration, which is slow and timing-sensitive. Remove the import and
time.sleep(1) call entirely, and change the patched value for
JWT_REFRESH_TOKEN_LIFETIME_DAYS from "0" to "-1" to create a token that is
already expired by definition rather than relying on wall-clock time to pass.
This makes the test deterministic and eliminates the unnecessary delay.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 96d67dcf-8229-481f-a7d0-16ae7940397e

📥 Commits

Reviewing files that changed from the base of the PR and between 620a48b and 43717ed.

📒 Files selected for processing (10)
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/auth.py
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/api/test_auth.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/services/test_auth.py
  • tests/integration/wsapi/test_consumer.py
  • tests/integration/wsapi/test_consumer_token_validation.py

@codecov-commenter

codecov-commenter commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.52174% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.53%. Comparing base (ae92f55) to head (3d4f482).

Files with missing lines Patch % Lines
src/aap_eda/wsapi/consumers.py 95.60% 4 Missing ⚠️
@@            Coverage Diff             @@
##             main    #1602      +/-   ##
==========================================
+ Coverage   92.48%   92.53%   +0.05%     
==========================================
  Files         244      244              
  Lines       11244    11333      +89     
==========================================
+ Hits        10399    10487      +88     
- Misses        845      846       +1     
Flag Coverage Δ
unit-int-tests-3.11 92.53% <96.52%> (+0.05%) ⬆️
unit-int-tests-3.12 92.53% <96.52%> (+0.05%) ⬆️

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

Files with missing lines Coverage Δ
src/aap_eda/api/serializers/auth.py 100.00% <100.00%> (ø)
src/aap_eda/api/views/auth.py 100.00% <100.00%> (ø)
src/aap_eda/services/activation/engine/common.py 84.33% <100.00%> (ø)
src/aap_eda/services/auth.py 82.81% <100.00%> (+3.18%) ⬆️
src/aap_eda/wsapi/consumers.py 94.90% <95.60%> (+0.85%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mkanoor mkanoor force-pushed the activation_based_token branch 4 times, most recently from 4a345f0 to fa7a5b1 Compare June 22, 2026 20:08
@mkanoor

mkanoor commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 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: 3

🤖 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 `@src/aap_eda/wsapi/consumers.py`:
- Around line 241-243: In the InvalidTokenError exception handler, the
logger.error call is logging the entire inbound message payload via the `data`
variable, which can leak sensitive information. Replace the full `data` object
in the log message with minimal identifying information needed for debugging
purposes, such as message type or a request ID if available. Keep the error
details from the `err` variable in the log as those are essential for
troubleshooting token authentication failures.
- Around line 131-132: The auth_header assignment at line 131 uses direct
decode() on potentially malformed bytes which can raise UnicodeDecodeError, and
this exception is not caught by the InvalidTokenError handler. Fix this by
wrapping the decode call in a try-except block to catch UnicodeDecodeError, or
use the decode method's error handling parameter (such as errors='replace' or
errors='ignore') to safely handle malformed UTF-8 bytes. Ensure that any decode
failures are treated as authentication failures that follow the intended 4003
auth failure path rather than propagating as uncaught exceptions.

In `@tests/integration/wsapi/test_consumer.py`:
- Around line 167-168: The disconnect calls on ws_communicator are
inconsistently handling asyncio.CancelledError across different test methods.
Wrap all instances of `await ws_communicator.disconnect()` with a try/except
block that catches `asyncio.CancelledError` and passes silently, applying this
pattern uniformly across all test methods including those at lines 167-168,
211-212, 1425-1426, 1467-1468, 1788-1789, 1821-1822, and 1969-1970 to prevent
intermittent test failures in Django 5.x.
🪄 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: ce8dbec4-7dbe-45fc-9978-df325bec513e

📥 Commits

Reviewing files that changed from the base of the PR and between 43717ed and fa7a5b1.

📒 Files selected for processing (10)
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/auth.py
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/api/test_auth.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/services/test_auth.py
  • tests/integration/wsapi/test_consumer.py
  • tests/integration/wsapi/test_consumer_token_validation.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • tests/integration/api/test_auth_serializers.py

Comment thread src/aap_eda/wsapi/consumers.py Outdated
Comment thread src/aap_eda/wsapi/consumers.py
Comment thread tests/integration/wsapi/test_consumer.py
@mkanoor mkanoor force-pushed the activation_based_token branch 2 times, most recently from 838dacb to 8e65325 Compare June 23, 2026 00:27

@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 `@src/aap_eda/wsapi/consumers.py`:
- Around line 216-220: The scope validation logic currently only checks for
activation_id in the incoming message data, but the code at line 502 also uses
message.ansible_rulebook_id as an activation instance for job processing. Update
the validation block to handle both cases: first check if activation_id exists
in the data, and if not, check for message.ansible_rulebook_id. Pass whichever
activation identifier is present to the _validate_token_scope() method, ensuring
that scope validation is applied regardless of which field contains the
activation reference. Additionally, add a regression test case that verifies
_validate_token_scope() is called when a JOB payload contains
ansible_rulebook_id but lacks activation_id.
🪄 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: 44e1643c-fb90-464c-a26b-2495063ab3ff

📥 Commits

Reviewing files that changed from the base of the PR and between fa7a5b1 and 8e65325.

📒 Files selected for processing (10)
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/auth.py
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/api/test_auth.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/services/test_auth.py
  • tests/integration/wsapi/test_consumer.py
  • tests/integration/wsapi/test_consumer_token_validation.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/api/serializers/auth.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/wsapi/test_consumer.py

Comment thread src/aap_eda/wsapi/consumers.py Outdated
@mkanoor mkanoor force-pushed the activation_based_token branch from 8e65325 to 73f96bd Compare June 26, 2026 14:50

@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: 3

🤖 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 `@src/aap_eda/wsapi/consumers.py`:
- Around line 145-147: WebSocket authentication in the consumer currently
accepts any JWT that passes parse_jwt_token(), so refresh tokens can slip
through if the scope check matches. Update the token handling in the consumer
method that sets _token_payload to explicitly require an access token by
checking the token_type claim after parse_jwt_token() returns, and reject the
connection otherwise. Use the existing token parsing and scope validation flow
in the WebSocket consumer to enforce this before returning the payload.
- Around line 221-227: The JOB scope check currently validates against
activation_id even when the record is persisted using ansible_rulebook_id, which
can let a token pass for one ID and write to another activation instance. Update
the validation in the message handling path around _validate_token_scope so JOB
messages always scope-check the same identifier used later for persistence,
preferring ansible_rulebook_id for MessageType.JOB and falling back only when
that value is unavailable.

In `@tests/integration/wsapi/test_consumer_token_validation.py`:
- Around line 362-393: The JOB token-validation tests in the consumer suite only
assert that the socket closes, so tighten them by patching the dispatch path and
checking whether the message handler ran. In the rejection cases around
test_job_message_rejects_mismatched_ansible_rulebook_id and the related receive
tests, mock handle_jobs on the consumer and assert it is not called when auth
fails; in the precedence test, assert it is called exactly once when the
higher-priority token source wins. Keep the existing close assertions, but add
handler-call assertions so the tests verify both blocking and dispatch behavior.
🪄 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: 85f897ca-f03b-41e3-9e08-92f184a91bf0

📥 Commits

Reviewing files that changed from the base of the PR and between 8e65325 and 73f96bd.

📒 Files selected for processing (10)
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/auth.py
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/api/test_auth.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/services/test_auth.py
  • tests/integration/wsapi/test_consumer.py
  • tests/integration/wsapi/test_consumer_token_validation.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/wsapi/test_consumer.py

Comment thread src/aap_eda/wsapi/consumers.py Outdated
Comment thread tests/integration/wsapi/test_consumer_token_validation.py Outdated
@mkanoor mkanoor force-pushed the activation_based_token branch 2 times, most recently from 0fd6c35 to 559e619 Compare June 29, 2026 21:02

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/aap_eda/wsapi/consumers.py (1)

306-313: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate scope before DB-backed log tracking.

Line 306 uses untrusted activation_id before token scope validation and outside the try, so invalid scoped messages can trigger DB lookups or uncaught ObjectDoesNotExist before the intended 4003 auth path.

Proposed fix
-        await self._set_log_tracking_id(data)
-
         logger.debug(f"AnsibleRulebookConsumer received: {data}")
         msg_type = MessageType(data.get("type"))
 
         try:
             await self._validate_message_token_scope(msg_type, data)
+            await self._set_log_tracking_id(data)
             await self._dispatch_message(msg_type, data)
🤖 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 `@src/aap_eda/wsapi/consumers.py` around lines 306 - 313, The message handling
in AnsibleRulebookConsumer should validate token scope before any DB-backed log
tracking that uses activation_id. Move the _validate_message_token_scope call
ahead of _set_log_tracking_id in the receive flow, and keep both inside the
existing try path so invalid scoped messages are rejected with the intended 4003
auth handling instead of triggering uncaught ObjectDoesNotExist from
_set_log_tracking_id.
🤖 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 `@src/aap_eda/wsapi/consumers.py`:
- Around line 221-258: The current _validate_message_token_scope flow skips
ANSIBLE_EVENT because AnsibleEventMessage lacks activation_id, but
_dispatch_message still processes it and insert_event_related_data persists by
event.job_id. Update _validate_message_token_scope (and the related
_dispatch_message/insert_event_related_data path) to resolve the activation from
job_id for MessageType.ANSIBLE_EVENT and validate that scope, or explicitly
reject ANSIBLE_EVENT here if that message type should no longer be accepted.
- Around line 145-156: The WebSocket auth flow in _get_token_payload currently
caches the parsed JWT payload before validating token_type, which allows
rejected refresh-token payloads to be reused on later calls. Update
_get_token_payload in consumers.py so self._token_payload is assigned only after
the token_type check passes and the token is confirmed to be an access token;
keep the validation and caching together so only validated access-token payloads
are stored.

---

Outside diff comments:
In `@src/aap_eda/wsapi/consumers.py`:
- Around line 306-313: The message handling in AnsibleRulebookConsumer should
validate token scope before any DB-backed log tracking that uses activation_id.
Move the _validate_message_token_scope call ahead of _set_log_tracking_id in the
receive flow, and keep both inside the existing try path so invalid scoped
messages are rejected with the intended 4003 auth handling instead of triggering
uncaught ObjectDoesNotExist from _set_log_tracking_id.
🪄 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: 8629ac97-90d6-47f2-820c-2fc36c83665d

📥 Commits

Reviewing files that changed from the base of the PR and between 73f96bd and 559e619.

📒 Files selected for processing (10)
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/auth.py
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/api/test_auth.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/services/test_auth.py
  • tests/integration/wsapi/test_consumer.py
  • tests/integration/wsapi/test_consumer_token_validation.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/aap_eda/api/serializers/auth.py
  • src/aap_eda/api/views/auth.py
  • src/aap_eda/services/activation/engine/common.py
  • tests/integration/api/test_auth_serializers.py
  • tests/integration/wsapi/test_consumer.py

Comment thread src/aap_eda/wsapi/consumers.py
Comment thread src/aap_eda/wsapi/consumers.py
@mkanoor mkanoor force-pushed the activation_based_token branch from 559e619 to 798f292 Compare June 29, 2026 21:18
…hentication

This commit adds security enhancements to ensure JWT tokens are scoped to
specific activation instances, preventing token reuse across different instances.
It also implements secure token refresh with scope preservation and backward-
compatible legacy token support with actionable warnings.

Key changes:
- Add activation_instance_id to JWT token payload (supports int and UUID)
- Validate token scope in WebSocket consumer before processing messages
- Implement secure token refresh that preserves activation_instance_id scope
- Support backward compatibility with legacy tokens (no forced logout)
- Add actionable warnings for legacy tokens with activation name
- Implement token payload caching for performance

Security improvements:
- Tokens cannot be used for different activation instances
- Token refresh preserves scope (prevents scope escalation vulnerability)
- Two-layer validation: middleware (signature/expiry) + consumer (scope)
- Automatic revocation via database existence checks
- Comprehensive error logging for audit trails

Token Refresh Security:
- RefreshTokenSerializer extracts activation_instance_id from refresh token
- New access tokens preserve the scope from refresh token (trusted source)
- Prevents privilege escalation by not accepting client-provided scope
- Legacy refresh tokens continue to work (no scope added)

Backward Compatibility:
- Legacy tokens without activation_instance_id are accepted with warnings
- Warnings logged once per connection to avoid log spam
- Warnings include activation name for easy operator action
- No event loss, no forced disconnection

Security Fixes:
- Fix UnicodeDecodeError handling for malformed Authorization headers
- Prevent sensitive data leakage in error logs (only log msg_type and activation_id)
- Close token scope validation bypass for JOB messages using ansible_rulebook_id
- Add regression tests for all security fixes

Files modified:
- src/aap_eda/services/auth.py: Token creation and validation
- src/aap_eda/services/activation/engine/common.py: Pass activation_instance_id
- src/aap_eda/wsapi/consumers.py: Token scope validation with async warnings
- src/aap_eda/api/serializers/auth.py: Extract scope from refresh tokens
- src/aap_eda/api/views/auth.py: Preserve scope in token refresh endpoint

Tests added:
- 9 tests for auth service (token creation, validation, scope checking)
- 14 tests for consumer validation (scope matching, legacy token warnings)
- 4 tests for security fixes (malformed headers, log sanitization, JOB validation)
- 3 tests for token refresh endpoint (scope preservation, legacy handling)
- 7 tests for RefreshTokenSerializer (scope extraction, error handling)
- Total: 37 passing tests

All code formatted with black, sorted with isort, and linted with flake8.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@mkanoor mkanoor force-pushed the activation_based_token branch from 798f292 to 3d4f482 Compare June 29, 2026 22:03
@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.

2 participants