feat(260923-dhh): Proxmox-Modul Aufgabe 1 - PVE per Token, Ende-zu-Ende
- ProxmoxServer/ProxmoxServerStatus mit RLS (tenant_isolation_policy +
system_read_policy auf ProxmoxServer fuer den kommenden Planer)
- proxmox-auth.ts (Token-Kopfzeilen PVE/PBS), proxmox-client.service.ts
(proxmoxGet, ausschliesslich lesend, Dispatcher je Aufruf aus
tlsRejectUnauthorized, nie global)
- proxmox.service.ts: Server anlegen (Geheimnis verschluesselt,
select ohne Geheimnisfelder), Serverliste, PVE-Abfrage mit
nachsichtiger Grundauswertung (Knoten/Gaeste)
- Controller/Modul/Seed nach Domaincheck-Vorbild, Kategorie
"infrastructure", @UseModule('proxmox') + @Roles auf Schreibwegen
- Modulseite (duenne Liste) + proxmox-api.ts + Registrierung in
MODULE_REGISTRY
- Zugriffsklassifikation nachgezogen (rls-access-inventory.spec.ts gruen)
Tore: api 1247/1247 (>=1240), web 693/693, type-check 4/4, lint 5/5,
Biome apps/web 53 Warnungen (unveraendert).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
-- 260923-dhh — Proxmox-Modul (PVE/PBS/PMG), nur beobachten (D-01).
|
||||
--
|
||||
-- Zweck: zwei neue Tabellen fuer das Proxmox-Modul. `ProxmoxServer` traegt
|
||||
-- die vom Administrator eingetragenen Server (Name, Typ, Adresse, Zugang,
|
||||
-- verschluesselt) — mehrere Zeilen je Mandant, Vorbild `CalendarSource`,
|
||||
-- NICHT `DkvModuleConfig` (Singleton je Mandant). `ProxmoxServerStatus` ist
|
||||
-- das Zwischenlager (D-05): der Hintergrunddienst (Aufgabe 4) beschreibt
|
||||
-- diese Zeile, die Modulseite liest ausschliesslich daraus.
|
||||
--
|
||||
-- Von Hand geschrieben (Vorbild 20260923120000_dashboard_tabs), von Hand
|
||||
-- gepflegter Kopfkommentar Pflicht bei jeder RLS-Migration in diesem Projekt.
|
||||
--
|
||||
-- Zeilenschutz (D-08, Pflicht — sonst schlaegt rls-coverage.spec.ts fehl):
|
||||
-- beide Tabellen tragen `tenantId` und `tenant_isolation_policy` OHNE
|
||||
-- Benutzerdimension (`USING ("tenantId" = current_tenant_id())`, Form aus
|
||||
-- `DkvModuleConfig`, Migration 20260909140000) — Proxmox-Server sind
|
||||
-- Verwaltungsdaten des Mandanten, nicht persoenliche Daten eines einzelnen
|
||||
-- Benutzers.
|
||||
--
|
||||
-- Zusaetzlich NUR auf "ProxmoxServer" eine `system_read_policy` (Form aus
|
||||
-- 20260914120000_rls_system_context_read): der Hintergrunddienst aus
|
||||
-- Aufgabe 4 muss beim Start ueber `forSystem()` die aktiven Server ALLER
|
||||
-- Mandanten sehen, um je Mandant einen eigenen Cron-Auftrag zu registrieren
|
||||
-- (Muster DKV-/Tender-Planer). "ProxmoxServerStatus" bekommt diese Regel
|
||||
-- BEWUSST NICHT — geschrieben wird dort ausschliesslich je Zeile
|
||||
-- mandantengebunden (`forTenant(prisma, tenantId)`), ein Systemlesezugriff
|
||||
-- auf das Zwischenlager hat keinen Aufrufer.
|
||||
--
|
||||
-- Rechte fuer die Anwendungsrolle tessera_app kommen automatisch ueber
|
||||
-- ALTER DEFAULT PRIVILEGES aus 20260909130000_rls_app_role — hier nichts zu
|
||||
-- tun.
|
||||
--
|
||||
-- WICHTIG: wie alle bisherigen RLS-Migrationen wirken diese Regeln erst,
|
||||
-- wenn die Anwendung als Rolle ohne Umgehungsrecht verbindet (Schalter
|
||||
-- heute AUS, siehe docs/mandantentrennung-datenbankrolle.md).
|
||||
|
||||
-- 1) ProxmoxServer
|
||||
CREATE TABLE "ProxmoxServer" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"productType" TEXT NOT NULL,
|
||||
"baseUrl" TEXT NOT NULL,
|
||||
"authMethod" TEXT NOT NULL,
|
||||
"tokenId" TEXT,
|
||||
"encryptedTokenSecret" TEXT,
|
||||
"username" TEXT,
|
||||
"encryptedPassword" TEXT,
|
||||
"tlsRejectUnauthorized" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"pollIntervalMin" INTEGER NOT NULL DEFAULT 5,
|
||||
"position" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProxmoxServer_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "ProxmoxServer_tenantId_idx" ON "ProxmoxServer"("tenantId");
|
||||
|
||||
ALTER TABLE "ProxmoxServer" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "ProxmoxServer" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "ProxmoxServer"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
CREATE POLICY system_read_policy ON "ProxmoxServer"
|
||||
FOR SELECT USING (is_system_context());
|
||||
|
||||
-- 2) ProxmoxServerStatus — Zwischenlager, 1:1 je Server, Loeschweitergabe.
|
||||
CREATE TABLE "ProxmoxServerStatus" (
|
||||
"id" TEXT NOT NULL,
|
||||
"serverId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"lastPolledAt" TIMESTAMP(3),
|
||||
"lastOkAt" TIMESTAMP(3),
|
||||
"reachable" BOOLEAN NOT NULL DEFAULT false,
|
||||
"errorKind" TEXT,
|
||||
"errorDetail" TEXT,
|
||||
"metrics" JSONB,
|
||||
"rawSample" JSONB,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProxmoxServerStatus_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProxmoxServerStatus_serverId_key" ON "ProxmoxServerStatus"("serverId");
|
||||
CREATE INDEX "ProxmoxServerStatus_tenantId_idx" ON "ProxmoxServerStatus"("tenantId");
|
||||
|
||||
ALTER TABLE "ProxmoxServerStatus" ADD CONSTRAINT "ProxmoxServerStatus_serverId_fkey"
|
||||
FOREIGN KEY ("serverId") REFERENCES "ProxmoxServer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "ProxmoxServerStatus" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "ProxmoxServerStatus" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "ProxmoxServerStatus"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
@@ -651,3 +651,54 @@ model TenderRssFeedSource {
|
||||
@@unique([userId, url])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// Quick-Auftrag 260923-dhh — Proxmox-Modul (PVE/PBS/PMG), nur beobachten (D-01).
|
||||
//
|
||||
// Vorbild ist `CalendarSource` (mehrere verschluesselte Fremdsystem-Zugaenge
|
||||
// je Mandant), NICHT `DkvModuleConfig` (Singleton je Mandant): ein Mandant
|
||||
// traegt hier beliebig viele Server ein. `authMethod` waehlt zwischen einem
|
||||
// API-Token (`tokenId`/`encryptedTokenSecret`) und Benutzer/Passwort
|
||||
// (`username`/`encryptedPassword`); PMG kennt laut Recherche nur Letzteres
|
||||
// (DTO lehnt Token bei PMG serverseitig ab, D-03). `tlsRejectUnauthorized`
|
||||
// ist woertlich der Feldname aus `LdapConfig` — Voreinstellung "pruefen",
|
||||
// pro Zeile umschaltbar, nie global (D-04).
|
||||
model ProxmoxServer {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
name String
|
||||
productType String // 'pve' | 'pbs' | 'pmg'
|
||||
baseUrl String
|
||||
authMethod String // 'token' | 'password'
|
||||
tokenId String?
|
||||
encryptedTokenSecret String? // AES-256-GCM ciphertext (iv:authTag:ciphertext hex), wie CalendarSource.encryptedPassword
|
||||
username String?
|
||||
encryptedPassword String? // AES-256-GCM ciphertext (iv:authTag:ciphertext hex)
|
||||
tlsRejectUnauthorized Boolean @default(true)
|
||||
isActive Boolean @default(true)
|
||||
pollIntervalMin Int @default(5)
|
||||
position Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
status ProxmoxServerStatus?
|
||||
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// Zwischenlager (D-05): der Hintergrunddienst (Aufgabe 4) beschreibt diese
|
||||
// Zeile, die Modulseite liest ausschliesslich daraus — nie live bei Proxmox.
|
||||
model ProxmoxServerStatus {
|
||||
id String @id @default(uuid())
|
||||
serverId String @unique
|
||||
server ProxmoxServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
||||
tenantId String
|
||||
lastPolledAt DateTime?
|
||||
lastOkAt DateTime?
|
||||
reachable Boolean @default(false)
|
||||
errorKind String?
|
||||
errorDetail String?
|
||||
metrics Json?
|
||||
rawSample Json?
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user