Vier Befunde aus einer externen Durchsicht der 2.4-Integration.
1. Die .htpasswd war eine Klartext-Kundenliste
Das htpasswd-Format hasht nur die Passwortspalte. Benutzername UND Passwort
waren der Lizenzschluessel - der Schluessel stand also im Klartext direkt
neben seinem eigenen bcrypt-Hash, und der Hash war Dekoration. Geschuetzt
hat das Ganze nur die FilesMatch-Regel in derselben Datei.
Der Benutzername wird jetzt abgeleitet: lic_<sha256(schluessel), 16 Hex>.
Die Datei enthaelt damit nur noch eine Einwegableitung und einen Hash ueber
einen hochentropen Schluessel.
Server und SDK muessen dabei zeichengenau uebereinstimmen; ein Test prueft
die C#-Ableitung gegen die PHP-Formel.
2. Ein Formatwechsel blieb unbemerkt liegen
Beim Umbau auf 1. faellt auf: reconcile() sah keinen Anlass zur
Neuerzeugung, die Dateien behielten das alte Format, waehrend die Clients
bereits das neue schickten. Die erzeugten Dateien tragen deshalb jetzt eine
Formatkennung; weicht sie ab, wird neu erzeugt.
3. Doku beschrieb Nginx, der Schutz ist Apache-only
.htaccess wird von Nginx ignoriert - dort waeren die Verzeichnisse offen und
die .htpasswd oeffentlich abrufbar. Die Statusanzeige pruefte nur, ob die
Dateien existieren, und haette in dem Fall "GESCHUETZT" gemeldet.
Neu: ein echter Selbsttest ruft die eigene Paket-Adresse OHNE Zugangsdaten
ab und erwartet 401. Er laeuft beim manuellen Erzeugen und nach jeder
automatischen Neuerzeugung; das Ergebnis steht in der Oberflaeche, ein
Fehlschlag im Log. Er findet nebenbei auch abgeschaltetes AllowOverride und
Tippfehler in der erzeugten Datei. Doku korrigiert, Nginx-Vorlage ergaenzt.
4. Erstinstallation schrieb an einen Ort, an dem Linux-Anwendungen nicht lesen
setup.json-Ziele waren immer installationsrelativ. Eine Anwendung, die sich
unter Linux richtig verhaelt, liest aus $XDG_CONFIG_HOME - /opt/<app> ist
fuer den Dienstbenutzer meist nicht schreibbar. Der Installer legte die
Datei also dorthin, wo nie jemand nachsieht.
Ziele haben jetzt ein "location": install (Vorgabe), config, data, home,
plus ${VAR}- und %VAR%-Ersetzung in "file". Unbekannte Variablen bleiben
stehen statt leer zu werden - ein Platzhalter faellt auf, ein falscher Pfad
nicht. Der Installer gibt den aufgeloesten Pfad aus, weil bei config das
Konto entscheidet, unter dem er laeuft.
Ausserdem
- Doku zeigte "status": "ok" fuer update/delete; Http::ok() erzeugt
"status": "success".
- UPGRADE §16.1 deckte Neuprodukte nicht ab: Fuer ein Produkt ohne Release
existiert /releases/<slug>/ nicht und wird uebersprungen. Das Verzeichnis
entsteht erst mit dem ersten Upload, der naechste Tick schuetzt es. Der erste
ausgelieferte Build muss die Zugangsdaten also schon mitbringen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
6.0 KiB
C#
149 lines
6.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Deploymentcenter.Client.Models
|
|
{
|
|
/// <summary>
|
|
/// Beschreibt, was eine Anwendung zum Laufen braucht - als Datei
|
|
/// <c>setup.json</c> im Wurzelverzeichnis des Pakets, neben der
|
|
/// manifest.json.
|
|
///
|
|
/// Bewusst im Paket und nicht zentral im Deploymentcenter: so ist die
|
|
/// Beschreibung mit der Anwendung versioniert. Braucht Version 2.0 ein
|
|
/// Feld mehr als 1.9, stimmt es automatisch - eine zweite Pflegestelle
|
|
/// wuerde frueher oder spaeter auseinanderlaufen.
|
|
///
|
|
/// Fehlt die Datei, laesst sich die Anwendung trotzdem installieren; der
|
|
/// Installer entpackt sie dann nur und fragt nichts ab.
|
|
/// </summary>
|
|
public class SetupDefinition
|
|
{
|
|
/// <summary>Format-Version dieser Datei.</summary>
|
|
[JsonPropertyName("schema")]
|
|
public int Schema { get; set; } = 1;
|
|
|
|
/// <summary>Anzeigename, sonst wird der Projekt-Slug verwendet.</summary>
|
|
[JsonPropertyName("displayName")]
|
|
public string DisplayName { get; set; } = string.Empty;
|
|
|
|
/// <summary>Kurzer Hinweistext, der vor der Abfrage angezeigt wird.</summary>
|
|
[JsonPropertyName("description")]
|
|
public string Description { get; set; } = string.Empty;
|
|
|
|
/// <summary>Wohin die ermittelten Werte geschrieben werden.</summary>
|
|
[JsonPropertyName("targets")]
|
|
public List<SetupTarget> Targets { get; set; } = new List<SetupTarget>();
|
|
|
|
/// <summary>Die benoetigten Werte.</summary>
|
|
[JsonPropertyName("fields")]
|
|
public List<SetupField> Fields { get; set; } = new List<SetupField>();
|
|
}
|
|
|
|
/// <summary>Eine Datei, in die Werte geschrieben werden.</summary>
|
|
public class SetupTarget
|
|
{
|
|
/// <summary>
|
|
/// Pfad, bezogen auf <see cref="Location"/>. Umgebungsvariablen sind
|
|
/// in beiden Schreibweisen erlaubt: <c>${XDG_CONFIG_HOME}</c> und
|
|
/// <c>%APPDATA%</c>. Ein absoluter Pfad wird unveraendert benutzt.
|
|
/// </summary>
|
|
[JsonPropertyName("file")]
|
|
public string File { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Wo die Datei liegt:
|
|
///
|
|
/// install (Vorgabe) - im Installationsverzeichnis
|
|
/// config - %APPDATA% bzw. $XDG_CONFIG_HOME (~/.config)
|
|
/// data - %LOCALAPPDATA% bzw. $XDG_DATA_HOME
|
|
/// home - Benutzerverzeichnis
|
|
///
|
|
/// Anwendungen, die sich unter Linux richtig verhalten, legen ihre
|
|
/// Konfiguration nicht neben das Programm - /opt/<app> ist fuer den
|
|
/// Dienstbenutzer meist nicht schreibbar. Ohne diese Angabe schriebe
|
|
/// der Installer dorthin, wo die Anwendung nie nachsieht.
|
|
/// </summary>
|
|
[JsonPropertyName("location")]
|
|
public string Location { get; set; } = "install";
|
|
|
|
/// <summary>"json" oder "env".</summary>
|
|
[JsonPropertyName("format")]
|
|
public string Format { get; set; } = "json";
|
|
|
|
/// <summary>
|
|
/// Kennung, ueber die Felder dieser Datei zugeordnet werden. Ohne
|
|
/// Angabe schreiben alle Felder in das erste Ziel.
|
|
/// </summary>
|
|
[JsonPropertyName("id")]
|
|
public string Id { get; set; } = string.Empty;
|
|
}
|
|
|
|
/// <summary>Ein einzelner Konfigurationswert.</summary>
|
|
public class SetupField
|
|
{
|
|
/// <summary>
|
|
/// Schluessel im Ziel. Bei JSON trennt ein Doppelpunkt die Ebenen
|
|
/// ("ConnectionStrings:Main"), passend zur Schreibweise von
|
|
/// Microsoft.Extensions.Configuration.
|
|
/// </summary>
|
|
[JsonPropertyName("key")]
|
|
public string Key { get; set; } = string.Empty;
|
|
|
|
/// <summary>Beschriftung fuer die Abfrage.</summary>
|
|
[JsonPropertyName("label")]
|
|
public string Label { get; set; } = string.Empty;
|
|
|
|
/// <summary>Erlaeuterung, die unter der Frage steht.</summary>
|
|
[JsonPropertyName("help")]
|
|
public string Help { get; set; } = string.Empty;
|
|
|
|
/// <summary>string | secret | url | int | bool | enum</summary>
|
|
[JsonPropertyName("type")]
|
|
public string Type { get; set; } = "string";
|
|
|
|
/// <summary>Muss ein Wert vorliegen?</summary>
|
|
[JsonPropertyName("required")]
|
|
public bool Required { get; set; } = true;
|
|
|
|
/// <summary>Vorbelegung.</summary>
|
|
[JsonPropertyName("default")]
|
|
public string Default { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Woher der Wert kommt, wenn nicht gefragt werden soll:
|
|
/// detect:hostname | detect:platform | detect:installdir | detect:username
|
|
/// provision - Token beim Deploymentcenter anfordern
|
|
/// ask - Vorgabe: nachfragen
|
|
/// </summary>
|
|
[JsonPropertyName("source")]
|
|
public string Source { get; set; } = "ask";
|
|
|
|
/// <summary>Rechte fuer source = provision.</summary>
|
|
[JsonPropertyName("scopes")]
|
|
public List<string> Scopes { get; set; } = new List<string>();
|
|
|
|
/// <summary>Auswahlmoeglichkeiten fuer type = enum.</summary>
|
|
[JsonPropertyName("options")]
|
|
public List<string> Options { get; set; } = new List<string>();
|
|
|
|
/// <summary>Regulaerer Ausdruck, gegen den der Wert geprueft wird.</summary>
|
|
[JsonPropertyName("validate")]
|
|
public string Validate { get; set; } = string.Empty;
|
|
|
|
/// <summary>Meldung, wenn die Pruefung fehlschlaegt.</summary>
|
|
[JsonPropertyName("validationMessage")]
|
|
public string ValidationMessage { get; set; } = string.Empty;
|
|
|
|
/// <summary>Kennung des Ziels aus <see cref="SetupTarget.Id"/>.</summary>
|
|
[JsonPropertyName("target")]
|
|
public string Target { get; set; } = string.Empty;
|
|
|
|
/// <summary>Wird der Wert verdeckt eingegeben und nicht angezeigt?</summary>
|
|
[JsonIgnore]
|
|
public bool IsSecret =>
|
|
string.Equals(Type, "secret", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(Source, "provision", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|