Die Runtime caldera.pyz
Das Programm, das einen Agent auf einem Rechner einrichtet und danach für ihn läuft und meldet: Befehle, Verzeichnisaufbau, die systemd-Unit, wie es den Agent überwacht, die Status-Datei, Einstellungen, Logs, Gesundheitsprüfungen, Updates und Rollback von Blueprints, das signierte Update der Runtime selbst, Verhalten ohne Caldera, Umgebungsvariablen, Python-Unterstützung und Sicherheitseigenschaften.
Was sie ist
caldera.pyz ist eine einzelne Python-3-Datei ohne Abhängigkeiten (nur die
Standardbibliothek; sie bringt einen eigenen kleinen YAML-Leser mit). Sie ist zugleich
das Kommandozeilenwerkzeug (caldera init <code>) und der dauerhaft laufende Prozess
(caldera run), der
- den Prozess des Agents startet, überwacht und stoppt,
- im Agent-Protokoll an Caldera meldet und die Befehle anwendet (pausieren, fortsetzen, jetzt ausführen, Einstellungen, Log),
- neue Versionen des Blueprints holt, auf Gesundheit prüft und anwendet, und zurückrollt,
- auf Wunsch sich selbst durch eine signierte neue Runtime ersetzt (unten),
- oder, bei einem Agent, der eigene Prozesse behält, keinen besitzt und alles an einen Adapter weitergibt (Adapter-Modus).
Sie entscheidet nie, was der Agent tut. Sie startet den Agent, liest die Status-Datei, die der Agent schreibt, und gibt sie weiter.
Dieselben Bytes werden unter https://app.calderaapp.io/cli/caldera.pyz ausgeliefert,
mit der Prüfsumme unter /cli/caldera.pyz.sha256 und der Signatur unter
/cli/caldera.pyz.sig. Das Archiv ist reproduzierbar: Dieselben Quellen ergeben
dieselben Bytes.
Beschaffen
Der Install-Dialog zeigt den Befehl mit eingebetteter Prüfsumme:
curl -fsSL https://app.calderaapp.io/cli/caldera.pyz -o caldera.pyz && echo "<sha256> caldera.pyz" | sha256sum -c -
Auf macOS shasum -a 256 -c - statt sha256sum -c - verwenden (der Dialog bietet diese
Variante an). Die Prüfsumme im Dialog wird aus genau den Bytes berechnet, die der Server
ausliefert, braucht also keine zweite Anfrage an denselben Ursprung.
Voraussetzungen
- Python 3.10 oder neuer. Das Programm deklariert kein Minimum; 3.10 ist die niedrigste getestete Version. Getestet wurde auch mit 3.13 und 3.14.
- Linux oder macOS. Es nutzt
fcntlund/procoderpssowie eine systemd-User-Unit für den Service. Windows wird nicht unterstützt. - systemd für den Service.
initfunktioniert auch ohne systemd (--no-service, oder keinsystemctl); der Agent wird dann mitcaldera run --dir <dir>gestartet. - Ausgehendes HTTPS zu Caldera. Auf dem Rechner lauscht nichts.
ssh-keygen(OpenSSH) für das signierte Update der Runtime selbst. Es ist auf praktisch jedem Rechner vorhanden, auf dem ein Agent läuft. Ohne es arbeitet und meldet die Runtime weiter, aktualisiert sich aber nicht selbst.
Befehle
Alle Befehle lauten python3 caldera.pyz <command>. Fehler geben caldera: <message> auf
stderr aus und beenden mit 1.
| Befehl | Tut | Braucht Caldera? |
|---|---|---|
init <code> [--dir DIR] [--url URL] [--no-service] |
Richtet einen Agent aus einem Einmal-Install-Code ein. | Ja, als einziger Befehl. |
run [--dir DIR] [--once] |
Der dauerhaft laufende Prozess: überwachen und melden. --once sendet eine Meldung und beendet (ein Test). |
Meldungen gehen hinaus; nichts wartet auf sie. |
status [--dir DIR] |
Zeigt den Zustand des Agents. | Nein |
pause [--dir DIR] |
Schreibt die Datei paused und stoppt den Agent. Im Adapter-Modus: ruft adapter pause auf. |
Nein |
resume [--dir DIR] |
Entfernt die Datei; startet den Agent, wenn die Runtime nicht läuft. Im Adapter-Modus: ruft adapter resume auf. |
Nein |
rollback [--dir DIR] |
Geht auf die vorherige Blueprint-Version zurück. | Nein |
rollback-runtime [--dir DIR] |
Legt die vorherige Runtime-Datei zurück und sagt einer laufenden Runtime, von ihr neu zu starten. | Nein |
pin-runtime <version|off> [--dir DIR] |
Hält die Runtime auf einer Version fest: Sie aktualisiert sich auf keine andere. | Nein |
log [name] [--dir DIR] |
Listet die Logs des Agents oder gibt das Ende eines Logs aus. | Nein |
--version |
Gibt caldera 1 (runtime <release>) aus. 1 ist die Konstante der CLI; das Release ist beim Bauen eingebaut (dev aus dem Quelltext). |
Nein |
--dirist bei allen außerinitstandardmäßig das aktuelle Verzeichnis. Angeben oder in das Verzeichnis des Agents wechseln.init --dirist standardmäßig~/caldera-agents/<agent name>;--urlist standardmäßig die UmgebungsvariableCALDERA_URL, sonsthttps://app.calderaapp.io. Die URL musshttpssein;httpwird nur fürlocalhost,127.0.0.1und::1akzeptiert.statusgibt aus: Agent-Name, Blueprint und Version (mit der vorherigen), Prozess (running,pausedodernot running), Runtime (runningodernot running), die letzte Meldung (okoderfailed, und wie viele Sekunden her), das letzte Update (Ergebnis und Nachricht), eine Sperre, falls vorhanden, und den Wert jeder Einstellung.
Was init tut
- Vorschau. Liest, was der Code installieren würde, ohne ihn zu verbrauchen. Sie nennt den Agent, das Verzeichnis und die Geheimnisse. Alles, wobei eine Person noch abbrechen kann (ein belegtes Verzeichnis, ein Geheimnis, das sie nicht hat), kommt vor dem Verbrauchen des Codes.
- Verzeichnisprüfung. Enthält das Zielverzeichnis schon einen Agent
(
.caldera/config.jsonexistiert), hältinitan. - Geheimnisse. Für jedes deklarierte Geheimnis: die Umgebungsvariable
CALDERA_SECRET_<NAME>, wenn gesetzt; sonst Abfrage im Terminal (verdeckte Eingabe), wenn stdin ein Terminal ist; sonst ein Fehler, außer das Geheimnis istoptional(dann wird es übersprungen). Eine leere Antwort auf ein optionales Geheimnis überspringt es. Geheimnisse gehen nie an Caldera. - Einlösen. Verbraucht den Code. Die Antwort trägt das Token des Agents, je Anbindung einen Satz Variablen und das Bundle. Die Runtime lehnt eine Antwort ab, die einen anderen Agent installiert als in der Vorschau, und einen Wert mit Zeilenumbruch.
- Entpacken. Prüft die SHA-256 des Bundles, entpackt es nach
versions/<version>/, validiert das Manifest und prüft, dass jede Datei existiert, die das Manifest nennt. Ein Bundle, das eine Prüfung nicht besteht, hinterlässt nichts. - Schreiben.
agent.env,secrets.env(beide Modus 600),config.json, die Einstellungsdatei und den Linkcurrent. - Service. Schreibt und startet eine systemd-User-Unit (außer bei
--no-service). Findet sich keinsystemctl, gibt es aus, wie der Agent von Hand gestartet wird:caldera run --dir <dir>.
Scheitert etwas nach Schritt 4, ist der Code verbraucht und der Agent existiert in Caldera ohne Rechner. Die Runtime gibt das aus, und eine Person löscht den Agent in Caldera und stellt einen neuen Code aus.
Das Verzeichnis
<dir>/ z. B. ~/caldera-agents/my-agent
versions/<semver>/ ein entpacktes Bundle je aufbewahrter Version (nie bearbeitet)
current -> versions/X die Version, die läuft; atomar getauscht
state/ was ein Update überlebt (die `state`-Pfade des Manifests)
.caldera/ die eigenen Dateien der Runtime; ein Bundle kann hier nie schreiben (Modus 700)
config.json (600) URL, Agent-Name, Blueprint, vorherige Version, Einstellungen, Sperre, Zeitfenster, letztes Update
agent.env (600) CALDERA_URL, CALDERA_TOKEN, Variablen der Anbindungen
secrets.env (600) was `init` abgefragt hat
settings.json (600) die Einstellungen, wie sie jetzt sind, zum Lesen für den Agent
paused existiert, solange der Agent pausiert
run-now von `run_now` angelegt; der Agent entfernt sie
update.json (600) das Journal eines laufenden Updates
lock gehalten, solange sich die Version ändert
agent.pid PID und Startzeit des Agents (seine Identität)
runtime.pid PID und Startzeit der Runtime
runtime.json (600) der eigene Update-Zustand der Runtime: Version, vorherige, Sperre, Festhalten, letztes Ergebnis, Probezeit
runtime.lock gehalten, solange sich dieser Zustand ändert
last-report.json wann die letzte Meldung hinausging und ob sie funktioniert hat
runtime.log das Log der Runtime
agent.out.log (600) stdout und stderr des Agents
caldera.pyz eine Kopie der Runtime, wenn `init` aus einer .pyz lief; die Datei, die die Unit ausführt, bei einem Selbst-Update ersetzt
caldera.pyz.previous die Runtime, die davor lief (Modus 700), für `rollback-runtime`
current ist ein Symlink, und der Tausch ist ein einziges rename. Einen Symlink mit
os.replace zu ersetzen ist atomar: In jedem Augenblick nennt current eine vollständige
Version. Ein an beliebiger Stelle beendetes Update lässt current auf der alten oder der
neuen Version, nie auf der Hälfte von beiden.
Zustand liegt außerhalb der Version und ist eingelinkt. Jeder Pfad in der
state-Liste des Manifests liegt in <dir>/state, und jede Version bekommt einen Symlink
darauf. Eine Datei, die das Bundle an einem State-Pfad mitbringt, ist ein Seed: Sie wird
nur nach state kopiert, wenn dort noch nichts liegt. So kann eine neue Version
Standardwerte mitbringen, ohne zu überschreiben, was der Agent gelernt hat.
Nur zwei Versionen werden aufbewahrt: die laufende und die vorherige. Ältere werden nach einem erfolgreichen Update gelöscht.
Der Service
init schreibt ~/.config/systemd/user/caldera-<name>.service (oder unter
$XDG_CONFIG_HOME):
[Unit]
Description=Caldera runtime for the agent <name>
After=network-online.target
[Service]
ExecStart="<python>" "<dir>/.caldera/caldera.pyz" "run" "--dir" "<dir>"
Restart=always
RestartSec=10
KillMode=process
[Install]
WantedBy=default.target
danach systemctl --user daemon-reload und systemctl --user enable --now caldera-<name>.service. Damit er weiterläuft, wenn niemand angemeldet ist: loginctl enable-linger <user>.
Die Unit ändert sich nie, wenn sich die Runtime ändert. Sie nennt
<dir>/.caldera/caldera.pyz, und ein Selbst-Update ersetzt diese Datei mit einem
rename; Unit, Interpreter und Argumente bleiben, wie sie sind.
KillMode=process ist kein Detail. Der Standard (control-group) beendet beim Stoppen
jeden Prozess, den die Unit gestartet hat, und würde den Agent bei einem Neustart der
Runtime mitnehmen. Der Agent muss die Runtime überleben.
Logs der Runtime: journalctl --user -u caldera-<name>.service und
<dir>/.caldera/runtime.log. Jede Zeile läuft durch einen Redaktor, der das Agent-Token
und die eingegebenen Geheimnisse durch ihre Namen ersetzt.
Wie sie den Agent überwacht
caldera run hat zwei Threads, bewusst getrennt:
- Der Supervisor sieht einmal pro Sekunde nach dem Agent und berührt nie das Netz.
- Der Reporter spricht in jedem Intervall mit Caldera, mit einem Timeout bei jeder
Anfrage (standardmäßig 10 s;
CALDERA_REQUEST_TIMEOUT, 1 bis 120).
Ein Caldera, das zehn Sekunden hängt, verzögert die nächste Meldung und sonst nichts. Der Agent läuft weiter, eine Pause wird binnen einer Sekunde angewendet, und das Rollback-Zeitfenster zählt weiter.
Beim Start schließt die Runtime zuerst ein unterbrochenes Update ab oder nimmt es zurück (siehe das Journal).
Prozess-Runtime (runtime.type: process)
- Der Agent wird aus
runtime.commandgestartet (eine Argumentliste, nie eine Shell), inversions/<version>/<workdir>, mit der Umgebung unten, stdin geschlossen, stdout und stderr an.caldera/agent.out.logangehängt (beim Start gekürzt, wenn größer als 5 MB). - Er wird in einer eigenen Session gestartet, ein Absturz, Neustart oder
systemctl stopder Runtime nimmt ihn also nicht mit. Eine Runtime, die erneut startet, findet den Agent über seine PID-Datei (PID und Startzeit, weil eine PID allein wiederverwendet wird) und übernimmt ihn, statt einen zweiten zu starten. - Stoppen: SIGTERM an die Prozessgruppe, bis zu 10 Sekunden Gnadenfrist, dann SIGKILL und bis zu 5 weitere Sekunden.
- Neustarts: Läuft der Agent nicht und ist nicht pausiert, wird er gestartet. Nach einem
Fehler wartet der nächste Versuch 5 Sekunden, dann verdoppelt sich die Wartezeit, bis
300 Sekunden. Ein Prozess, der länger als 60 Sekunden lief, setzt die Zählung zurück.
run_nowüberspringt das Warten.
Compose-Runtime (runtime.type: compose)
Derselbe Vertrag über docker compose -f <file>: up -d zum Starten, stop zum Stoppen,
ps --status running -q zum Nachsehen, mit 300 Sekunden Timeout je Aufruf. Sie wurde auf
keinem echten Docker-Rechner überprüft. Als unerprobt behandeln.
Die Umgebung des Agents
Der Agent bekommt die eigene Umgebung der Runtime plus alles in .caldera/agent.env und
.caldera/secrets.env, außer CALDERA_TOKEN: Der Agent braucht das Token nicht, das
für die Runtime spricht, und ein Prozess, der es nie hielt, kann es nicht preisgeben. Dazu
diese Variablen:
| Variable | Ist |
|---|---|
CALDERA_SETTING_<KEY> |
der Wert jeder Einstellung beim Start des Prozesses (true oder false bei bool, leer wenn ungesetzt); der Schlüssel ist großgeschrieben |
CALDERA_SETTINGS_FILE |
JSON aller Einstellungen, bei jeder Änderung neu geschrieben |
CALDERA_PAUSE_FILE |
existiert, solange der Agent pausiert (die Runtime stoppt den Prozess, das Prüfen ist also optional) |
CALDERA_RUN_NOW_FILE |
von run_now angelegt; der Agent löscht sie, wenn er handelt |
CALDERA_STATE_DIR, CALDERA_AGENT_DIR |
das Zustandsverzeichnis und das Verzeichnis des Agents |
CALDERA_AGENT_STARTED |
Unix-Zeit (Sekunden, drei Nachkommastellen), zu der dieser Prozess gestartet wurde |
CALDERA_BINDING_<PRODUCT>_URL, _WORKSPACE, _TOKEN |
ein Satz je Anbindung (aus agent.env) |
CALDERA_URL steht ebenfalls in der Umgebung (es steht in agent.env). Der Agent braucht
nichts davon, um zu laufen. So arbeitet er ohne Caldera mit.
Die Status-Datei
Der Agent meldet, indem er die JSON-Datei schreibt, die das Manifest als status nennt
(relativ zum Versionsverzeichnis, zum Beispiel data/status.json), in der Form einer
Protokoll-Meldung. Die Runtime gibt nur diese Schlüssel weiter: state, health,
active, gauges, runs, panels. Jeder andere Schlüssel, etwa ein heartbeat, den der
Agent für seine eigene Gesundheitsprüfung führt, wird nicht weitergegeben.
- Eine Datei über 200 KB wird nicht gelesen; die Meldung bekommt eine Gesundheitszeile "Status file" bei warn.
- Eine Datei, die kein JSON-Objekt ist, kostet eine Gesundheitszeile, nicht die Meldung.
- Eine Datei, die noch nicht existiert, bedeutet, dass der Agent noch nichts gesagt hat.
- Atomar schreiben (eine temporäre Datei schreiben, dann umbenennen). Der Blueprint
starterim Katalog tut das.
Die Runtime ergänzt agent (Version und Hostname), settings, controls, acks und
applied, dazu die Gesundheitseinträge Process und je Anbindung Binding <product>. Sie
ersetzt state durch Paused (warn) oder Not running (bad, mit "restarting in N s"),
wenn das zutrifft, und verwendet Running (ok), wenn der Agent keinen Zustand meldet.
Befehle von Caldera
Jeder wird gegen das Manifest dieses Agents geprüft und in der nächsten Meldung mit einem Ack beantwortet.
| Befehl | Was die Runtime tut |
|---|---|
pause |
Steht pause in den controls des Manifests: schreibt .caldera/paused, stoppt den Agent. Ack "Paused." |
resume |
Wenn angeboten: entfernt die Datei; der Supervisor startet den Agent. Ack "Running again." |
run_now |
Wenn angeboten: wird abgelehnt mit "Paused. Resume first.", solange pausiert ist. Sonst legt sie die Datei run-now an und überspringt ein Neustart-Warten. Ack "Requested." |
set |
Jeder Schlüssel muss eine deklarierte Einstellung sein und jeder Wert innerhalb seiner Grenzen, sonst ändert sich nichts (ganz oder gar nicht). Ack "Applied: ." oder "Nothing changed: : ." |
log |
Nur ein im Manifest unter logs deklarierter Name und nur eine Datei, die innerhalb des Agent-Verzeichnisses und außerhalb von .caldera/ aufgelöst wird. Die letzten 512 KB werden hochgeladen; alles andere wird mit dem Text "There is no such log." beantwortet. Kein Ack: Der Upload ist die Antwort. |
| alles andere | Abgelehnt: "This agent does not know the command ..." |
Ein Control, das das Manifest nicht anbietet, wird genauso abgelehnt. controls ist
standardmäßig pause und resume; run_now muss genannt werden.
Die Pause ist dieselbe Datei, die caldera pause schreibt. Das macht jeden Befehl ohne
Caldera umkehrbar.
Einstellungen
- Das Manifest deklariert Schema, Grenzen und Standardwerte. Werte liegen in
.caldera/config.json; eine Einstellung ohne gespeicherten Wert liest ihren Standardwert. setschreibtsettings.jsonneu und startet den Agent nicht neu. Die UmgebungsvariablenCALDERA_SETTING_<KEY>ändern sich erst beim nächsten Start; ein Agent, der sofort reagieren muss, liest vor jeder EntscheidungCALDERA_SETTINGS_FILE.nulllöscht eine Einstellung nur, wenn das Manifest ihr keinen Standardwert gibt.- Ein Schlüssel, den eine neuere Version nicht mehr deklariert, bleibt in
config.json, wird aber ignoriert.
Gesundheitsprüfungen
Der health.command des Manifests (argv, in dem Arbeitsverzeichnis der neuen Version
ausgeführt, mit der Umgebung des Agents einschließlich Geheimnissen und
CALDERA_AGENT_STARTED) entscheidet, ob eine Version funktioniert. Exit 0 besteht. Er
schlägt fehl bei einem Exit-Code ungleich 0, einem Timeout (timeout_seconds, standardmäßig
60, höchstens 600) oder einem Befehl, der nicht laufen kann. Ein Manifest ohne health
besteht immer.
Die Prüfung so schreiben, dass sie voraussetzt, dass die neue Version gearbeitet hat.
Einen Heartbeat in der Status-Datei mit CALDERA_AGENT_STARTED vergleichen: Eine frisch
wirkende Datei, die die vorherige Version hinterlassen hat, darf nicht für die neue gelten.
Eine gute Prüfung verifiziert außerdem die Konfiguration des Agents und dass die Programme,
die er aufruft, im Pfad liegen.
Updates und Rollback von Blueprints
Ein Update hat fünf Schritte, und die Reihenfolge ist das Sicherheitsargument:
- Holen des Bundles und Prüfen der Prüfsumme. Ein Netzfehler beendet das Update als
failed, mit Wiederholung nach 10 Minuten. - Bereitstellen neben der laufenden Version und validieren. Ein abgelehntes Bundle
endet als
failed, und die Version wird gesperrt. - Tauschen von
current, einrename. - Neu starten auf der neuen Version und
healthausführen. - Behalten (
previous= die alte Version, ein Zeitfenster von 30 Minuten, Ergebnisok), wennhealthbesteht, oder zurücktauschen, die alte Version neu starten, Ergebnisrolled_backund die neue sperren, wenn nicht.
Ist es gewollt? Die Runtime wendet desired an, wenn alles zutrifft: Es nennt den
eigenen Blueprint des Agents, version ist eine Semantic Version (alles andere wird nie als
Pfad benutzt), sie ist nicht die laufende Version, und sie ist nicht gesperrt.
Die Sperre. Eine Version, von der die Runtime zurückgerollt hat oder die sie abgelehnt
hat, wird nicht erneut angewendet, bis desired eine andere Version nennt. Sonst würde
die nächste Meldung ein lokales caldera rollback rückgängig machen. Ein erfolgreiches
Update auf eine andere Version hebt die Sperre auf. Die eine Ausnahme ist ein
fehlgeschlagener Abruf (Caldera nicht erreichbar): Er wird nach zehn Minuten wiederholt.
Ergebnisse (gemeldet in applied.outcome, mit einer einzeiligen Nachricht):
| Ergebnis | Bedeutung |
|---|---|
ok |
Die neue Version läuft und hat ihre Gesundheitsprüfung bestanden. Die Nachricht lautet vor jedem Update "installed". |
rolled_back |
Die neue Version hat die Gesundheitsprüfung nicht bestanden, oder wurde im Zeitfenster schlechter, oder eine Person hat caldera rollback ausgeführt. Der Agent ist zurück auf der alten. |
failed |
Nichts hat sich geändert: Das Bundle konnte nicht geholt werden, wurde abgelehnt, oder ein Update wurde unterbrochen und zurückgenommen. |
Das Zeitfenster. Nach einem behaltenen Update merkt sich die Runtime den Level des
Agents zu diesem Zeitpunkt. 30 Minuten lang (fest, nicht einstellbar) prüft sie alle 15
Sekunden und rollt von selbst zurück, wenn der Level schlechter wurde. Der Level ist
bad, wenn der Agent nicht läuft, sonst der level des state in der Status-Datei (info
zählt als ok). Gesundheitseinträge zählen nicht; ein pausierter Agent wird nicht geprüft,
und die Pause einer Person ist nie ein Grund zum Zurückrollen.
Rollback (caldera rollback, oder Caldera nennt die alte Version) tauscht current auf
die vorherige Version und startet neu. Er braucht kein Netz, nur die Platte. Weil nur zwei
Versionen aufbewahrt werden, wechselt rollback zwischen der laufenden und der vorherigen
Version hin und her. Um weiter zurückzugehen, nennt Caldera diese Version, und die Runtime
holt sie erneut.
Das Journal und die Wiederherstellung
update.json hält den erreichten Schritt fest: fetched, staged, swapped. Ein Prozess,
der zwischen zwei Schritten beendet wurde, wird beim nächsten Start behandelt:
- bei
fetchedoderstagedbeendet: Die Reste werden verworfen; die alte Version war nie aufgehört, die richtige zu sein; - bei
swappedbeendet (die neue Version ist eingelinkt, aber niemand hat sie geprüft): zurück auf die alte Version, Ergebnisfailed, Sperre auf der neuen.
Eine Sperrdatei (Lock) verhindert, dass sich rollback und ein Update verschränken, und
dass der Supervisor während eines Updates die falsche Version startet.
Die Runtime selbst aktualisieren
Ein Update der Runtime ist Code von Caldera, der als Benutzer des Agents läuft, der auf einem
echten Rechner Schlüssel und Produkt-Token hält. Eine Prüfsumme vom selben Server beweist
nichts gegen einen kompromittierten Server oder ein manipuliertes Release, deshalb ist jede
Runtime-Datei signiert, außerhalb des Servers, und die Runtime lehnt ein Update ab, das
sie nicht verifizieren kann. Die Runtime aktualisiert sich nie ungefragt: Ein Admin
drückt auf der Seite des Agents "Runtime aktualisieren", wodurch die nächste Antwort
runtime_desired trägt.
- Herunterladen von
caldera.pyzundcaldera.pyz.sig, ohne Token. Ein Netzfehler oder eine fehlende Signatur endet alsfailed, wiederholt nach 10 Minuten. - Verifizieren der Prüfsumme, dann der Signatur. Kein Schlüssel, kein
ssh-keygenoder eine falsche Signatur endet alsfailed; die alte Datei bleibt. - Die neue Datei nach ihrer Version fragen (
python new.pyz --version). Eine Datei, die nicht startet oder eine andere Version nennt, endet alsfailed. - Tauschen und neu starten. Die alte Datei wird als
.previousaufbewahrt, die neue ersetzt sie mit einemrename, und die Runtime startet an Ort und Stelle neu. - Probezeit. Die erste erfolgreiche Meldung binnen fünf Minuten macht sie gut (
ok). Eine Runtime, die abstürzt oder nicht rechtzeitig meldet, geht von selbst auf.previouszurück (rolled_back), und die Version wird gesperrt.
Die Signatur. Der Release-Build signiert caldera.pyz mit einem OpenSSH-ed25519-Schlüssel,
Namespace caldera-runtime. Die Runtime prüft sie mit
ssh-keygen -Y verify -f <allowed_signers> -I caldera-release -n caldera-runtime -s <sig> < caldera.pyz
gegen einen öffentlichen Schlüssel, der in die Runtime eingebaut ist. Kein ssh-keygen,
keine Signatur (/cli/caldera.pyz.sig antwortet 404), eine Signatur eines anderen Schlüssels
oder für andere Bytes: kein Update. Das Ergebnis ist failed, mit dem Grund in
runtime.applied.message, und die alte Datei bleibt unberührt.
Ist es gewollt? Auf den Wunsch wird gehandelt, wenn runtime_desired eine
Versionszeichenkette ist ([0-9A-Za-z][0-9A-Za-z._-]{0,63}, nie ein Pfad), sie nicht die
laufende Version ist, nicht gesperrt ist, nicht durch ein Festhalten ausgeschlossen
ist und kein früheres Update noch in der Probezeit ist.
Was gesperrt wird. Eine Version, von der die Runtime zurückgegangen ist, und eine Datei,
die sie wegen ihres Inhalts abgelehnt hat (eine Prüfsumme, eine Signatur, eine Datei, die
nicht startet, eine andere Version als der Wunsch), wird nicht erneut versucht, bis
runtime_desired eine andere Version nennt. Was nur Ärger des Rechners oder des Netzes ist
(kein ssh-keygen, noch keine Signatur, ein fehlgeschlagener Download, nicht aus der Datei
der Unit gestartet), wird nach zehn Minuten wiederholt, solange der Wunsch besteht.
Neustart an Ort und Stelle. Nach dem Tausch ersetzt sich die Runtime im selben Prozess
durch die neue Datei: dieselbe Prozess-ID, die systemd-Unit sieht also einen
ununterbrochenen Prozess. Die eigenen Prozesse des Agents werden nicht berührt: Im
Prozess-Modus läuft der Agent in einer eigenen Session, und die neue Runtime findet ihn über
seine PID-Datei wieder; im Adapter-Modus gibt es nichts zu übernehmen. Nur eine Runtime, die
aus <dir>/.caldera/caldera.pyz läuft (was die Unit nennt), kann sich ersetzen; eine von
woanders gestartete sagt das.
Atomar und stabil. Die alte Datei wird zuerst nach .caldera/caldera.pyz.previous
geschrieben, dann ersetzt die neue Datei caldera.pyz mit einem rename: Beide Dateien sind
in jedem Augenblick vollständig. Die Unit wird nie umgeschrieben.
Die Probezeit. Die neue Runtime gilt nach ihrer ersten erfolgreichen Meldung binnen fünf
Minuten als gut (das Fenster beginnt mit jedem Start der neuen Runtime neu). Sie geht
von selbst auf die vorherige Datei zurück, wenn das Fenster ohne erfolgreiche Meldung
vergeht oder wenn sie mehr als dreimal ohne eine gestartet wurde (eine Absturzschleife). Das
Zurückgehen legt die vorherige Datei an ihren Platz, sperrt die Version, startet an Ort und
Stelle neu und meldet rolled_back. Ist Caldera während der Probezeit nicht erreichbar, geht
daher auch ein gutes Update zurück und wird gesperrt: Das ist der Preis einer Prüfung, die
kein Vertrauen in die neue Datei braucht.
Ergebnisse sind die des Blueprints (ok, rolled_back, failed) und laufen in der
Meldung als runtime.applied mit der Version, die jetzt funktioniert. Solange eine Runtime
in der Probezeit ist, behauptet sie nichts; ok wird erst gemeldet, nachdem sie sich bewährt
hat.
Offline. caldera rollback-runtime tauscht die beiden Dateien (ein zweiter Aufruf geht
also wieder vor), sperrt die Version, die es verlassen hat, und signalisiert einer laufenden
Runtime, von der Datei neu zu starten; ohne laufende Runtime wird die Datei beim nächsten
Start benutzt. caldera pin-runtime <version> lässt die Runtime auf keine andere Version
aktualisieren als diese, was Caldera auch wünscht; pin-runtime off hebt es auf. Das
Festhalten wird gemeldet. Beides braucht keine Verbindung. Ist Caldera nicht erreichbar,
geschieht gar nichts: Die Runtime behält die Datei, die sie hat.
Eine Runtime, die vor dieser Funktion installiert wurde, kann sich nicht selbst
aktualisieren. .caldera/caldera.pyz einmal von Hand ersetzen; danach geht es. Siehe
Agents betreiben.
Adapter-Modus
Bei einem Agent, der eigene Prozesse behält, besitzt die Runtime keinen. Sie startet nichts, stoppt nichts, startet nichts neu und liest keine Datei des Agents. Sie ruft einen Befehl auf, den das Manifest nennt, mit einem Verb. Die Schnittstelle steht unter Adapter-Modus und sein Vertrag.
Der Hinweis auf das Claude-Konto
Die Runtime kann melden, bei welchem Claude-Konto ihr Rechner angemeldet ist. Siehe Der Hinweis auf das Claude-Konto.
Verhalten, wenn Caldera nicht erreichbar ist
- Der Reporter loggt
report failed: ...und versucht es im nächsten Intervall erneut. Sonst ändert sich nichts: Der Agent läuft weiter, Neustarts passieren weiter, Pause und Fortsetzen funktionieren, das Rollback-Zeitfenster zählt weiter. - Ein Abruf, der während eines Updates fehlschlägt, ist Ergebnis
failedund wird nach zehn Minuten wiederholt. status,pause,resumeundrollbacköffnen nie eine Verbindung.- Die Runtime zu stoppen stoppt den Agent nicht.
- Nur
initbraucht Caldera.
Umgebungsvariablen
| Variable | Benutzt von | Ist |
|---|---|---|
CALDERA_URL |
init |
der Standard für --url |
CALDERA_REQUEST_TIMEOUT |
run und die lokalen Befehle |
Sekunden, die eine Anfrage dauern darf; standardmäßig 10, begrenzt auf 1 bis 120 |
CALDERA_SECRET_<NAME> |
init |
der Wert für das deklarierte Geheimnis <NAME>, für unbeaufsichtigte Installationen |
XDG_CONFIG_HOME |
init |
wohin die systemd-User-Unit geschrieben wird (standardmäßig ~/.config) |
Die Variablen, die der Agent bekommt, stehen in der Tabelle oben. Namen, die mit
CALDERA_ beginnen, gehören der Runtime; die secrets eines Manifests dürfen sie nicht
benutzen.
Sicherheitseigenschaften
| Eigenschaft | Wie |
|---|---|
| Das Token ist privat | agent.env und secrets.env haben Modus 600 und werden über eine temporäre Datei geschrieben, die von Anfang an mit diesem Modus angelegt wird, ein Geheimnis ist also zwischendurch nie lesbar. .caldera/ hat Modus 700. |
| Der Agent bekommt das Token nie | CALDERA_TOKEN wird aus der Umgebung des Agents entfernt. |
| Geheimnisse bleiben aus Logs | Jede Logzeile läuft durch einen Redaktor für das Token und die eingegebenen Geheimnisse (Werte ab 6 Zeichen). |
| Keine Weiterleitungen | Eine Weiterleitung würde den Authorization-Header dorthin erneut senden, wohin sie zeigt. Das Protokoll hat keine, also wird eine abgelehnt. |
| Kein Klartext von diesem Rechner weg | Nur https, http nur für localhost. |
| Fehler nennen kein Geheimnis | Ein Fehler trägt den HTTP-Status und den Fehlercode des Servers, nie einen Header, Body, ein Token oder eine URL. |
| Ein Bundle ist nicht vertrauenswürdig | Wird ganz abgelehnt, ohne etwas zu hinterlassen, wenn ein Eintrag einen absoluten Pfad, .., einen Backslash, einen Laufwerksbuchstaben oder ein NUL hat; ein Symlink, Gerät oder eine andere nicht reguläre Datei ist; doppelt vorkommt; unter .caldera/ liegt; oder wenn das Archiv mehr als 5000 Einträge hat, entpackt mehr als 100 MB ergibt (beim Entpacken gezählt, nicht aus Headern gelesen) oder eine Datei mehr als 50 MB, oder größer als 20 MB ist; oder eine vom Manifest genannte Datei fehlt; oder seine Version nicht die erfragte ist. |
| Der echte Pfad wird geprüft | Nach dem Auflösen muss jedes Ziel innerhalb des Verzeichnisses bleiben; jeder Pfad aus einem Manifest (workdir, status, Logs) wird über eine Prüfung des echten Pfads zusammengesetzt, ein Symlink kann also kein Lesen aus dem Verzeichnis des Agents hinaus oder nach .caldera/ hinein führen. |
| Die Prüfsumme wird verifiziert | Bei der Installation muss die sha256 der Antwort zum Bundle passen. Beim Update muss der Header X-Caldera-Sha256 passen; ein fehlender Header heißt keine Prüfung, die Runtime verlässt sich also darauf, dass Caldera ihn sendet. |
| Das Runtime-Update ist signiert | Eine Runtime-Datei wird erst ersetzt, nachdem ihre Signatur gegen den Schlüssel verifiziert wurde, der in der laufenden Runtime eingebaut ist. |
| Keine Shell | runtime.command und health.command sind Argumentlisten. |
| Versionen sind unveränderlich | Ein Versionsverzeichnis wird nach dem Bereitstellen nie bearbeitet. |
| Dotenv-Zeilen sind sicher | Ein Name oder Wert, der aus einer Zeile ausbrechen würde (ein Zeilenumbruch oder NUL), wird abgelehnt. |
Was sie nicht tut: Sie verschlüsselt secrets.env nicht (eine private Datei ist der
Schutz), und sie rotiert runtime.log nicht.
Bekannte Grenzen
- Das Erneuern des Tokens ist Handarbeit. Das Erneuern des Tokens in Caldera erreicht den
Rechner nicht;
agent.envbearbeiten und neu starten (siehe Agents betreiben). - Kein Container-Image der Runtime. Die Compose-Runtime ruft
docker composevom Host aus auf. - Keine Ersetzung in
.mcp.json. Die Runtime liefert die Datei unverändert aus und gibt die Variablen in der Umgebung mit; was die Datei liest, löst${NAME}auf. - Das 30-Minuten-Fenster ist fest.
- Eine Änderung an einem Geheimnis heißt,
secrets.envvon Hand zu bearbeiten und neu zu starten.