Files
minerops/backend/app/discovery/csv_import.py
T
lussierj 8b0d32cd6b security(discovery): enforce Zone isolation and retire unsafe legacy writers
Bind Agent observations, review, enrichment and telemetry to current Zone
and inventory authority. Preserve immutable MAC ownership and historical
snapshots with stale-result rejection and transactional security audits.

Retire unbound direct/range/scheduled discovery and one/bulk legacy summary
writes in both modes. Require reconciled exact inventory identity or
explicit Zone-scoped IP selection for CSV metadata imports.

Independent Blocker #1 application-security re-audit: PASS on tree
cf863aadc9. Evidence: 141 focused PostgreSQL/HTTP
tests passed; Chromium and PowerShell checks passed. Two documented baseline
source assertions remain unchanged. Production deployment is not approved.
2026-10-17 00:45:04 +00:00

97 lines
3.9 KiB
Python

"""Exact administrative CSV selection, inside the caller's locked transaction.
CSV metadata never establishes ownership or replaces board identity. Every supplied
identity is a constraint; a failed strong identifier never falls back to an IP.
"""
from uuid import UUID
from fastapi import HTTPException
from app.discovery.repository import address, mac
def resolve_inventory_row(cur, row, row_number, selected_zone=None):
def fail(message, status=409):
raise HTTPException(status_code=status, detail=f"CSV row {row_number}: {message}")
def field(names, normalize=lambda v: v):
values = []
for name in names:
value = row.get(name)
if value is not None and str(value).strip():
try:
values.append(normalize(str(value).strip()))
except (ValueError, HTTPException):
fail(f"invalid {names[0]}", 422)
if len(set(values)) > 1:
fail(f"conflicting {names[0]} columns")
return values[0] if values else None
def uuid(value):
return str(UUID(value))
mid = field(["Miner ID", "Miner UUID", "miner_id", "id"], uuid)
zone = field(["Agent Zone ID", "Zone ID", "agent_zone_id", "zone_id"], uuid)
site = field(["Site ID", "site_id"], uuid)
ip = field(["IP", "IP Address", "ip", "ip_address"], address)
current_mac = field(["MAC Addr", "MAC", "Mac", "mac", "mac_address"], mac)
serial = field(["Serial", "Serial Number", "SN", "Miner SN", "miner_sn", "serial_number"])
if selected_zone:
try:
selected_zone = uuid(selected_zone)
except ValueError:
fail("invalid selected Agent Zone ID", 422)
if zone and zone != selected_zone:
fail("row Zone disagrees with selected Zone")
zone = selected_zone
if zone:
cur.execute("SELECT site_id::text FROM agent_zones WHERE id=%s", (zone,))
zone_row = cur.fetchone()
if not zone_row:
fail("selected Zone does not exist", 422)
if site and site != zone_row["site_id"]:
fail("Site and Zone disagree")
if not (mid or current_mac or serial or (ip and zone)):
fail("provide Miner ID, current MAC, serial, or IP plus Agent Zone ID", 422)
selectors = []
if mid:
selectors.append(("id=%s::uuid", mid))
if current_mac:
selectors.append(("regexp_replace(lower(current_mac), '[^0-9a-f]', '', 'g')=%s", current_mac.replace(":", "").lower()))
if serial:
selectors.append(("miner_sn=%s", serial))
# IP is only a namespace-scoped selector when no stronger identity is supplied.
if not selectors:
selectors.append(("current_ip=%s::inet AND agent_zone_id=%s::uuid", (ip, zone)))
candidates = {}
for predicate, value in selectors:
params = value if isinstance(value, tuple) else (value,)
cur.execute(
"SELECT id::text, site_id::text, agent_zone_id::text, "
"host(current_ip) AS current_ip, current_mac, miner_sn, model, miner_type, firmware_version "
"FROM miners WHERE " + predicate + " ORDER BY id LIMIT 2 FOR UPDATE",
params,
)
matches = cur.fetchall()
if len(matches) > 1:
fail("ambiguous inventory identity; supply exact Miner ID and consistent identifiers")
candidates.update({m["id"]: m for m in matches})
if not candidates:
return None
if len(candidates) != 1:
fail("identifiers select different inventory records")
miner = next(iter(candidates.values()))
if (
(mid and mid != miner["id"])
or (zone and zone != miner["agent_zone_id"])
or (site and site != miner["site_id"])
or (ip and ip != miner["current_ip"])
or (serial and serial != miner["miner_sn"])
or (current_mac and current_mac != mac(miner["current_mac"]))
):
fail("identifiers disagree with current inventory; refresh selection before importing")
return miner