Notary ist ein macOS-Compliance- und Hardening-Projekt mit Fokus auf lokale Prüfungen, strukturierte Findings und Jamf-orientierten Ergebnistransport. Die aktuelle Implementierung ist ein Swift-basierter Runner mit optionaler Remediation und einem Transporter, der Ergebnisse nur bei Änderungen oder im Heartbeat-Intervall weiterreicht.
Heute sind diese Kernelemente real vorhanden:
- Swift-CLI
notaryals eigentlicher Prüflauf - sichtbare
Notary.appals lokaler GUI-Einstiegspunkt - Persistenter Zustand in
/var/db/notary.plist - Jamf-Transport über Extension Attributes
- Token-Reuse für das Jamf-API-Token bis zum Ablauf
- Update-on-change für Findings und Compliance-Zustand
- Heartbeat für den Transporter nur alle 60 Minuten
Engagementals laufender Main-Loop-Service über LaunchDaemon- Report-only GUI für lokale Einsicht in Findings und Transportzustand
Die Begriffstrennung ist wichtig:
-
RunnerDer Runner ist der einzelne Prüfzyklus. Er lädt Konfiguration und Zustand, führt die Checks aus, berechnet Proof und Transportdaten und liefert einen abgeschlossenen Lauf zurück. -
EngagementDas Engagement ist das heutige Service-Laufzeitmodell. Es hält Timer, Profil-Reloads, Signal-Handling und wiederkehrende Runner-Zyklen zusammen.
Aktuell existieren im Produkt beide Ebenen:
Runnerfür One-shot-LäufeEngagementfür den dauerhaften Hintergrundbetrieb
Der aktuelle Ablauf im Service-Modell ist:
- Start des
Engagementüber LaunchDaemon oder eines einzelnen Runner-Laufs über CLI. - Laden von Konfiguration und gespeichertem Zustand aus der Plist.
- Ausführen der konfigurierten Checks.
- Erzeugen von Proof-Daten und Transportwerten.
- Optionales Jamf-Update, aber nur wenn sich Findings geändert haben oder der Heartbeat fällig ist.
- Schreiben des aktualisierten Zustands.
- Im
Engagementfolgt danach der nächste Zyklus über Timer oder Reload-Trigger.
Wichtig dabei:
- Der Heartbeat des Transporters beträgt aktuell
60 Minuten. - Die Werte
Notary Runner,Notary IssuesundNotary Compliancewerden in/var/db/notary.plistabgelegt. - Der Heartbeat-Timer wird nur nach erfolgreichem Schreiben der Transportdaten zurückgesetzt.
Der Transporter verwendet heute folgende Regeln:
- Ergebnisse werden mit dem zuletzt transportierten Stand verglichen.
- Ein Jamf-Update erfolgt nur bei Änderungen an Status, Findings oder Compliance-Wert.
- Falls keine Änderung vorliegt, wird frühestens nach 60 Minuten ein Heartbeat geschrieben.
- Das Jamf-Bearer-Token wird in der Plist wiederverwendet und erst bei Ablauf oder nach einem
401erneuert. - Die EA-Definitionen werden gecacht und mit Cooldown aktualisiert, um unnötige API-Last zu vermeiden.
Der zentrale Speicherort ist:
/var/db/notary.plistbeiroot
Für Entwicklungs- oder Nicht-Root-Läufe verwendet der SecurePlistStore eine Fallback-Datei unter /tmp, damit das Verhalten lokal testbar bleibt.
In der Plist liegen heute unter anderem:
- Jamf-Client-ID und Client-Secret
- zuletzt verwendetes Bearer-Token samt Ablaufzeit
- gecachte EA-Definitionen
- Zeitstempel des letzten erfolgreichen Transports
- zuletzt transportierte Werte für Runner, Issues und Compliance
Der Runner liest seine Managed-Preferences standardmäßig aus der Domain:
de.twocent.notary
Hilfreiche CLI-Optionen:
-
--dump-configZeigt den rohen effektiven Managed-Prefs-Snapshot. -
--dump-resolvedZeigt zusätzlich die aufgelösten Pentabool-Werte und normalisierte Parameter. -
-voder--verboseAktiviert erweitertes Logging. -
--developAktiviert das ausführlichste Logging.
Tools/deploy.sh richtet das heutige Betriebsmodell aus:
- schreibt oder aktualisiert
/var/db/notary.plist - hinterlegt Jamf-Zugangsdaten und Flags
- richtet einen LaunchDaemon für
--engagementein - setzt
KeepAliveundExitTimeOut=15 - startet das Service-Binary unter
/usr/local/libexec/notarymit--engagement --engagement-interval <sekunden>
Wichtige Eigenschaften des heutigen Deploy-Pfads:
- bestehende Jamf-Credentials in
/var/db/notary.plistwerden bei Updates weiterverwendet - die Plist wird atomisch über Temp-Datei und finalen Austausch aktualisiert
- temporär blockierte Locks werden intern erneut versucht
- verwaiste Lock-Verzeichnisse werden nach kurzer Altersschwelle bereinigt
- für den Schreibpfad werden nur macOS-Bordmittel verwendet, keine Command Line Tools
Wenn ein Client beim Deployment auffällig wird, lohnt sich zuerst der Blick auf diese typischen Ursachen:
-
Could not write domain /var/db/notary.plistFrühererdefaults-Schreibpfad. Der aktuelle Stand vonTools/deploy.shvermeidet diesen Fehler durch atomisches Schreiben ohnedefaults. -
xcode-select: error: No developer tools were foundHinweis auf einen veralteten Deploy-Script-Stand, der nochpython3voraussetzt. Der aktuelle Stand verwendet keine Entwicklerwerkzeuge. -
could not acquire plist lock for /var/db/notary.plistKurzzeitige Kollision zwischen Deployment und laufendem Service. Der aktuelle Deploy-Pfad enthält interne Retries und Stale-Lock-Bereinigung. Bleibt der Fehler bestehen, sollte geprüft werden, ob auf dem Client ältere Script-Versionen oder manuelle Eingriffe Locks hinterlassen. -
mktemp ... File existsHinweis auf einen älteren Deploy-Script-Stand mit nicht BSD-kompatiblem Template. Der aktuelle Stand bereinigt fehlerhafte Alt-Tempdateien und verwendet ein macOS-kompatiblesmktemp-Pattern.
Für Jamf-Policies ist deshalb wichtig:
Tools/deploy.shimmer zusammen mit dem aktuellen PKG-Stand aktualisieren- auf die im Script ausgegebene Deploy-Version achten, z. B.
Deploy Notary – v2.1.0 - bei Supportfällen Deploy-Version, Build-Label und den relevanten Ausschnitt aus dem Policy-Log gemeinsam betrachten
Für den API-basierten Transport muss in Jamf Pro ein API Client mit folgenden Rechten vorhanden sein:
Update Computer Extension AttributesUpdate ComputersRead ComputersRead Computer Extension AttributesCreate Computer Extension Attributes
Zusätzlich muss für diesen API Client eine Token-Lebensdauer von 86400 Sekunden gesetzt werden.
Wichtig für Beta-Tester:
- Wenn bereits ein bestehender Notary-API-Client verwendet wird, sollte die Token-Lebensdauer aktiv auf
86400angepasst werden. - Notary kann das Token lokal wiederverwenden, aber nur innerhalb der von Jamf erlaubten Laufzeit.
- Zu kurze Token-Laufzeiten erzeugen unnötige OAuth-Erneuerungen und verschlechtern genau das Verhalten, das mit dem Token-Reuse reduziert werden soll.
-
Sources/NotaryRunnerRunner, Checks, Jamf-Transport, Persistenz und LoggingWichtige Unterteilung im aktuellen Source-Pfad:
Corefür gemeinsame Fachlogik,Servicefür Runner/Engagement-Einstiege undUIfür die lokale App-Oberfläche -
ToolsHilfsskripte für Deployment, Versionierung und Schema-Generierung -
.versionSteuerdateien für Marketing-Version, Build-Label und Release-Kanal -
DokumentationArbeitsnotizen, Legacy-Material und Referenzen -
.githubIssue-Templates und GitHub-Projektstruktur
Die visuelle Richtung für Notary ist aktuell in zwei Referenzen festgehalten:
Diese Dateien definieren den derzeit bekannten Zielcharakter für GUI, Materialität, Informationshierarchie und das Motiv Proof im Shield.
Die Zielarchitektur ist jetzt nicht mehr nur vorgemerkt, sondern im Build angelegt:
- ein gemeinsamer
NotaryCorefür Checks, Proof, Transport, State und Konfiguration - ein GUI-Frontend für Report und späteren Configurator
- ein separates Service-/Daemon-Frontend für
Engagement - optional ein eigenständiges
NotaryCLIfür Debug, Admin und Automatisierung
Wichtig für diese Trennung:
- Der Daemon bleibt die schreibende Instanz und operative Quelle der Wahrheit.
- Die GUI startet keinen zweiten Engagement-Lauf, sondern liest, beobachtet und erklärt den vorhandenen Zustand.
- Zwei Prozesse mit gemeinsamem Core sind langfristig bevorzugt gegenüber einer dauerhaft gemischten GUI-/Daemon-Instanz desselben Laufzeitprozesses.
- Das SwiftPM-Paket baut jetzt getrennte Frontends für
NotaryAppundnotary, die sich denselbenNotaryCoreteilen.
Die wichtigsten Targets liegen im Makefile.
make build
make release
make staplemake build erzeugt vor dem Swift-Build die Laufzeit-Version aus den Dateien unter .version.
Der aktuelle Packaging-Zuschnitt ist:
Notary.appnach/Applications- Service-Binary
notarynach/usr/local/libexec - LaunchDaemon über
Tools/deploy.sh
Notary verwendet aktuell ein zweistufiges Versionsmodell:
- Marketing-Version: numerisch, z. B.
2.0 - Build-Label: intern, z. B.
1A148f
Die Werte werden aus folgenden Dateien abgeleitet:
.version/major_index.version/minor_letter.version/channel.version/build_number
Tools/gen_version.sh erzeugt daraus:
Sources/NotaryRunner/Version.generated.swift.version/version.mk
Die generierten Dateien sind bewusst nicht für Git vorgesehen. Damit bleibt das Repository stabil, während lokale Builds weiterhin ihre Build-Nummer fortschreiben können.
Der Kanal in .version/channel beschreibt die Reife eines Builds:
cLetzter Feature-Kanal. Hier dürfen neue Funktionen noch aufgenommen werden.bStabilisierungskanal. Ab hier werden nur noch Bugfixes, Zuverlässigkeits-, Sicherheits- und Packaging-Verbesserungen aufgenommen.aRelease-Candidate-Kanal. Keine neuen Features mehr, nur noch Go/No-Go-Fixes.- ohne Kanal-Suffix Finaler Release / Golden Master.
Praktische Merkregel:
- je höher der Buchstabe, desto weiter ist der Build vom finalen Release entfernt
- je niedriger der Buchstabe, desto näher ist der Build am finalen Release
Für Notary 2.0 gilt aktuell bewusst:
c-Builds sind die letzten Builds mit neuem Funktionsumfangb-Builds dienen nur noch der Steigerung von Stabilität, Zuverlässigkeit und Sicherheita-Builds sind Release Candidates
Hinweis:
Die interne Laufzeitversion wird aktuell aus NotaryVersion abgeleitet. Die ArgumentParser-Metadaten im CLI sollten künftig noch an das gleiche Modell angeglichen werden, damit keine sichtbaren Versionsunterschiede mehr entstehen.
Das Repository ist live unter:
Aktueller Startpunkt:
- Default-Branch:
main - Initialer Versions-Tag:
v2.0.0
Empfohlene Tag-Namenskonvention für künftige Releases:
v2.0.0v2.1.0v2.1.1
Das interne Build-Label bleibt davon unabhängig und eignet sich weiter für Jamf, Logs und Supportfälle.
Für den praktischen Workflow im Repository:
- Branching und Beitragsablauf: CONTRIBUTING.md
- Release-Ablauf: RELEASE.md
Einige Remediation-Schritte verwenden Apple-Systemwerkzeuge wie systemsetup. Auf aktuellen macOS-Versionen können diese Aktionen selbst als root durch TCC blockiert werden, wenn dem Runner oder dem aufrufenden Management-Prozess kein Full Disk Access gewährt wurde.
Beispiel:
Turning Remote Login on or off requires Full Disk Access privileges.
Ein PPPC-Profil sollte Full Disk Access für den Notary Runner oder den verwendeten Management-Agent bereitstellen, damit Remediation deterministisch und reproduzierbar funktioniert.
Wenn systemsetup durch TCC blockiert wird, kann der Runner abhängig von der Konfiguration den betroffenen Dienst best effort stoppen, z. B. per launchctl bootout für sshd.
Dieser Fallback ist nicht in jedem Fall persistent und dient als Sicherheitsnetz, falls PPPC fehlt oder fehlerhaft ist. Nach jeder Remediation wird der Zustand verifiziert. Bleibt der Zustand unklar oder schlägt die Verifikation fehl, wird das Ergebnis als FAIL gewertet.