Merge branch 'phase/030-gitea-abgleich-2'
This commit is contained in:
@@ -1,19 +1,21 @@
|
|||||||
# #030 Issues nach Gitea übertragen
|
# #030 Issues nach Gitea übertragen
|
||||||
**Status:** offen
|
**Status:** erledigt
|
||||||
**Meilenstein:** v0.1.0
|
**Meilenstein:** v0.1.0
|
||||||
**Labels:** chore
|
**Labels:** chore
|
||||||
**Geschätzt:** 1 h **Tatsächlich:** –
|
**Geschätzt:** 1 h **Tatsächlich:** 0 h 10 min
|
||||||
|
|
||||||
## Ziel
|
## Ziel
|
||||||
Issue-Dateien, Labels und Meilensteine in Gitea anlegen (wartet auf Zugangsdaten).
|
Issue-Dateien, Labels und Meilensteine in Gitea anlegen (wartet auf Zugangsdaten).
|
||||||
|
|
||||||
## Aufgaben
|
## Aufgaben
|
||||||
- [ ] `scripts/gitea-issues.ts`
|
- [x] `scripts/gitea-issues.ts`
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
## Akzeptanzkriterien
|
||||||
- [ ] wird zu Beginn der Phase verfeinert
|
- [x] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
## Abhängigkeiten
|
## Abhängigkeiten
|
||||||
–
|
–
|
||||||
|
|
||||||
## Notizen / Entscheidungen
|
## 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.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
+18
-8
@@ -76,14 +76,15 @@ class Gitea {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly basis: string,
|
private readonly basis: string,
|
||||||
private readonly repo: 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<T>(pfad: string, optionen: RequestInit = {}): Promise<T> {
|
private async anfrage<T>(pfad: string, optionen: RequestInit = {}): Promise<T> {
|
||||||
const antwort = await fetch(`${this.basis}/api/v1/repos/${this.repo}${pfad}`, {
|
const antwort = await fetch(`${this.basis}/api/v1/repos/${this.repo}${pfad}`, {
|
||||||
...optionen,
|
...optionen,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `token ${this.token}`,
|
Authorization: this.autorisierung,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...optionen.headers,
|
...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`).
|
* Holt die Zugangsdaten aus Git (Schlüsselbund oder `~/.git-credentials`), damit sie nicht
|
||||||
* So genügt ein einziger Token für Push und API – er muss nicht zusätzlich in der .env stehen.
|
* 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;
|
const host = new URL(basis).host;
|
||||||
|
|
||||||
// Erst die eingerichteten Helfer (z. B. Schlüsselbund), dann zusätzlich die Datei
|
// 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"],
|
stdio: ["pipe", "pipe", "ignore"],
|
||||||
});
|
});
|
||||||
const passwort = /^password=(.*)$/m.exec(antwort);
|
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 {
|
} catch {
|
||||||
// Nächste Möglichkeit versuchen.
|
// Nächste Möglichkeit versuchen.
|
||||||
}
|
}
|
||||||
@@ -186,8 +190,14 @@ async function main() {
|
|||||||
const remote = ausRemote();
|
const remote = ausRemote();
|
||||||
const basis = (process.env.GITEA_URL ?? remote?.basis ?? "").replace(/\/$/, "");
|
const basis = (process.env.GITEA_URL ?? remote?.basis ?? "").replace(/\/$/, "");
|
||||||
const repo = process.env.GITEA_REPO ?? remote?.repo ?? "";
|
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";
|
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 verzeichnis = path.join(process.cwd(), "docs", "issues");
|
||||||
const issues = await dateienLesen(verzeichnis);
|
const issues = await dateienLesen(verzeichnis);
|
||||||
@@ -215,7 +225,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Ziel: ${basis}/${repo} (Token aus: ${tokenQuelle})`);
|
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 vorhandeneLabels = new Map((await gitea.labels()).map((l) => [l.name.toLowerCase(), l.id]));
|
||||||
const benoetigteLabels = [...new Set(issues.flatMap((issue) => issue.labels))];
|
const benoetigteLabels = [...new Set(issues.flatMap((issue) => issue.labels))];
|
||||||
|
|||||||
Reference in New Issue
Block a user