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 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
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<T>(pfad: string, optionen: RequestInit = {}): Promise<T> {
|
||||
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))];
|
||||
|
||||
Reference in New Issue
Block a user