Skip to content

0020 — Passwortschutz vor dem Launch als Teil der erzeugten .htaccess ​

Status: angenommen · Milestone: Betrieb

Kontext ​

Eine Kundenwebsite geht oft vor dem eigentlichen Launch schon auf das Zielsystem — zur Abnahme, für einen Kunden, der sich den Stand ansehen soll, oder weil das Deployment selbst schon geprobt wird, bevor eine Domain öffentlich beworben ist. Bis dahin soll die Auslieferung erreichbar, aber nicht ohne Weiteres einsehbar sein. Das erste Kundenprojekt hat genau das gebraucht und selbst gebaut; diese Entscheidung holt es in den Starter.

HTTP Basic Auth über Apache ist dafür das naheliegende Werkzeug: kein JavaScript, keine eigene Seite, ein Login-Dialog des Browsers vor jeder Anfrage. Die Frage ist, wie das in einen Starter passt, dessen .htaccess bereits ein erzeugtes Artefakt ist (ADR 0012) und dessen Konfiguration öffentlich ist — project.config.ts landet im ausgelieferten HTML.

Entscheidung ​

Nur deployment.apache.basicAuth (Realm-Text und der absolute Dokumentenstamm des Zielsystems) steht in project.config.ts. Benutzername und Passwort stehen dort nie — sie kommen aus den Umgebungsvariablen KT_BASIC_AUTH_USER und KT_BASIC_AUTH_PASSWORD, gelesen zur Bauzeit direkt aus process.env, genau wie PUBLIC_TURNSTILE_SITE_KEY an anderer Stelle.

Der Dokumentenstamm ist Teil der Konfiguration, kein Geheimnis. AuthUserFile verlangt einen Dateisystempfad; relativ wäre er gegen die ServerRoot von Apache aufgelöst, nicht gegen den Dokumentenstamm der Domain — auf einem geteilten Server nicht vorhersagbar. Der Pfad allein gewährt keinen Zugriff, deshalb ist er in deployment.apache.basicAuth.documentRoot genauso öffentlich wie canonicalHost. Er muss mit der DEPLOY_PATH-Variable der Deployment-Umgebung übereinstimmen (ADR 0014) — das prüft niemand automatisch, siehe Konsequenzen.

Fehlen die Variablen in production, bricht der Build ab. checkEnvironment lehnt eine production-Ausgabe mit aktivem basicAuth ohne Zugangsdaten ab — dieselbe Stelle und derselbe Grund wie beim fehlenden Turnstile-Sitekey: Eine Auslieferung, die einen Schutz zusagt, den sie nicht hat, ist die gefährlichere Richtung als ein abgebrochener Build.

Außerhalb von production entsteht die Ausgabe ohne den Block, wenn die Variablen fehlen — statt abzubrechen. Das betrifft jeden lokalen Build und pnpm verify. Ohne diese Ausnahme bräuchte jede Entwicklungsumgebung und jeder CI-Lauf eines Pull Requests die Zugangsdaten, nur damit der Build durchläuft — für eine Funktion, die dort keine Wirkung haben muss. Ein logger.warn macht den fehlenden Schutz beim Bauen sichtbar.

Gehasht wird mit dem {SHA}-Schema klassischer htpasswd-Dateien (Base64 von SHA1, mit dem Node-eigenen crypto-Modul). Kein bcrypt: Node bringt dafür kein eingebautes Modul mit, und eine zusätzliche Abhängigkeit ist für einen Vorschau-Schutz — keine Anmeldung für empfindliche Daten — nicht zu rechtfertigen. Apaches mod_authn_file liest das Format seit jeher ohne Zusatzmodul.

Die .htpasswd entsteht als zweite Datei neben der .htaccess, vom selben Integrations-Hook geschrieben. Sie bekommt keine eigene Sperrregel: Die Regel „alles mit einem führenden Punkt ist gesperrt" (ADR 0012) deckt sie ab.

Der Auth-Block steht in drei verschachtelten <IfModule> — mod_auth_basic.c, mod_authn_file.c, mod_authz_user.c. Die drei Direktiven gehören zu drei Modulen; ein Server, dem eines fehlt, soll einen zuordenbaren Fehler zeigen, statt die ganze Auslieferung mit einem 500 zu beantworten (Regel 16).

Der Init fragt danach. pnpm starter:init bietet den Schutz für Mittwald-Projekte an und übernimmt den Dokumentenstamm; der Abschlussbericht nennt die beiden Secrets.

Begründung ​

Warum die Prüfung nur auf production gilt und nicht auf preview gleich mit. Das ist dieselbe Grenze, die checkEnvironment bereits für den Turnstile-Sitekey zieht — eine einzige, etablierte Unterscheidung statt einer neuen. Wer den Schutz auch für ein preview-Deployment erzwingen will, setzt die Variablen dort ebenfalls.

Warum nicht astro:env mit access: 'secret'. Dieser Mechanismus ist für Geheimnisse gedacht, die eine serverseitig gerenderte Route zur Laufzeit liest. Bei einem rein statischen Build gibt es diese Laufzeit nicht — apacheIntegration läuft einmalig als Node-Prozess beim Bauen, genau dort, wo deployPlanIntegration bereits process.env.PUBLIC_ENVIRONMENT direkt liest.

Verworfene Alternativen ​

Benutzername und Passwort in project.config.ts. Unmöglich per Definition — die Datei ist öffentlich.

Ein absoluter Pfad, der aus DEPLOY_PATH der GitHub-Umgebung in die .htaccess injiziert wird. Dann reproduzierte pnpm run build allein die ausgelieferte Datei nicht mehr — ein Build außerhalb des Deployment-Workflows hätte eine andere .htaccess als der, der ausliefert. Genau das soll die Qualitätskette verhindern (ADR 0014).

bcrypt über eine neue Abhängigkeit. Sicherer, aber unbegründet für einen Vorschau-Schutz ohne empfindliche Daten dahinter.

Konsequenzen ​

  • deployment.apache.basicAuth.documentRoot und DEPLOY_PATH der GitHub-Umgebung müssen von Hand übereinstimmen. Ein Unterschied fällt erst auf, wenn Apache mit „Internal Server Error" auf jede Anfrage antwortet, weil AuthUserFile ins Leere zeigt.
  • KT_BASIC_AUTH_USER/KT_BASIC_AUTH_PASSWORD liegen als Secrets in jeder GitHub-Umgebung, die produktiv baut — dieselbe Stelle wie DEPLOY_SSH_KEY. deploy.yml reicht sie nur an den Build der Auslieferung, nicht an die Qualitätskette.
  • Nach dem Launch entfernt das Projekt deployment.apache.basicAuth aus project.config.ts — die .htpasswd verschwindet beim nächsten Build, weil sie nur entsteht, wenn beides, Konfiguration und Zugangsdaten, vorhanden ist.
  • {SHA} ist kein Schutz gegen ernsthafte Angriffe auf die Passwortdatei selbst — dafür ist er nicht gedacht. Die .htpasswd bleibt durch die Sperre aller Punktdateien vor direktem HTTP-Zugriff geschützt.