Implement Engine HTTP interfaces for health, enrollment, and tokens

This commit is contained in:
2025-10-22 13:33:15 -06:00
parent 7b5248dfe5
commit 9292cfb280
28 changed files with 1840 additions and 77 deletions

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import logging
import sqlite3
import time
import uuid
from contextlib import closing
from datetime import datetime, timezone
from typing import Optional
@@ -152,6 +153,133 @@ class SQLiteDeviceRepository:
return self._row_to_record(row)
def ensure_device_record(
self,
*,
guid: DeviceGuid,
hostname: str,
fingerprint: DeviceFingerprint,
) -> DeviceRecord:
now_iso = datetime.now(tz=timezone.utc).isoformat()
now_ts = int(time.time())
with closing(self._connections()) as conn:
cur = conn.cursor()
cur.execute(
"""
SELECT guid, hostname, token_version, status, ssl_key_fingerprint, key_added_at
FROM devices
WHERE UPPER(guid) = ?
""",
(guid.value.upper(),),
)
row = cur.fetchone()
if row:
stored_fp = (row[4] or "").strip().lower()
new_fp = fingerprint.value
if not stored_fp:
cur.execute(
"UPDATE devices SET ssl_key_fingerprint = ?, key_added_at = ? WHERE guid = ?",
(new_fp, now_iso, row[0]),
)
elif stored_fp != new_fp:
token_version = self._coerce_int(row[2], default=1) + 1
cur.execute(
"""
UPDATE devices
SET ssl_key_fingerprint = ?,
key_added_at = ?,
token_version = ?,
status = 'active'
WHERE guid = ?
""",
(new_fp, now_iso, token_version, row[0]),
)
cur.execute(
"""
UPDATE refresh_tokens
SET revoked_at = ?
WHERE guid = ?
AND revoked_at IS NULL
""",
(now_iso, row[0]),
)
conn.commit()
else:
resolved_hostname = self._resolve_hostname(cur, hostname, guid)
cur.execute(
"""
INSERT INTO devices (
guid,
hostname,
created_at,
last_seen,
ssl_key_fingerprint,
token_version,
status,
key_added_at
)
VALUES (?, ?, ?, ?, ?, 1, 'active', ?)
""",
(
guid.value,
resolved_hostname,
now_ts,
now_ts,
fingerprint.value,
now_iso,
),
)
conn.commit()
cur.execute(
"""
SELECT guid, ssl_key_fingerprint, token_version, status
FROM devices
WHERE UPPER(guid) = ?
""",
(guid.value.upper(),),
)
latest = cur.fetchone()
if not latest:
raise RuntimeError("device record could not be ensured")
record = self._row_to_record(latest)
if record is None:
raise RuntimeError("device record invalid after ensure")
return record
def record_device_key(
self,
*,
guid: DeviceGuid,
fingerprint: DeviceFingerprint,
added_at: datetime,
) -> None:
added_iso = added_at.astimezone(timezone.utc).isoformat()
with closing(self._connections()) as conn:
cur = conn.cursor()
cur.execute(
"""
INSERT OR IGNORE INTO device_keys (id, guid, ssl_key_fingerprint, added_at)
VALUES (?, ?, ?, ?)
""",
(str(uuid.uuid4()), guid.value, fingerprint.value, added_iso),
)
cur.execute(
"""
UPDATE device_keys
SET retired_at = ?
WHERE guid = ?
AND ssl_key_fingerprint != ?
AND retired_at IS NULL
""",
(added_iso, guid.value, fingerprint.value),
)
conn.commit()
def _row_to_record(self, row: tuple) -> Optional[DeviceRecord]:
try:
guid = DeviceGuid(row[0])
@@ -181,3 +309,31 @@ class SQLiteDeviceRepository:
token_version=max(token_version, 1),
status=status,
)
@staticmethod
def _coerce_int(value: object, *, default: int = 0) -> int:
try:
return int(value)
except Exception:
return default
def _resolve_hostname(self, cur: sqlite3.Cursor, hostname: str, guid: DeviceGuid) -> str:
base = (hostname or "").strip() or guid.value
base = base[:253]
candidate = base
suffix = 1
while True:
cur.execute(
"SELECT guid FROM devices WHERE hostname = ?",
(candidate,),
)
row = cur.fetchone()
if not row:
return candidate
existing = (row[0] or "").strip().upper()
if existing == guid.value:
return candidate
candidate = f"{base}-{suffix}"
suffix += 1
if suffix > 50:
return guid.value

View File

