Exclude expired disabled NPM certificate dependencies
This commit is contained in:
+133
-1
@@ -1,5 +1,5 @@
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, replace
|
||||
from datetime import datetime, timezone
|
||||
from typing import Protocol
|
||||
|
||||
@@ -20,6 +20,7 @@ from .object_normalizer import normalize_observations
|
||||
from .persistence import SQLiteStateStore
|
||||
from .projections import project_proxy_hosts_to_dns
|
||||
from .reconciliation import (
|
||||
ReconciliationConflict,
|
||||
build_endpoint_plans,
|
||||
merge_desired_with_previous,
|
||||
reconcile_observations,
|
||||
@@ -133,6 +134,132 @@ def _authoritative_observations(
|
||||
]
|
||||
|
||||
|
||||
def _certificate_expired(
|
||||
object: NormalizedObject,
|
||||
now: datetime,
|
||||
) -> bool:
|
||||
value = object.payload.get("expires_on")
|
||||
|
||||
if value is None:
|
||||
return False
|
||||
|
||||
if not isinstance(value, str):
|
||||
raise ReconciliationConflict(
|
||||
"Certificate expires_on must be normalized text"
|
||||
)
|
||||
|
||||
normalized = value.strip()
|
||||
|
||||
if normalized.endswith("Z"):
|
||||
normalized = normalized[:-1] + "+00:00"
|
||||
|
||||
try:
|
||||
expires_at = datetime.fromisoformat(normalized)
|
||||
except ValueError as exc:
|
||||
raise ReconciliationConflict(
|
||||
"Certificate expires_on is not a valid timestamp"
|
||||
) from exc
|
||||
|
||||
if expires_at.tzinfo is None:
|
||||
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
||||
|
||||
return expires_at.astimezone(timezone.utc) <= now
|
||||
|
||||
|
||||
def _filter_expired_certificate_dependencies(
|
||||
observations: Iterable[Observation],
|
||||
now: datetime,
|
||||
) -> list[Observation]:
|
||||
rows = list(observations)
|
||||
|
||||
expired_local: set[tuple[str, str]] = set()
|
||||
|
||||
for observation in rows:
|
||||
object = observation.object
|
||||
|
||||
if (
|
||||
object.object_type != CERTIFICATES
|
||||
or not _certificate_expired(object, now)
|
||||
):
|
||||
continue
|
||||
|
||||
if object.local_key is None:
|
||||
raise ReconciliationConflict(
|
||||
"Expired observed certificate requires "
|
||||
"endpoint-local identity"
|
||||
)
|
||||
|
||||
expired_local.add(
|
||||
(
|
||||
observation.endpoint_id,
|
||||
object.local_key,
|
||||
)
|
||||
)
|
||||
|
||||
filtered: list[Observation] = []
|
||||
|
||||
for observation in rows:
|
||||
object = observation.object
|
||||
|
||||
if (
|
||||
object.object_type == CERTIFICATES
|
||||
and object.local_key is not None
|
||||
and (
|
||||
observation.endpoint_id,
|
||||
object.local_key,
|
||||
)
|
||||
in expired_local
|
||||
):
|
||||
continue
|
||||
|
||||
if object.object_type != PROXY_HOSTS:
|
||||
filtered.append(observation)
|
||||
continue
|
||||
|
||||
expired_references = tuple(
|
||||
reference
|
||||
for reference in object.references
|
||||
if (
|
||||
reference.object_type == CERTIFICATES
|
||||
and reference.local_key is not None
|
||||
and (
|
||||
observation.endpoint_id,
|
||||
reference.local_key,
|
||||
)
|
||||
in expired_local
|
||||
)
|
||||
)
|
||||
|
||||
if not expired_references:
|
||||
filtered.append(observation)
|
||||
continue
|
||||
|
||||
if object.payload.get("enabled") is True:
|
||||
raise ReconciliationConflict(
|
||||
"Enabled proxy host references an expired "
|
||||
f"certificate: {object.logical_key!r} "
|
||||
f"on {observation.endpoint_id}"
|
||||
)
|
||||
|
||||
retained_references = tuple(
|
||||
reference
|
||||
for reference in object.references
|
||||
if reference not in expired_references
|
||||
)
|
||||
|
||||
filtered.append(
|
||||
replace(
|
||||
observation,
|
||||
object=replace(
|
||||
object,
|
||||
references=retained_references,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def _planning_adapter_views(
|
||||
adapters: Iterable[ReconciliationAdapter],
|
||||
live_pairs: frozenset[tuple[str, str]],
|
||||
@@ -310,6 +437,11 @@ def discover_reconcile_plan(
|
||||
),
|
||||
)
|
||||
|
||||
npm_observations = _filter_expired_certificate_dependencies(
|
||||
npm_observations,
|
||||
timestamp,
|
||||
)
|
||||
|
||||
npm_desired = (
|
||||
reconcile_observations(npm_observations)
|
||||
if npm_observations
|
||||
|
||||
Reference in New Issue
Block a user