From 87b733cc9fecda2c3787d085ce54161e9189d333 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Mon, 12 Oct 2026 17:13:15 +0200 Subject: [PATCH] chore(gitea): Issues nach Gitea, Basic-Auth fuer Konto-Passwort (#030) In den Git-Zugangsdaten steht das Konto-Passwort, kein API-Token. Gitea nimmt es nur per Basic-Auth an; das Skript erkennt das jetzt selbst. Alle 30 Issues mit Labels und Meilensteinen uebertragen. Neu: #031 Rotation des Deployment-Keys und #032 Nacharbeiten fuer 1.0.0. Co-Authored-By: Claude Opus 5.5 --- docs/issues/030-gitea-abgleich.md | 10 +++++---- docs/issues/031-deploymentkey-rotation.md | 25 ++++++++++++++++++++++ docs/issues/032-nacharbeiten.md | 22 +++++++++++++++++++ scripts/gitea-issues.ts | 26 ++++++++++++++++------- 4 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 docs/issues/031-deploymentkey-rotation.md create mode 100644 docs/issues/032-nacharbeiten.md diff --git a/docs/issues/030-gitea-abgleich.md b/docs/issues/030-gitea-abgleich.md index 73bcf2d..5489721 100644 --- a/docs/issues/030-gitea-abgleich.md +++ b/docs/issues/030-gitea-abgleich.md @@ -1,19 +1,21 @@ # #030 Issues nach Gitea übertragen -**Status:** offen +**Status:** erledigt **Meilenstein:** v0.1.0 **Labels:** chore -**Geschätzt:** 1 h **Tatsächlich:** – +**Geschätzt:** 1 h **Tatsächlich:** 0 h 10 min ## Ziel Issue-Dateien, Labels und Meilensteine in Gitea anlegen (wartet auf Zugangsdaten). ## Aufgaben -- [ ] `scripts/gitea-issues.ts` +- [x] `scripts/gitea-issues.ts` ## Akzeptanzkriterien -- [ ] wird zu Beginn der Phase verfeinert +- [x] wird zu Beginn der Phase verfeinert ## Abhängigkeiten – ## Notizen / Entscheidungen +- Repo `florian.wilde/sshkeyvault` auf git.wilde-server.de (von Florian angelegt), `main` und Tags v0.1.0–v0.5.0 gepusht. +- `pnpm issues:sync` liest Adresse und Repo aus dem Git-Remote und den Token aus den Git-Zugangsdaten; die Issue-Dateien bleiben die Quelle der Wahrheit. diff --git a/docs/issues/031-deploymentkey-rotation.md b/docs/issues/031-deploymentkey-rotation.md new file mode 100644 index 0000000..598d20a --- /dev/null +++ b/docs/issues/031-deploymentkey-rotation.md @@ -0,0 +1,25 @@ +# #031 Rotation des Deployment-Keys +**Status:** offen +**Meilenstein:** v1.0.0 +**Labels:** sicherheit, backend +**Geschätzt:** 3 h **Tatsächlich:** – + +## Ziel +Den Deployment-Key der Instanz geführt tauschen – regelmäßig oder im Notfall (sshkv kompromittiert, siehe `docs/betrieb/sicherheit.md`). + +## Aufgaben +- [ ] Neuen Deployment-Key erzeugen, ohne den alten abzulösen (zweiter Instanz-Key im Übergang) +- [ ] Auf jedem eingerichteten Host den neuen Key neben den alten in den verwalteten Block des Anmeldebenutzers schreiben (Dry-Run, Ausrollen) +- [ ] Verbindung mit dem neuen Key je Host prüfen +- [ ] Erst wenn alle Hosts den neuen Key akzeptieren: alten aus allen Blöcken entfernen, alten Key verwerfen +- [ ] Nicht erreichbare Hosts sichtbar machen; Abschluss erst, wenn alle umgestellt sind (oder bewusst übergangen) +- [ ] Nur Admin mit frischer Bestätigung, alles protokolliert + +## Akzeptanzkriterien +- [ ] Integrationstest: nach der Rotation meldet sich sshkv nur noch mit dem neuen Key an, der alte steht auf keinem Host mehr +- [ ] Aussperrschutz: zu keinem Zeitpunkt steht ein Host ohne funktionierenden Deployment-Key da + +## Abhängigkeiten +#016, #021 + +## Notizen / Entscheidungen diff --git a/docs/issues/032-nacharbeiten.md b/docs/issues/032-nacharbeiten.md new file mode 100644 index 0000000..7995d03 --- /dev/null +++ b/docs/issues/032-nacharbeiten.md @@ -0,0 +1,22 @@ +# #032 Nacharbeiten aus Phase 4/5 +**Status:** offen +**Meilenstein:** v1.0.0 +**Labels:** test, chore +**Geschätzt:** 2 h **Tatsächlich:** – + +## Ziel +Kleinere offene Punkte aus den Berichten der Phasen 4 und 5 erledigen. + +## Aufgaben +- [ ] Massenwiderruf im Browser testen (E2E mit frischer Bestätigung über den Mock-OIDC) +- [ ] Laufende Rotation abbrechen können (Nachfolger widerrufen, Vorgänger wieder aktiv) +- [ ] Protokoll-IDs ohne Zweierschritte (Spalten-Default und Trigger ziehen beide `nextval`) +- [ ] „Zum Dry-Run“ aus der Zuweisungsmatrix direkt zur Dry-Run-Karte springen (Anker) + +## Akzeptanzkriterien +- [ ] Alle Suiten grün + +## Abhängigkeiten +#023, #024, #025, #027 + +## Notizen / Entscheidungen diff --git a/scripts/gitea-issues.ts b/scripts/gitea-issues.ts index f2b1de8..44510bc 100644 --- a/scripts/gitea-issues.ts +++ b/scripts/gitea-issues.ts @@ -76,14 +76,15 @@ class Gitea { constructor( private readonly basis: string, private readonly repo: string, - private readonly token: string, + /** Fertiger Wert fuer den Authorization-Header (`token …` oder `Basic …`). */ + private readonly autorisierung: string, ) {} private async anfrage(pfad: string, optionen: RequestInit = {}): Promise { const antwort = await fetch(`${this.basis}/api/v1/repos/${this.repo}${pfad}`, { ...optionen, headers: { - Authorization: `token ${this.token}`, + Authorization: this.autorisierung, "Content-Type": "application/json", ...optionen.headers, }, @@ -149,10 +150,12 @@ function ausRemote(): { basis: string; repo: string } | null { } /** - * Holt den Token aus den Git-Zugangsdaten (Schlüsselbund oder `~/.git-credentials`). - * So genügt ein einziger Token für Push und API – er muss nicht zusätzlich in der .env stehen. + * Holt die Zugangsdaten aus Git (Schlüsselbund oder `~/.git-credentials`), damit sie nicht + * zusätzlich in der .env stehen müssen. Hinterlegt ist dort ein API-Token oder – wie bei + * sshkeyvault – das Konto-Passwort; Gitea nimmt beides an, das Passwort aber nur per Basic-Auth + * (#030). */ -function tokenAusGitCredentials(basis: string): string | null { +function zugangAusGitCredentials(basis: string): { benutzer: string; geheimnis: string } | null { const host = new URL(basis).host; // Erst die eingerichteten Helfer (z. B. Schlüsselbund), dann zusätzlich die Datei @@ -167,7 +170,8 @@ function tokenAusGitCredentials(basis: string): string | null { stdio: ["pipe", "pipe", "ignore"], }); const passwort = /^password=(.*)$/m.exec(antwort); - if (passwort?.[1]) return passwort[1]; + const benutzer = /^username=(.*)$/m.exec(antwort); + if (passwort?.[1]) return { benutzer: benutzer?.[1] ?? "", geheimnis: passwort[1] }; } catch { // Nächste Möglichkeit versuchen. } @@ -186,8 +190,14 @@ async function main() { const remote = ausRemote(); const basis = (process.env.GITEA_URL ?? remote?.basis ?? "").replace(/\/$/, ""); const repo = process.env.GITEA_REPO ?? remote?.repo ?? ""; - const token = process.env.GITEA_TOKEN || (basis ? (tokenAusGitCredentials(basis) ?? "") : ""); + const zugang = process.env.GITEA_TOKEN ? null : basis ? zugangAusGitCredentials(basis) : null; + const token = process.env.GITEA_TOKEN || zugang?.geheimnis || ""; const tokenQuelle = process.env.GITEA_TOKEN ? "Umgebungsvariable" : "Git-Zugangsdaten"; + // Ein Gitea-Token sind 40 Hex-Zeichen; alles andere ist ein Passwort und geht per Basic-Auth. + const autorisierung = + process.env.GITEA_TOKEN || /^[0-9a-f]{40}$/.test(token) || !zugang?.benutzer + ? `token ${token}` + : `Basic ${Buffer.from(`${zugang.benutzer}:${token}`).toString("base64")}`; const verzeichnis = path.join(process.cwd(), "docs", "issues"); const issues = await dateienLesen(verzeichnis); @@ -215,7 +225,7 @@ async function main() { } console.log(`Ziel: ${basis}/${repo} (Token aus: ${tokenQuelle})`); - const gitea = new Gitea(basis, repo, token); + const gitea = new Gitea(basis, repo, autorisierung); const vorhandeneLabels = new Map((await gitea.labels()).map((l) => [l.name.toLowerCase(), l.id])); const benoetigteLabels = [...new Set(issues.flatMap((issue) => issue.labels))];