Merge branch 'phase/009-host-key-pruefung'
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "@/lib/validierung";
|
||||
import { bestaetigungFrisch, rechtErforderlich, RechteFehler } from "@/server/auth/sitzungspflicht";
|
||||
import { HostKeyEntscheidungFehler, hostKeyEntscheiden } from "@/server/services/hostkeys";
|
||||
import { vorgangEinreihen } from "@/server/vorgaenge/vorgaenge";
|
||||
|
||||
/** Aktionen rund um Host-Keys und Erreichbarkeit (#009). */
|
||||
|
||||
export type VorgangsAntwort = { readonly vorgangId?: string; readonly fehler?: string };
|
||||
export type EntscheidungsAntwort = {
|
||||
readonly fehler?: string;
|
||||
readonly bestaetigen?: string;
|
||||
readonly erledigt?: boolean;
|
||||
};
|
||||
|
||||
const hostIdSchema = z.string().min(1).max(64);
|
||||
|
||||
async function einreihen(
|
||||
art: "HOSTKEY_ABRUFEN" | "ERREICHBARKEIT",
|
||||
hostId: string,
|
||||
): Promise<VorgangsAntwort> {
|
||||
try {
|
||||
const { benutzer } = await rechtErforderlich("hostsVerwalten");
|
||||
const id = hostIdSchema.parse(hostId);
|
||||
return { vorgangId: await vorgangEinreihen({ art, angefordertVonId: benutzer.id, hostId: id }) };
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof RechteFehler) return { fehler: fehler.message };
|
||||
throw fehler;
|
||||
}
|
||||
}
|
||||
|
||||
export async function hostKeyAbrufenAktion(hostId: string): Promise<VorgangsAntwort> {
|
||||
return einreihen("HOSTKEY_ABRUFEN", hostId);
|
||||
}
|
||||
|
||||
export async function erreichbarkeitPruefenAktion(hostId: string): Promise<VorgangsAntwort> {
|
||||
return einreihen("ERREICHBARKEIT", hostId);
|
||||
}
|
||||
|
||||
const entscheidungSchema = z.object({
|
||||
hostId: hostIdSchema,
|
||||
hostSchluesselId: z.string().min(1).max(64),
|
||||
vertrauen: z.boolean(),
|
||||
ersetzen: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export async function hostKeyEntscheidenAktion(
|
||||
eingabe: z.input<typeof entscheidungSchema>,
|
||||
): Promise<EntscheidungsAntwort> {
|
||||
try {
|
||||
const { benutzer } = await rechtErforderlich("hostKeyEntscheiden");
|
||||
const daten = entscheidungSchema.parse(eingabe);
|
||||
// Einen vertrauten Key zu ersetzen ist der Moment eines moeglichen Angriffs – nur mit
|
||||
// frischer Bestaetigung der eigenen Anmeldung.
|
||||
if (daten.ersetzen && !bestaetigungFrisch(benutzer.bestaetigtAm)) {
|
||||
return { bestaetigen: `/bestaetigen?ziel=${encodeURIComponent(`/hosts/${daten.hostId}`)}` };
|
||||
}
|
||||
await hostKeyEntscheiden(daten, { id: benutzer.id, name: benutzer.name });
|
||||
revalidatePath(`/hosts/${daten.hostId}`);
|
||||
return { erledigt: true };
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof RechteFehler || fehler instanceof HostKeyEntscheidungFehler)
|
||||
return { fehler: fehler.message };
|
||||
throw fehler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { prisma } from "@/lib/db";
|
||||
import { protokollieren } from "@/server/services/protokoll";
|
||||
|
||||
/**
|
||||
* Entscheidung eines Menschen ueber einen Host-Key (#009, TOFU).
|
||||
*
|
||||
* Einen vertrauten Key zu ersetzen ist der Moment, in dem ein Angreifer mit einer
|
||||
* abgefangenen Verbindung gewinnen wuerde. Deshalb verlangt das ein ausdrueckliches
|
||||
* `ersetzen` (die Oberflaeche fragt nach und verlangt eine frische Bestaetigung).
|
||||
*/
|
||||
export class HostKeyEntscheidungFehler extends Error {
|
||||
constructor(nachricht: string) {
|
||||
super(nachricht);
|
||||
this.name = "HostKeyEntscheidungFehler";
|
||||
}
|
||||
}
|
||||
|
||||
export type Entscheidung = {
|
||||
readonly hostSchluesselId: string;
|
||||
readonly vertrauen: boolean;
|
||||
readonly ersetzen?: boolean;
|
||||
};
|
||||
|
||||
export async function hostKeyEntscheiden(
|
||||
eingabe: Entscheidung,
|
||||
akteur: { readonly id: string; readonly name: string },
|
||||
): Promise<void> {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const key = await tx.hostSchluessel.findUnique({
|
||||
where: { id: eingabe.hostSchluesselId },
|
||||
include: { host: true },
|
||||
});
|
||||
if (!key) throw new HostKeyEntscheidungFehler("Diesen Host-Key gibt es nicht.");
|
||||
if (key.status !== "AUSSTEHEND") {
|
||||
throw new HostKeyEntscheidungFehler("Über diesen Host-Key wurde bereits entschieden.");
|
||||
}
|
||||
|
||||
if (!eingabe.vertrauen) {
|
||||
await tx.hostSchluessel.update({
|
||||
where: { id: key.id },
|
||||
data: { status: "ABGELEHNT", entschiedenVon: akteur.name, entschiedenAm: new Date() },
|
||||
});
|
||||
await protokollieren(
|
||||
{
|
||||
akteur,
|
||||
aktion: "hostkey.abgelehnt",
|
||||
ergebnis: "ERFOLG",
|
||||
hostName: key.host.name,
|
||||
fingerprint: key.fingerprint,
|
||||
},
|
||||
tx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const bisher = await tx.hostSchluessel.findFirst({
|
||||
where: { hostId: key.hostId, algorithmus: key.algorithmus, status: "VERTRAUT" },
|
||||
});
|
||||
if (bisher && !eingabe.ersetzen) {
|
||||
throw new HostKeyEntscheidungFehler(
|
||||
`Für ${key.algorithmus} ist bereits ein anderer Host-Key vertraut. Ersetzen muss ausdrücklich bestätigt werden.`,
|
||||
);
|
||||
}
|
||||
if (bisher) {
|
||||
await tx.hostSchluessel.update({ where: { id: bisher.id }, data: { status: "ERSETZT" } });
|
||||
}
|
||||
await tx.hostSchluessel.update({
|
||||
where: { id: key.id },
|
||||
data: { status: "VERTRAUT", entschiedenVon: akteur.name, entschiedenAm: new Date() },
|
||||
});
|
||||
// Eine offene Abweichung zu genau diesem Key ist damit erledigt.
|
||||
await tx.abweichung.updateMany({
|
||||
where: { hostId: key.hostId, art: "HOSTKEY_GEAENDERT", fingerprint: key.fingerprint, behobenAm: null },
|
||||
data: { behobenAm: new Date() },
|
||||
});
|
||||
await protokollieren(
|
||||
{
|
||||
akteur,
|
||||
aktion: bisher ? "hostkey.ersetzt" : "hostkey.bestaetigt",
|
||||
ergebnis: "ERFOLG",
|
||||
hostName: key.host.name,
|
||||
fingerprint: key.fingerprint,
|
||||
details: bisher
|
||||
? { bisher: bisher.fingerprint, algorithmus: key.algorithmus }
|
||||
: { algorithmus: key.algorithmus },
|
||||
},
|
||||
tx,
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user