feat(hosts): Schluessel auf dem Host anzeigen und ausrollen (#016)
Die Hostseite listet, welche Public und Private Keys sshkv dort verwaltet - direkt oder ueber eine Hostgruppe - mit Zustand als Punkt und Text. Ein Dry-Run je Host zeigt das Diff aller Benutzer dieses Hosts und rollt nach Bestaetigung aus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,12 +70,17 @@ export async function publicEntfernenAktion(
|
||||
}
|
||||
}
|
||||
|
||||
export async function trockenlaufAktion(schluesselId: string): Promise<VorgangsAntwort> {
|
||||
export type AusrollAuswahl = { readonly schluesselId: string } | { readonly hostId: string };
|
||||
|
||||
export async function trockenlaufAktion(auswahl: AusrollAuswahl): Promise<VorgangsAntwort> {
|
||||
try {
|
||||
const { benutzer } = await rechtErforderlich("publicKeyAusrollen");
|
||||
const id = z.string().min(1).max(64);
|
||||
return {
|
||||
vorgangId: await trockenlaufStarten(
|
||||
{ schluesselId: z.string().min(1).max(64).parse(schluesselId) },
|
||||
"schluesselId" in auswahl
|
||||
? { schluesselId: id.parse(auswahl.schluesselId) }
|
||||
: { hostId: id.parse(auswahl.hostId) },
|
||||
akteurVon(benutzer),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -20,3 +20,36 @@ export async function zielAuswahl() {
|
||||
}
|
||||
|
||||
export type ZielAuswahl = Awaited<ReturnType<typeof zielAuswahl>>;
|
||||
|
||||
/** Was sshkv auf einem Host verwaltet (#016): Public Keys direkt und ueber Gruppen. */
|
||||
export async function verwendungenAufHost(hostId: string) {
|
||||
const [oeffentlich, privat] = await Promise.all([
|
||||
prisma.publicVerwendung.findMany({
|
||||
where: {
|
||||
zustand: { not: "ENTFERNT" },
|
||||
OR: [{ hostId }, { hostgruppe: { mitglieder: { some: { hostId } } } }],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
zielbenutzer: true,
|
||||
optionen: true,
|
||||
zustand: true,
|
||||
hostgruppe: { select: { name: true } },
|
||||
schluessel: { select: { id: true, name: true, typ: true, fingerprint: true } },
|
||||
},
|
||||
orderBy: [{ zielbenutzer: "asc" }, { angelegtAm: "asc" }],
|
||||
}),
|
||||
prisma.privateVerwendung.findMany({
|
||||
where: { hostId, zustand: { not: "ENTFERNT" } },
|
||||
select: {
|
||||
id: true,
|
||||
zielbenutzer: true,
|
||||
pfad: true,
|
||||
zustand: true,
|
||||
schluessel: { select: { id: true, name: true, typ: true, fingerprint: true } },
|
||||
},
|
||||
orderBy: [{ zielbenutzer: "asc" }, { pfad: "asc" }],
|
||||
}),
|
||||
]);
|
||||
return { oeffentlich, privat };
|
||||
}
|
||||
|
||||
@@ -100,11 +100,20 @@ export type Ziel = { readonly hostId: string; readonly zielbenutzer: string };
|
||||
export async function zieleVonVerwendungen(where: {
|
||||
schluesselId?: string;
|
||||
ids?: string[];
|
||||
hostId?: string;
|
||||
}): Promise<Ziel[]> {
|
||||
const verwendungen = await prisma.publicVerwendung.findMany({
|
||||
where: {
|
||||
...(where.schluesselId ? { schluesselId: where.schluesselId } : {}),
|
||||
...(where.ids ? { id: { in: where.ids } } : {}),
|
||||
...(where.hostId
|
||||
? {
|
||||
OR: [
|
||||
{ hostId: where.hostId },
|
||||
{ hostgruppe: { mitglieder: { some: { hostId: where.hostId } } } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
zustand: { not: "ENTFERNT" },
|
||||
},
|
||||
select: {
|
||||
@@ -117,9 +126,11 @@ export async function zieleVonVerwendungen(where: {
|
||||
});
|
||||
const ziele = new Map<string, Ziel>();
|
||||
for (const verwendung of verwendungen) {
|
||||
const hostIds = verwendung.hostId
|
||||
const alleHostIds = verwendung.hostId
|
||||
? [verwendung.hostId]
|
||||
: (verwendung.hostgruppe?.mitglieder.filter((m) => !m.host.archiviertAm).map((m) => m.hostId) ?? []);
|
||||
// Bei der Auswahl nach Host nur diesen Host, auch wenn die Verwendung an einer Gruppe haengt.
|
||||
const hostIds = where.hostId ? alleHostIds.filter((id) => id === where.hostId) : alleHostIds;
|
||||
for (const hostId of hostIds) {
|
||||
ziele.set(`${hostId}\u0000${verwendung.zielbenutzer}`, {
|
||||
hostId,
|
||||
|
||||
@@ -117,13 +117,14 @@ export async function publicVerwendungEntfernen(id: string, akteur: Akteur): Pro
|
||||
|
||||
/** Trockenlauf fuer die Ziele eines Schluessels oder fuer ausgewaehlte Verwendungen. */
|
||||
export async function trockenlaufStarten(
|
||||
auswahl: { schluesselId?: string; verwendungIds?: string[]; ziele?: Ziel[] },
|
||||
auswahl: { schluesselId?: string; hostId?: string; verwendungIds?: string[]; ziele?: Ziel[] },
|
||||
akteur: Akteur,
|
||||
): Promise<string> {
|
||||
const ziele =
|
||||
auswahl.ziele ??
|
||||
(await zieleVonVerwendungen({
|
||||
...(auswahl.schluesselId ? { schluesselId: auswahl.schluesselId } : {}),
|
||||
...(auswahl.hostId ? { hostId: auswahl.hostId } : {}),
|
||||
...(auswahl.verwendungIds ? { ids: auswahl.verwendungIds } : {}),
|
||||
}));
|
||||
if (ziele.length === 0) throw new VerwendungsFehler("Es gibt nichts auszurollen.");
|
||||
@@ -131,6 +132,7 @@ export async function trockenlaufStarten(
|
||||
art: "TROCKENLAUF",
|
||||
angefordertVonId: akteur.id,
|
||||
schluesselId: auswahl.schluesselId ?? null,
|
||||
hostId: auswahl.hostId ?? null,
|
||||
eingabe: { ziele },
|
||||
});
|
||||
}
|
||||
@@ -144,6 +146,7 @@ export async function ausrollenStarten(trockenlaufId: string, akteur: Akteur): P
|
||||
art: "AUSROLLEN",
|
||||
angefordertVonId: akteur.id,
|
||||
schluesselId: trockenlauf.schluesselId,
|
||||
hostId: trockenlauf.hostId,
|
||||
eingabe: { trockenlaufId },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user