fix(ops): bridge production metrics securely
This commit is contained in:
@@ -51,7 +51,12 @@ INTERNAL_ONLY_ENDPOINTS = frozenset({
|
||||
})
|
||||
INTERNAL_METRICS_NETWORKS = tuple(
|
||||
ipaddress.ip_network(cidr)
|
||||
for cidr in ("127.0.0.0/8", "172.16.0.0/12", "::1/128")
|
||||
for cidr in (
|
||||
"127.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.110/32",
|
||||
"::1/128",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -60,6 +60,15 @@ def render_nginx_config(config_text: str, fragment_text: str) -> str:
|
||||
fragment = fragment_text.strip()
|
||||
if "location = /metrics" not in fragment or "deny all;" not in fragment:
|
||||
raise ValueError("metrics nginx fragment is incomplete")
|
||||
if re.search(r"(?m)^server\s*\{", fragment):
|
||||
managed = f"{NGINX_BEGIN}\n{fragment}\n{NGINX_END}"
|
||||
replaced = _managed_replace(config_text, NGINX_BEGIN, NGINX_END, managed)
|
||||
if replaced is not None:
|
||||
return f"{replaced.rstrip()}\n"
|
||||
if config_text.strip():
|
||||
raise ValueError("standalone nginx fragment requires a new or managed config")
|
||||
return f"{managed}\n"
|
||||
|
||||
indented_fragment = "\n".join(f" {line}" if line else "" for line in fragment.splitlines())
|
||||
managed = f"{NGINX_BEGIN}\n{indented_fragment}\n {NGINX_END}"
|
||||
replaced = _managed_replace(config_text, NGINX_BEGIN, NGINX_END, managed)
|
||||
@@ -121,7 +130,8 @@ def build_metrics_edge_plan(
|
||||
) -> dict[str, Any]:
|
||||
if not IDENTITY_PATTERN.fullmatch(run_id) or not IDENTITY_PATTERN.fullmatch(work_item_id):
|
||||
raise ValueError("run_id and work_item_id must use bounded safe characters")
|
||||
nginx_before = nginx_config.read_text(encoding="utf-8")
|
||||
nginx_existed_before = nginx_config.exists()
|
||||
nginx_before = nginx_config.read_text(encoding="utf-8") if nginx_existed_before else ""
|
||||
prometheus_before = prometheus_config.read_text(encoding="utf-8")
|
||||
nginx_after = render_nginx_config(
|
||||
nginx_before,
|
||||
@@ -140,6 +150,7 @@ def build_metrics_edge_plan(
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"nginx": {
|
||||
"path": str(nginx_config),
|
||||
"existed_before": nginx_existed_before,
|
||||
"before_sha256": _sha256(nginx_before),
|
||||
"candidate_sha256": _sha256(nginx_after),
|
||||
"changed": nginx_before != nginx_after,
|
||||
@@ -174,7 +185,7 @@ def _run(command: list[str], timeout: int = 30) -> subprocess.CompletedProcess[s
|
||||
|
||||
|
||||
def _atomic_write(path: Path, content: str) -> None:
|
||||
current = path.stat()
|
||||
current = path.stat() if path.exists() else None
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
@@ -184,11 +195,23 @@ def _atomic_write(path: Path, content: str) -> None:
|
||||
) as handle:
|
||||
handle.write(content)
|
||||
temporary = Path(handle.name)
|
||||
os.chmod(temporary, current.st_mode)
|
||||
os.chown(temporary, current.st_uid, current.st_gid)
|
||||
os.chmod(temporary, current.st_mode if current else 0o644)
|
||||
os.chown(
|
||||
temporary,
|
||||
current.st_uid if current else os.geteuid(),
|
||||
current.st_gid if current else os.getegid(),
|
||||
)
|
||||
os.replace(temporary, path)
|
||||
|
||||
|
||||
def _inplace_write(path: Path, content: str) -> None:
|
||||
"""Update a file bind mount without replacing its host inode."""
|
||||
with path.open("w", encoding="utf-8") as handle:
|
||||
handle.write(content)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
|
||||
|
||||
def _prometheus_target_health(timeout_seconds: int) -> str:
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
url = "http://127.0.0.1:9090/api/v1/targets?state=active"
|
||||
@@ -212,14 +235,25 @@ def _prometheus_target_health(timeout_seconds: int) -> str:
|
||||
return last_health
|
||||
|
||||
|
||||
def apply_metrics_edge_plan(plan: dict[str, Any], *, canary_timeout: int = 90) -> dict[str, Any]:
|
||||
def apply_metrics_edge_plan(
|
||||
plan: dict[str, Any],
|
||||
*,
|
||||
canary_timeout: int = 90,
|
||||
apply_scope: str = "full",
|
||||
metrics_canary_url: str = "https://mo.wooo.work/metrics",
|
||||
metrics_canary_resolve: str = "mo.wooo.work:443:127.0.0.1",
|
||||
) -> dict[str, Any]:
|
||||
if apply_scope not in {"full", "prometheus_only"}:
|
||||
raise ValueError("apply_scope must be full or prometheus_only")
|
||||
candidate = plan.pop("_candidate_text")
|
||||
nginx_path = Path(plan["nginx"]["path"])
|
||||
prometheus_path = Path(plan["prometheus"]["path"])
|
||||
suffix = re.sub(r"[^A-Za-z0-9_.-]", "_", plan["run_id"])
|
||||
nginx_backup = nginx_path.with_name(f"{nginx_path.name}.bak.{suffix}")
|
||||
prometheus_backup = prometheus_path.with_name(f"{prometheus_path.name}.bak.{suffix}")
|
||||
shutil.copy2(nginx_path, nginx_backup)
|
||||
nginx_existed_before = bool(plan["nginx"].get("existed_before"))
|
||||
if apply_scope == "full" and nginx_existed_before:
|
||||
shutil.copy2(nginx_path, nginx_backup)
|
||||
shutil.copy2(prometheus_path, prometheus_backup)
|
||||
receipt = {
|
||||
key: value for key, value in plan.items()
|
||||
@@ -228,33 +262,42 @@ def apply_metrics_edge_plan(plan: dict[str, Any], *, canary_timeout: int = 90) -
|
||||
receipt.update({
|
||||
"receipt_kind": "metrics_edge_controlled_apply_receipt",
|
||||
"mode": "apply",
|
||||
"apply_scope": apply_scope,
|
||||
"status": "in_progress",
|
||||
"backup_paths": {
|
||||
"nginx": str(nginx_backup),
|
||||
"prometheus": str(prometheus_backup),
|
||||
},
|
||||
"rollback_performed": False,
|
||||
"post_verifier": {},
|
||||
})
|
||||
if apply_scope == "full" and nginx_existed_before:
|
||||
receipt["backup_paths"]["nginx"] = str(nginx_backup)
|
||||
elif apply_scope == "full":
|
||||
receipt["nginx_created_new_file"] = True
|
||||
try:
|
||||
_atomic_write(nginx_path, candidate["nginx"])
|
||||
_atomic_write(prometheus_path, candidate["prometheus"])
|
||||
_run(["nginx", "-t"])
|
||||
if apply_scope == "full":
|
||||
_atomic_write(nginx_path, candidate["nginx"])
|
||||
_inplace_write(prometheus_path, candidate["prometheus"])
|
||||
if apply_scope == "full":
|
||||
_run(["nginx", "-t"])
|
||||
_run([
|
||||
"docker", "exec", "prometheus", "promtool", "check", "config",
|
||||
"/etc/prometheus/prometheus.yml",
|
||||
])
|
||||
_run(["systemctl", "reload", "nginx"])
|
||||
if apply_scope == "full":
|
||||
_run(["systemctl", "reload", "nginx"])
|
||||
_run(["docker", "kill", "--signal=SIGHUP", "prometheus"])
|
||||
internal = _run([
|
||||
"curl", "-ksS", "--max-time", "20", "--resolve",
|
||||
"mo.wooo.work:443:127.0.0.1", "-o", "/dev/null", "-w", "%{http_code}",
|
||||
"https://mo.wooo.work/metrics",
|
||||
canary_command = ["curl", "--noproxy", "*", "-sS", "--max-time", "20"]
|
||||
if metrics_canary_resolve:
|
||||
canary_command.extend(["--resolve", metrics_canary_resolve])
|
||||
canary_command.extend([
|
||||
"-o", "/dev/null", "-w", "%{http_code}", metrics_canary_url,
|
||||
])
|
||||
internal = _run(canary_command)
|
||||
internal_code = internal.stdout.strip()
|
||||
target_health = _prometheus_target_health(max(10, canary_timeout))
|
||||
receipt["post_verifier"] = {
|
||||
"nginx_config_valid": True,
|
||||
"nginx_config_valid": apply_scope == "full",
|
||||
"prometheus_config_valid": True,
|
||||
"internal_metrics_http_code": internal_code,
|
||||
"metrics_internal_canary": internal_code == "200",
|
||||
@@ -268,14 +311,16 @@ def apply_metrics_edge_plan(plan: dict[str, Any], *, canary_timeout: int = 90) -
|
||||
receipt["completed_at"] = datetime.now(timezone.utc).isoformat()
|
||||
return receipt
|
||||
except Exception as exc:
|
||||
shutil.copy2(nginx_backup, nginx_path)
|
||||
if apply_scope == "full" and nginx_existed_before:
|
||||
shutil.copy2(nginx_backup, nginx_path)
|
||||
elif apply_scope == "full":
|
||||
nginx_path.unlink(missing_ok=True)
|
||||
shutil.copy2(prometheus_backup, prometheus_path)
|
||||
rollback_errors: list[str] = []
|
||||
for command in (
|
||||
["nginx", "-t"],
|
||||
["systemctl", "reload", "nginx"],
|
||||
["docker", "kill", "--signal=SIGHUP", "prometheus"],
|
||||
):
|
||||
rollback_commands = [["docker", "kill", "--signal=SIGHUP", "prometheus"]]
|
||||
if apply_scope == "full":
|
||||
rollback_commands[:0] = [["nginx", "-t"], ["systemctl", "reload", "nginx"]]
|
||||
for command in rollback_commands:
|
||||
try:
|
||||
_run(command)
|
||||
except Exception as rollback_exc:
|
||||
|
||||
Reference in New Issue
Block a user