mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-27 03:21:57 -06:00
Implement Engine HTTP interfaces for health, enrollment, and tokens
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user