"use server"; import { ZielkontoFehler } from "@/lib/ssh/zielkonto"; import { z } from "@/lib/validierung"; import { akteurVon } from "@/server/actions/formular"; import { BestaetigungFehler, RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht"; import { HostFehler } from "@/server/services/hosts"; import { erzeugenAnfordern } from "@/server/services/schluessel"; import { VerwendungsFehler, privateVerwendungAnlegen, publicVerwendungAnlegen, } from "@/server/services/verwendungen"; import { zielkontoAnfordern } from "@/server/services/zielkonten"; /** * Schritte des Host-zu-Host-Assistenten (#020). Jeder Schritt ist eine eigene Aktion, die * Oberflaeche verkettet sie und wartet dazwischen auf den Worker. So bleibt jede Aktion * duenn, und jeder Schritt hat seine eigene Rechtepruefung. */ export type SchrittAntwort = { readonly vorgangId?: string; readonly privatVerwendungId?: string; readonly fehler?: string; /** Kritischer Schritt ohne frische Bestaetigung: dorthin, dann weiter. */ readonly bestaetigen?: boolean; }; function alsAntwort(fehler: unknown): SchrittAntwort { if (fehler instanceof BestaetigungFehler) return { bestaetigen: true, fehler: fehler.message }; if ( fehler instanceof RechteFehler || fehler instanceof VerwendungsFehler || fehler instanceof ZielkontoFehler || fehler instanceof HostFehler ) { return { fehler: fehler.message }; } throw fehler; } const id = z.string().min(1).max(64); export async function assistentErzeugenAktion(eingabe: { name: string; vorlageId: string | null; ablaufAm: string | null; }): Promise { try { const { benutzer } = await rechtErforderlich("privateKeyAusrollen"); return { vorgangId: await erzeugenAnfordern( { name: eingabe.name, vorlageId: eingabe.vorlageId, ablaufAm: eingabe.ablaufAm, tags: ["host-zu-host"], }, undefined, akteurVon(benutzer), ), }; } catch (fehler) { return alsAntwort(fehler); } } export async function assistentZielkontoAktion(eingabe: { hostId: string; benutzer: string; modus: "KEINER" | "BEFEHLE" | "VOLL"; befehle: string[]; }): Promise { try { // Ein neuer Benutzer mit sudo ist so viel wert wie root: Admin mit frischer Bestaetigung. const { benutzer } = await rechtErforderlich("zielkontenVerwalten"); return { vorgangId: await zielkontoAnfordern({ ...eingabe, hostId: id.parse(eingabe.hostId) }, benutzer), }; } catch (fehler) { return alsAntwort(fehler); } } export async function assistentZuweisenAktion(eingabe: { schluesselId: string; quelle: { hostId: string; benutzer: string; pfad: string }; ziel: { hostId: string; benutzer: string; optionen: string | null }; ssh: { alias: string; hostName: string; port: number; knownHosts: boolean }; }): Promise { try { const { benutzer } = await rechtErforderlich("privateKeyAusrollen"); const akteur = akteurVon(benutzer); const privatVerwendungId = await privateVerwendungAnlegen( { schluesselId: id.parse(eingabe.schluesselId), hostId: id.parse(eingabe.quelle.hostId), zielbenutzer: eingabe.quelle.benutzer, pfad: eingabe.quelle.pfad, sshConfig: { alias: eingabe.ssh.alias, hostName: eingabe.ssh.hostName, benutzer: eingabe.ziel.benutzer, port: eingabe.ssh.port, }, knownHostsFuer: eingabe.ssh.knownHosts ? [id.parse(eingabe.ziel.hostId)] : [], }, akteur, ); await publicVerwendungAnlegen( { schluesselId: eingabe.schluesselId, hostId: id.parse(eingabe.ziel.hostId), zielbenutzer: eingabe.ziel.benutzer, optionen: eingabe.ziel.optionen, }, akteur, ); return { privatVerwendungId }; } catch (fehler) { return alsAntwort(fehler); } }