Zwei Beobachtungen aus der laufenden Integration. 1. Der Installer schrieb Geheimnisse weltlesbar SetupWriter benutzte File.WriteAllText ohne Rechteanpassung - unter Linux also die Standardmaske und damit ueblicherweise 644. In genau dieser Datei stehen Lizenzschluessel und Anwendungstoken; jeder Benutzer des Systems konnte sie lesen. Der Lizenz-Cache in StateStore wird aus demselben Grund seit jeher auf 600 gesetzt - der Installer zog nicht nach. Enthaelt ein Ziel mindestens einen geheimen Wert, wird die Datei jetzt auf den eigenen Benutzer beschraenkt. Als geheim gilt type=secret UND source=provision: ein so geholtes Token traegt oft den Typ "string", ist aber genauso schutzbeduerftig. Unter Windows bleibt es beim Profil-ACL. 2. Zielnamen koennen plattformabhaengig unterschiedlich sein %APPDATA%\MeineAnwendung gegen $XDG_CONFIG_HOME/meineanwendung - eine setup.json kannte nur eine Schreibweise. Die kleingeschriebene Form allein traegt, weil NTFS die Schreibweise ignoriert, aber nur solange das Dateisystem tatsaechlich unempfindlich ist; auf APFS mit Beachtung der Schreibweise oder bei groesseren Unterschieden entstuende ein zweites, leeres Verzeichnis neben dem, aus dem die Anwendung liest. Ziele haben deshalb optional fileWindows, fileLinux und fileMacOS; ohne Angabe gilt weiterhin file. Die Dokumentation haelt ausserdem fest, dass der Installer bewusst Klartext schreibt und die Anwendung selbst entscheidet, ob und wie sie ihn danach schuetzt - und dass eine Entschluesselung, die Klartext durchreicht, deshalb kein Altlast-Zweig mehr ist, sondern ein aktiv genutzter Pfad. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
7.4 KiB
C#
177 lines
7.4 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>
|
|
/// Abweichender Pfad unter Windows.
|
|
///
|
|
/// Konfigurationsordner heissen plattformabhaengig unterschiedlich:
|
|
/// unter Windows ist "%APPDATA%\MeineAnwendung" ueblich, unter Linux
|
|
/// "$XDG_CONFIG_HOME/meineanwendung" klein. Meist genuegt es, die
|
|
/// kleingeschriebene Form anzugeben - NTFS unterscheidet nicht nach
|
|
/// Gross- und Kleinschreibung, umgekehrt entstuende unter Linux ein
|
|
/// zweites, leeres Verzeichnis neben dem, aus dem die Anwendung liest.
|
|
///
|
|
/// Das traegt aber nur, solange das Dateisystem tatsaechlich
|
|
/// unempfindlich ist - auf APFS mit Beachtung der Schreibweise oder
|
|
/// einem per fsutil umgestellten NTFS-Ordner nicht mehr. Und es hilft
|
|
/// gar nicht, wenn sich die Namen um mehr als die Schreibweise
|
|
/// unterscheiden. Fuer diese Faelle sind die folgenden Felder da; ohne
|
|
/// Angabe gilt <see cref="File"/>.
|
|
/// </summary>
|
|
[JsonPropertyName("fileWindows")]
|
|
public string FileWindows { get; set; } = string.Empty;
|
|
|
|
/// <summary>Abweichender Pfad unter Linux.</summary>
|
|
[JsonPropertyName("fileLinux")]
|
|
public string FileLinux { get; set; } = string.Empty;
|
|
|
|
/// <summary>Abweichender Pfad unter macOS.</summary>
|
|
[JsonPropertyName("fileMacOS")]
|
|
public string FileMacOS { 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);
|
|
}
|
|
}
|