@@ -78,6 +78,50 @@ class SQLiteEnrollmentRepository:
self._log.warning("invalid enrollment code record for code=%s: %s", code_value, exc)
return None
def fetch_install_code_by_id(self, record_id: str) -> Optional[EnrollmentCode]:
record_value = (record_id or "").strip()
if not record_value:
return None
with closing(self._connections()) as conn:
cur = conn.cursor()
cur.execute(
"""
SELECT id,
code,
expires_at,
used_at,
used_by_guid,
max_uses,
use_count,
last_used_at
FROM enrollment_install_codes
WHERE id = ?
""",
(record_value,),
)
row = cur.fetchone()
if not row:
return None
record = {
"id": row[0],
"code": row[1],
"expires_at": row[2],
"used_at": row[3],
"used_by_guid": row[4],
"max_uses": row[5],
"use_count": row[6],
"last_used_at": row[7],
}
try:
return EnrollmentCode.from_mapping(record)
except Exception as exc: # pragma: no cover - defensive logging
self._log.warning("invalid enrollment code record for id=%s: %s", record_value, exc)
return None
def update_install_code_usage(
self,
record_id: str,
@@ -135,6 +179,53 @@ class SQLiteEnrollmentRepository:
return None
return self._fetch_device_approval("id = ?", (record_value,))
def fetch_pending_approval_by_fingerprint(
self, fingerprint: DeviceFingerprint
) -> Optional[EnrollmentApproval]:
return self._fetch_device_approval(
"ssl_key_fingerprint_claimed = ? AND status = 'pending'",
(fingerprint.value,),
)
def update_pending_approval(
self,
record_id: str,
*,
hostname: str,
guid: Optional[DeviceGuid],
enrollment_code_id: Optional[str],
client_nonce_b64: str,
server_nonce_b64: str,
agent_pubkey_der: bytes,
updated_at: datetime,
) -> None:
with closing(self._connections()) as conn:
cur = conn.cursor()
cur.execute(
"""
UPDATE device_approvals
SET hostname_claimed = ?,
guid = ?,
enrollment_code_id = ?,
client_nonce = ?,
server_nonce = ?,
agent_pubkey_der = ?,
updated_at = ?
WHERE id = ?
""",
(
hostname,
guid.value if guid else None,
enrollment_code_id,
client_nonce_b64,
server_nonce_b64,
agent_pubkey_der,
self._isoformat(updated_at),
record_id,
),
)
conn.commit()
def create_device_approval(
self,
*,
@@ -143,8 +234,8 @@ class SQLiteEnrollmentRepository:
claimed_hostname: str,
claimed_fingerprint: DeviceFingerprint,
enrollment_code_id: Optional[str],
client_nonce: bytes,
server_nonce: bytes,
client_nonce_b64: str,
server_nonce_b64: str,
agent_pubkey_der: bytes,
created_at: datetime,
status: EnrollmentApprovalStatus = EnrollmentApprovalStatus.PENDING,
@@ -183,8 +274,8 @@ class SQLiteEnrollmentRepository:
status.value,
created_iso,
created_iso,
client_nonce,
server_nonce,
client_nonce_b64,
server_nonce_b64,
agent_pubkey_der,
),
)
@@ -244,7 +335,10 @@ class SQLiteEnrollmentRepository:
created_at,
updated_at,
status,
approved_by_user_id
approved_by_user_id,
client_nonce,
server_nonce,
agent_pubkey_der
FROM device_approvals
WHERE {where}
""",
@@ -266,6 +360,9 @@ class SQLiteEnrollmentRepository:
"updated_at": row[7],
"status": row[8],
"approved_by_user_id": row[9],
"client_nonce": row[10],
"server_nonce": row[11],
"agent_pubkey_der": row[12],
}
try:

View File

@@ -78,6 +78,35 @@ class SQLiteRefreshTokenRepository:
)
conn.commit()
def create(
self,
*,
record_id: str,
guid: DeviceGuid,
token_hash: str,
created_at: datetime,
expires_at: Optional[datetime],
) -> None:
created_iso = self._isoformat(created_at)
expires_iso = self._isoformat(expires_at) if expires_at else None
with closing(self._connections()) as conn:
cur = conn.cursor()
cur.execute(
"""
INSERT INTO refresh_tokens (
id,
guid,
token_hash,
created_at,
expires_at
)
VALUES (?, ?, ?, ?, ?)
""",
(record_id, guid.value, token_hash, created_iso, expires_iso),
)
conn.commit()
def _row_to_record(self, row: tuple) -> Optional[RefreshTokenRecord]:
try:
guid = DeviceGuid(row[1])