Skip to content

Commit 577fac8

Browse files
authored
Keep service auth PostgreSQL-owned in ConfigMap mode (#1267)
* Preserve service auth identity in ConfigMap mode * Make PostgreSQL authoritative for service auth * Hydrate service auth in ConfigMap integration tests
1 parent f7f6be8 commit 577fac8

3 files changed

Lines changed: 222 additions & 60 deletions

File tree

src/service/core/config/configmap_loader.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,9 @@ def __init__(
131131
backend_queue_updater: Callable[..., bool] | None = None,
132132
backend_test_updater: Callable[..., bool] | None = None,
133133
):
134-
# Kept for existing service/test wiring. ConfigMap loads must not
135-
# hydrate ConfigMap-managed values from Postgres.
136-
del postgres
137134
self._config_file_path = config_file_path
135+
self._postgres = postgres
136+
self._db_service_auth: Dict[str, Any] | None = None
138137
self._event_recorder = event_recorder
139138
self._enable_reconciliation = enable_reconciliation
140139
self._backend_queue_updater = backend_queue_updater
@@ -239,11 +238,19 @@ def _load_and_apply(self) -> LoadResult:
239238
# Dataset config is deprecated; tolerate stale ConfigMap blocks without loading them.
240239
managed_configs.pop('dataset', None)
241240

241+
# JWT signing identity is PostgreSQL-owned. Discard ConfigMap input before
242+
# resolving secret references so it is never interpreted as runtime auth.
243+
service_config = managed_configs.get('service')
244+
if isinstance(service_config, dict):
245+
service_config.pop('service_auth', None)
246+
242247
# Resolve secret file references (reads mounted K8s Secret files)
243248
for section in managed_configs.values():
244249
if isinstance(section, dict):
245250
_resolve_secret_file_references(section)
246251

252+
self._hydrate_service_auth(managed_configs)
253+
247254
validation_errors = _validate_configmap_runtime_contract(managed_configs)
248255
validation_errors.extend(_validate_configs(managed_configs))
249256
if validation_errors:
@@ -282,6 +289,29 @@ def _load_and_apply(self) -> LoadResult:
282289

283290
return LoadResult.SUCCESS
284291

292+
def _hydrate_service_auth(
293+
self, managed_configs: Dict[str, Any],
294+
) -> None:
295+
"""Hydrate the stable JWT signing identity from Postgres."""
296+
service_config = managed_configs.setdefault('service', {})
297+
if not isinstance(service_config, dict):
298+
return
299+
300+
if self._db_service_auth is None and self._postgres is not None:
301+
persisted_service_config = self._postgres.get_service_configs()
302+
persisted_service_config_dict = (
303+
persisted_service_config.plaintext_dict(
304+
by_alias=True, exclude_unset=True))
305+
persisted_service_auth = persisted_service_config_dict.get(
306+
'service_auth')
307+
if isinstance(persisted_service_auth, dict):
308+
self._db_service_auth = copy.deepcopy(persisted_service_auth)
309+
310+
if self._db_service_auth is not None:
311+
service_config['service_auth'] = copy.deepcopy(
312+
self._db_service_auth)
313+
314+
285315
def start_config_watcher(
286316
config_file: str | None,
287317
postgres: connectors.PostgresConnector | None = None,
@@ -304,8 +334,10 @@ def start_config_watcher(
304334
are handled defensively by configmap_events; this gate just avoids
305335
cross-service duplication.)
306336
307-
ConfigMap mode is authoritative for service config. The watcher does
308-
not read config rows from Postgres.
337+
ConfigMap mode is authoritative for managed config except service auth.
338+
ConfigMap-supplied service auth is ignored. On the first load, the watcher
339+
recovers the stable signing identity from Postgres; reloads preserve only
340+
that watcher-local, DB-derived identity.
309341
"""
310342
if not config_file:
311343
return None
@@ -723,12 +755,17 @@ def _validate_configmap_runtime_contract(
723755
) -> List[str]:
724756
"""Validate runtime fields that ConfigMap mode must own.
725757
726-
ConfigMap mode does not hydrate config from DB. Fields that used to be
727-
DB/runtime-derived but are required for backend side effects must therefore
728-
be present in the ConfigMap.
758+
Runtime fields must be present after explicit secret resolution and
759+
PostgreSQL auth hydration.
729760
"""
730761
errors: List[str] = []
731762

763+
service_config = managed_configs.get('service')
764+
if (not isinstance(service_config, dict)
765+
or 'service_auth' not in service_config):
766+
errors.append(
767+
'service.service_auth: required from PostgreSQL in ConfigMap mode')
768+
732769
backends = managed_configs.get('backends', {})
733770
if isinstance(backends, dict):
734771
for backend_name, backend_config in backends.items():

src/service/core/config/tests/test_configmap_loader_integration.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ def test_watcher_load_populates_snapshot(self):
327327
mode='w', suffix='.yaml', delete=False) as temp_file:
328328
yaml.dump(_with_service_auth(config), temp_file)
329329
try:
330-
watcher = configmap_loader.ConfigMapWatcher(temp_file.name)
330+
watcher = configmap_loader.ConfigMapWatcher(
331+
temp_file.name, self._get_postgres())
331332
result = watcher._load_and_apply()
332333
self.assertTrue(result)
333334

@@ -381,7 +382,8 @@ def test_watcher_resolves_pool_parsed_fields(self):
381382
mode='w', suffix='.yaml', delete=False) as temp_file:
382383
yaml.dump(_with_service_auth(config), temp_file)
383384
try:
384-
watcher = configmap_loader.ConfigMapWatcher(temp_file.name)
385+
watcher = configmap_loader.ConfigMapWatcher(
386+
temp_file.name, self._get_postgres())
385387
result = watcher._load_and_apply()
386388
self.assertTrue(result)
387389

0 commit comments

Comments
 (0)