Skip to content

Commit b0b0f31

Browse files
luis5tbclaude
andcommitted
fix: fail-fast on missing or invalid DCR_ENCRYPTION_KEY
Previously, _encrypt_secret() silently generated an ephemeral Fernet key when DCR_ENCRYPTION_KEY was not configured. Secrets encrypted with an ephemeral key become permanently unrecoverable after a container restart. - Raise RuntimeError in _encrypt_secret() when no Fernet cipher is available instead of generating an ephemeral key - Raise ValueError in DCRService.__init__() when DCR_ENCRYPTION_KEY is missing on Cloud Run (K_SERVICE is set) - Raise ValueError on invalid DCR_ENCRYPTION_KEY at construction time instead of logging and continuing with _fernet=None - Add a stable Fernet key to test conftest so DCR tests have a valid encryption key available Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6bb139c commit b0b0f31

2 files changed

Lines changed: 29 additions & 4 deletions

File tree

src/lightspeed_agent/dcr/service.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import logging
6+
import os
67
from typing import TYPE_CHECKING
78

89
import httpx
@@ -73,13 +74,28 @@ def __init__(
7374
self._client_repository = client_repository or get_dcr_client_repository()
7475
self._settings = get_settings()
7576

76-
# Fernet cipher for encrypting client secrets
77+
# Fernet cipher for encrypting client secrets.
78+
# In production (Cloud Run), DCR_ENCRYPTION_KEY is required to prevent
79+
# silent data loss from missing encryption configuration.
7780
self._fernet: Fernet | None = None
7881
if self._settings.dcr_encryption_key:
7982
try:
8083
self._fernet = Fernet(self._settings.dcr_encryption_key.encode())
8184
except Exception as e:
82-
logger.error("Invalid DCR encryption key: %s", e)
85+
raise ValueError(
86+
f"Invalid DCR_ENCRYPTION_KEY: {e}. "
87+
"Generate a valid key with: "
88+
"python -c 'from cryptography.fernet import Fernet; "
89+
"print(Fernet.generate_key().decode())'"
90+
) from e
91+
elif os.getenv("K_SERVICE"):
92+
raise ValueError(
93+
"DCR_ENCRYPTION_KEY is required in production "
94+
f"(K_SERVICE={os.getenv('K_SERVICE')}). "
95+
"Client secrets cannot be stored without an encryption key. "
96+
"Generate a key with: python -c 'from cryptography.fernet import Fernet; "
97+
"print(Fernet.generate_key().decode())'"
98+
)
8399

84100
def _get_gma_client(self) -> GMAClient:
85101
"""Get the GMA client (lazy initialization)."""
@@ -95,10 +111,15 @@ def _encrypt_secret(self, secret: str) -> str:
95111
96112
Returns:
97113
Encrypted secret as base64 string.
114+
115+
Raises:
116+
RuntimeError: If DCR_ENCRYPTION_KEY is not configured.
98117
"""
99118
if not self._fernet:
100-
logger.warning("DCR_ENCRYPTION_KEY not set, using ephemeral key")
101-
self._fernet = Fernet(Fernet.generate_key())
119+
raise RuntimeError(
120+
"Cannot encrypt client secret: DCR_ENCRYPTION_KEY is not configured. "
121+
"Set DCR_ENCRYPTION_KEY to a valid Fernet key before performing DCR operations."
122+
)
102123
return self._fernet.encrypt(secret.encode()).decode()
103124

104125
def _decrypt_secret(self, encrypted_secret: str) -> str | None:

tests/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
os.environ["DCR_ENABLED"] = "false" # Use pre-seeded credentials for tests
1919
os.environ["RED_HAT_SSO_CLIENT_ID"] = "test-static-client-id"
2020
os.environ["RED_HAT_SSO_CLIENT_SECRET"] = "test-static-client-secret"
21+
# Stable Fernet key for tests — secrets encrypted in one test can be decrypted in another
22+
from cryptography.fernet import Fernet as _Fernet # noqa: E402
23+
24+
os.environ["DCR_ENCRYPTION_KEY"] = _Fernet.generate_key().decode()
2125

2226

2327
@pytest.fixture

0 commit comments

Comments
 (0)