Drei Dinge, die beim ersten Lauf des Installers auf einer Linux-Maschine auffielen. 1. Die Release-Ablage wies Administratorkonten ab. ReleaseGuard nahm nur die Rolle 'installer' in die .htpasswd auf, waehrend Installskripte und Agent ausdruecklich sagten, ein Administratorkonto tue es auch: Anmeldung und Katalog gelangen, erst der Download endete mit 401 - und die Meldung sprach von abgelaufenen Lizenzen, die es bei einer Erstinstallation gar nicht geben kann. Adminkonten zaehlen jetzt zu den Installationskonten. FORMAT_VERSION auf 3, damit reconcile() die Dateien sofort neu schreibt statt erst beim naechsten turnusmaessigen Lauf; ein neu angelegtes Konto landet ausserdem unabhaengig von seiner Rolle sofort darin. Bei einem 401 mit Benutzerzugangsdaten nennt der Client jetzt Konto und zugangsberechtigte Rollen, und der Agent bricht ab, statt ueber die API weiterzusuchen und dieselbe Meldung ein paar Schritte spaeter ein zweites Mal zu zeigen. 2. Der Installer prueft die .NET-Laufzeit. Bisher endete eine gelungene Installation auf einer Maschine ohne .NET mit einer Anwendung, die sich nicht starten laesst - und die Fehlersuche begann beim Deploymentcenter, weil das der letzte bewusste Schritt war. Gelesen wird die runtimeconfig.json der Anwendung und mit "dotnet --list-runtimes" verglichen; fehlt etwas, nennt der Installer den Installationsbefehl fuer diese Plattform. Eigenstaendig veroeffentlichte Pakete werden nicht bemaengelt, rollForward wird beachtet. 3. Die Ueberwachung der Maschine entsteht im Installer. Zwei Fragen - Name im Dashboard und ob eingeplant werden soll - statt fuenf Schritten in der Oberflaeche an einem anderen Rechner. Monitor, Token mit genau watchdog:ping, Skript, Dateirechte, ein Heartbeat zur Probe und der Cron-Eintrag bzw. die geplante Aufgabe entstehen daraus. Fuer Maschinen ohne Installation: --action monitor. Die Agent-Skripte werden jetzt in src/Modules/Watchdog/AgentScript.php erzeugt - von Oberflaeche und Installer gemeinsam - und melden Last, Speicher, Plattenbelegung und Laufzeit mit, statt nur "status: ok". Beim Ausfuehren fielen zwei Fehler auf, die dort behoben sind: df -P verrutscht bei Geraetenamen mit Leerzeichen (gezaehlt wird jetzt von hinten), und ohne LC_ALL=C erzeugt awk auf einem deutschen System "12,5" und damit kaputtes JSON. Neu: POST /api/setup/v1/agent. SDK 2.6.0 mit SetupClient.RequestWatchdogAgentAsync(). Die OpenAPI-Beschreibung des neuen Endpunkts bleibt zunaechst aussen vor: public/api/openapi.php traegt gerade auch fremde, noch nicht committete Aenderungen aus einer parallel laufenden Arbeit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
5.6 KiB
C#
141 lines
5.6 KiB
C#
using System;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
|
|
namespace Deploymentcenter.Client
|
|
{
|
|
/// <summary>
|
|
/// Zugangsdaten fuer die Release-Ablage.
|
|
///
|
|
/// Die Verzeichnisse unter /releases/ liegen hinter HTTP-Basic-Auth. Wer
|
|
/// herunterladen darf, entscheidet der Lizenzschluessel: Eine Anwendung
|
|
/// mit gueltiger Lizenz versorgt sich damit selbst weiter, ohne dass
|
|
/// zusaetzliche Geheimnisse verteilt werden muessten. Zuvor war die Ablage
|
|
/// offen - und damit auch fuer jeden im Internet herunterladbar.
|
|
///
|
|
/// Bei einer Erstinstallation gibt es noch keinen Schluessel; dort treten
|
|
/// die Zugangsdaten des Installationskontos an seine Stelle.
|
|
/// </summary>
|
|
public sealed class ReleaseCredentials
|
|
{
|
|
private ReleaseCredentials(string user, string password, bool fromLicense)
|
|
{
|
|
User = user;
|
|
Password = password;
|
|
FromLicense = fromLicense;
|
|
}
|
|
|
|
public string User { get; }
|
|
|
|
public string Password { get; }
|
|
|
|
/// <summary>
|
|
/// Stammt der Zugang aus einem Lizenzschluessel oder aus einem
|
|
/// Installationskonto? Nur fuer die Fehlermeldung von Belang - ein
|
|
/// abgelehntes Konto hat andere Ursachen als eine abgelehnte Lizenz,
|
|
/// und wer die falschen genannt bekommt, sucht an der falschen Stelle.
|
|
/// </summary>
|
|
public bool FromLicense { get; }
|
|
|
|
/// <summary>
|
|
/// Zugang ueber den Lizenzschluessel.
|
|
///
|
|
/// Der Benutzername wird aus dem Schluessel abgeleitet, das Passwort
|
|
/// ist der Schluessel selbst. Der Grund liegt im htpasswd-Format:
|
|
/// gehasht wird dort nur die Passwortspalte. Stuende der Schluessel
|
|
/// auch als Benutzername in der Datei, waere sie eine vollstaendige
|
|
/// Klartext-Kundenliste und der Hash daneben blosse Dekoration.
|
|
///
|
|
/// MUSS zeichengenau mit ReleaseGuard::licenseUsername() auf dem
|
|
/// Server uebereinstimmen.
|
|
/// </summary>
|
|
public static ReleaseCredentials? FromLicenseKey(string? licenseKey)
|
|
{
|
|
string key = (licenseKey ?? string.Empty).Trim();
|
|
|
|
if (key.Length == 0)
|
|
return null;
|
|
|
|
return new ReleaseCredentials(UsernameForLicenseKey(key), key, fromLicense: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ableitung des Benutzernamens: "lic_" plus die ersten 16 Hexzeichen
|
|
/// des SHA-256 ueber den Schluessel.
|
|
/// </summary>
|
|
public static string UsernameForLicenseKey(string licenseKey)
|
|
{
|
|
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes((licenseKey ?? string.Empty).Trim()));
|
|
|
|
var builder = new StringBuilder("lic_", 20);
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
builder.Append(hash[i].ToString("x2"));
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
/// <summary>Zugang ueber ein Installationskonto.</summary>
|
|
public static ReleaseCredentials? FromUser(string? user, string? password)
|
|
{
|
|
string u = (user ?? string.Empty).Trim();
|
|
string p = password ?? string.Empty;
|
|
|
|
return u.Length == 0 ? null : new ReleaseCredentials(u, p, fromLicense: false);
|
|
}
|
|
|
|
/// <summary>Wert fuer den Authorization-Header.</summary>
|
|
public string ToHeaderValue()
|
|
{
|
|
string raw = User + ":" + Password;
|
|
return "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(raw));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Haengt den Header an eine Anfrage. Ohne Zugangsdaten passiert
|
|
/// nichts - eine noch ungeschuetzte Ablage bleibt damit erreichbar.
|
|
/// </summary>
|
|
public static void Apply(HttpRequestMessage request, ReleaseCredentials? credentials)
|
|
{
|
|
if (credentials == null || request == null)
|
|
return;
|
|
|
|
// Nicht doppelt setzen, falls der Aufrufer schon etwas mitgibt.
|
|
if (request.Headers.Contains("Authorization"))
|
|
return;
|
|
|
|
request.Headers.TryAddWithoutValidation("Authorization", credentials.ToHeaderValue());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Erklaerung fuer den Menschen davor, wenn der Server 401 antwortet.
|
|
/// Ohne diesen Hinweis sieht ein abgelaufener Vertrag aus wie ein
|
|
/// Netzwerkfehler, und man sucht an der falschen Stelle.
|
|
/// </summary>
|
|
public static string DescribeUnauthorized(ReleaseCredentials? credentials)
|
|
{
|
|
if (credentials == null)
|
|
{
|
|
return "Die Release-Ablage verlangt Zugangsdaten, es wurden aber keine mitgegeben. "
|
|
+ "Erwartet wird der Lizenzschluessel dieser Installation.";
|
|
}
|
|
|
|
if (!credentials.FromLicense)
|
|
{
|
|
// Bei einer Erstinstallation gibt es noch gar keine Lizenz -
|
|
// von abgelaufenen Vertraegen zu sprechen schickt den Menschen
|
|
// davor in die falsche Richtung.
|
|
return "Die Release-Ablage hat das Konto '" + credentials.User + "' abgelehnt. "
|
|
+ "Zugang haben nur Konten der Rollen installer und admin, die nicht "
|
|
+ "abgeschaltet sind. Der Zugangsschutz wird auf dem Server erzeugt und "
|
|
+ "kennt Kontoaenderungen erst nach dem naechsten Lauf.";
|
|
}
|
|
|
|
return "Die Release-Ablage hat die Zugangsdaten abgelehnt. Moegliche Gruende: die Lizenz "
|
|
+ "ist abgelaufen, wurde widerrufen oder gehoert zu einem anderen Produkt.";
|
|
}
|
|
}
|
|
}
|