fix(security): add API key rotation and optional TTL expiry#1738
Open
anshul23102 wants to merge 1 commit into
Open
fix(security): add API key rotation and optional TTL expiry#1738anshul23102 wants to merge 1 commit into
anshul23102 wants to merge 1 commit into
Conversation
Fixes utksh1#1619: backend/data/.api_key stored the single authentication secret as a plaintext file with no TTL, rotation endpoint, or revocation mechanism. Any process with read access to that directory (a co-located container, a path-traversal exploit, or a leaked backup) obtained a credential that was valid indefinitely. Changes: - Key file is now JSON ({"key": ..., "created_at": <epoch seconds>}) instead of bare plaintext, so the key's age can be tracked. A pre-existing plaintext key file is migrated in place on next startup -- the key itself is kept, just wrapped with a fresh created_at. - POST /api/v1/admin/api-key/rotate generates a new key and invalidates the old one immediately (there is no grace period -- the old key stops authenticating the instant this returns). Gated by the separate, statically-configured admin API key so a leaked client key can never be used to mint itself a replacement. - GET /api/v1/admin/api-key/status reports the key's age and, if configured, its expiry, without exposing the key itself. - New SECUSCAN_API_KEY_TTL_SECONDS setting (default 0 = disabled, so existing deployments are unaffected until an operator opts in). When set, both require_api_key and the session-creation endpoint reject the client key once it is older than the TTL, forcing rotation. - Documented the new rotation/expiry flow in docs/api-authentication.md. Testing: - testing/backend/unit/test_api_key_rotation.py: 15 new tests covering the JSON key format, legacy-plaintext migration, rotate/status endpoint auth gating, immediate old-key invalidation, and TTL expiry on both the main auth dependency and session creation. - Updated testing/backend/unit/test_auth.py and test_api_auth.py: the existing init_api_key tests asserted the key file was bare plaintext; updated to read the new JSON format (the key value itself is unchanged). - pytest testing/backend/unit -q -m 'not benchmark' -- 2240 passed, 1 pre-existing failure unrelated to this change (parser sandbox timeout test, flaky in this environment) - ruff check backend/secuscan/auth.py backend/secuscan/routes.py backend/secuscan/config.py testing/backend/unit/test_api_key_rotation.py testing/backend/unit/test_api_auth.py testing/backend/unit/test_auth.py -- all checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
backend/data/.api_keystores the single authentication secret as a plaintext file with no TTL, rotation endpoint, or revocation mechanism. Any process with read access to that directory (a co-located container, a path-traversal exploit, or a leaked backup) obtains a credential that is valid indefinitely.Fix
{"key": ..., "created_at": <epoch seconds>}) instead of bare plaintext, so the key's age can be tracked. A pre-existing plaintext key file is migrated in place on next startup -- the key itself is kept, just wrapped with a freshcreated_at.POST /api/v1/admin/api-key/rotategenerates a new key and invalidates the old one immediately -- no grace period. Gated by the separate, statically-configured admin API key so a leaked client key can never be used to mint itself a replacement.GET /api/v1/admin/api-key/statusreports the key's age and, if configured, its expiry, without exposing the key itself.SECUSCAN_API_KEY_TTL_SECONDSsetting (default0= disabled, so existing deployments are unaffected until an operator opts in). When set, bothrequire_api_keyand the session-creation endpoint reject the client key once it's older than the TTL, forcing rotation.docs/api-authentication.md.Testing
testing/backend/unit/test_api_key_rotation.py: 15 new tests covering the JSON key format, legacy-plaintext migration, rotate/status endpoint auth gating, immediate old-key invalidation, and TTL expiry on both the main auth dependency and session creationtest_auth.pyandtest_api_auth.py: existinginit_api_keytests asserted the key file was bare plaintext; updated to read the new JSON format (the key value itself is unchanged)pytest testing/backend/unit -q -m "not benchmark"-- 2240 passed, 1 pre-existing failure unrelated to this change (parser sandbox timeout test, flaky in this environment)ruff check-- all checks passedFixes #1619