Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions app/service/auth_svc.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,22 @@
except (ValueError, TypeError):
max_age = None
try:
if os.path.exists(os.path.join('data', cookie_file)):
secret_key = file_svc._read(cookie_path)
self.log.debug('Loaded persistent session key from data/cookie_storage')
if os.path.exists(cookie_path):
try:
secret_key = file_svc._read(cookie_path)
self.log.debug('Loaded persistent session key from data/cookie_storage')
except SystemExit:

Check failure on line 90 in app/service/auth_svc.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reraise this exception to stop the application as the user expects

See more on https://sonarcloud.io/project/issues?id=mitre_caldera&issues=AZ1VCzFxKQyapgKNB6vO&open=AZ1VCzFxKQyapgKNB6vO&pullRequest=3354
# file_svc._read() calls sys.exit(1) on InvalidToken (encryption key mismatch).
self.log.warning('Failed to decrypt cookie_storage (encryption key mismatch). '
'Regenerating session key — existing sessions will be invalidated.')
os.remove(cookie_path)
secret_key = os.urandom(32)
Comment thread
deacon-mp marked this conversation as resolved.
file_svc._save(cookie_path, secret_key, encrypt=True)
Comment thread
deacon-mp marked this conversation as resolved.
else:
Comment thread
deacon-mp marked this conversation as resolved.
# Generate a new random 32-byte key for AES encryption if no valid key is found in the config or data folder
secret_key = os.urandom(32)
file_svc._save(cookie_path, secret_key, encrypt=True)
self.log.debug('Generated and saved new persistent session key.')
except Exception as e:
# Fallback if file operations fail
self.log.warning('Could not manage persistent key file, falling back to ephemeral: %s', e)
secret_key = os.urandom(32)
if len(secret_key) != 32:
Expand Down
1 change: 1 addition & 0 deletions app/service/data_svc.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'data/results/*',
'data/sources/*',
'data/object_store',
'data/cookie_storage',
)

PAYLOADS_CONFIG_STANDARD_KEY = 'standard_payloads'
Expand Down
74 changes: 74 additions & 0 deletions tests/services/test_fresh_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Tests for --fresh cleanup and auth_svc cookie recovery."""
import copy
import os

import pytest

from app.service.auth_svc import AuthService, CONFIG_API_KEY_RED
from app.service.data_svc import DATA_FILE_GLOBS
from app.service.file_svc import FileSvc
from app.utility.base_world import BaseWorld


class TestDataFileGlobs:
"""Verify that critical encrypted files are in the --fresh cleanup list."""

def test_cookie_storage_in_data_file_globs(self):
assert any('cookie_storage' in p for p in DATA_FILE_GLOBS), \
'data/cookie_storage must be in DATA_FILE_GLOBS so --fresh cleans it up'

def test_object_store_in_data_file_globs(self):
assert any('object_store' in p for p in DATA_FILE_GLOBS), \
'data/object_store must be in DATA_FILE_GLOBS'


Comment thread
deacon-mp marked this conversation as resolved.
class TestAuthSvcCookieRecovery:
"""Verify auth_svc recovers when cookie_storage has a stale encryption key."""

@pytest.fixture(autouse=True)
def setup_and_teardown(self):
self.cookie_path = os.path.join('data', 'cookie_storage')
# Save existing state
self._saved_config = copy.deepcopy(BaseWorld._app_configuration)
# Clean pre-existing cookie
if os.path.exists(self.cookie_path):
os.remove(self.cookie_path)
yield
# Restore original state — leave no trace
if os.path.exists(self.cookie_path):
os.remove(self.cookie_path)
BaseWorld._app_configuration = self._saved_config

def _apply_config(self, encryption_key):
BaseWorld.clear_config()
BaseWorld.apply_config(
name='main',
config={
CONFIG_API_KEY_RED: 'abc123',
'crypt_salt': 'REPLACE_WITH_RANDOM_VALUE',
'encryption_key': encryption_key,
'users': {
'red': {'reduser': 'redpass'},
'blue': {'blueuser': 'bluepass'}
},
},
apply_hash=True
)
Comment thread
deacon-mp marked this conversation as resolved.
FileSvc()

Comment thread
deacon-mp marked this conversation as resolved.
Comment thread
deacon-mp marked this conversation as resolved.
@pytest.mark.asyncio
async def test_stale_cookie_does_not_crash_server(self):
"""Cookie encrypted with key A must not crash server when loaded with key B."""
from aiohttp import web

# Round 1: create cookie_storage with key A
self._apply_config('KEY_ALPHA_123')
auth1 = AuthService()
await auth1.apply(app=web.Application(), users=BaseWorld.get_config('users'))
assert os.path.exists(self.cookie_path), 'cookie_storage should be created'

# Round 2: switch to key B — before fix this was sys.exit(1)
self._apply_config('KEY_BETA_456')
auth2 = AuthService()
await auth2.apply(app=web.Application(), users=BaseWorld.get_config('users'))
assert os.path.exists(self.cookie_path), 'cookie_storage should be regenerated'
Loading