-
-
Notifications
You must be signed in to change notification settings - Fork 0
Jamf API Security and Diagnostics
Forsetti centralizes Jamf Pro access through shared app services. Feature modules receive a ModuleContext with the API gateway, credentials store, and diagnostics reporter instead of building independent networking or secret-storage stacks.
flowchart TD
Container["ForsettiFrameworkContainer"] --> Diagnostics["DiagnosticsCenter"]
Container --> Credentials["JamfCredentialsStore"]
Container --> Auth["JamfAuthenticationService"]
Container --> Gateway["JamfAPIGateway"]
Container --> ModuleRegistry["Dashboard ModuleRegistry"]
Container --> PackageManager["ModulePackageManager"]
Credentials --> Keychain["KeychainSecureStore"]
Auth --> TokenEndpoints["Jamf token endpoints"]
Gateway --> Auth
Gateway --> Credentials
Gateway --> Diagnostics
Gateway --> Jamf["Jamf Pro API"]
PackageManager --> ModuleRegistry
Credentials are stored through JamfCredentialsStore, backed by KeychainSecureStore.
| Behavior | Implementation detail |
|---|---|
| Storage key | jamf.credentials |
| Keychain class | Generic password |
| Keychain service | com.ravenforge.forsetti |
| Accessibility | kSecAttrAccessibleWhenUnlockedThisDeviceOnly |
| Legacy migration | Reads legacy service com.forsetti.app, writes current service, then deletes the legacy item. |
| Validation | Credentials are storage-sanitized and must be complete before persistence. |
Forsetti supports both API client credentials and username/password token exchange.
sequenceDiagram
participant Feature as Feature view model
participant Gateway as JamfAPIGateway
participant Store as JamfCredentialsStore
participant Auth as JamfAuthenticationService
participant Jamf as Jamf Pro
participant Diag as DiagnosticsCenter
Feature->>Gateway: request(path, method, query, body)
Gateway->>Store: loadCredentials()
Store-->>Gateway: JamfCredentials
Gateway->>Auth: accessToken(for credentials)
alt cached token valid
Auth-->>Gateway: bearer token
else token required
Auth->>Jamf: POST token endpoint
Jamf-->>Auth: token + expiration
Auth->>Diag: token refresh event
Auth-->>Gateway: bearer token
end
Gateway->>Jamf: authenticated API request
Jamf-->>Gateway: response
Gateway->>Diag: request-finish event
Gateway-->>Feature: decoded or raw data
JamfAPIGateway is actor-isolated and is the shared path for Modern API calls.
| Behavior | Detail |
|---|---|
| Token injection | Adds Bearer token from JamfAuthenticationService. |
| Credential loading | Reads current credentials from JamfCredentialsStore. |
| Concurrency limit | Uses AsyncSemaphore(maxConcurrency: 5). |
| Request diagnostics | Logs method, path, query count, body size, retry count, status code, response size, elapsed time, and failure category. |
| Unauthorized response | On 401, invalidates token and retries once with a fresh token. A second 401 reports credentials rejected. |
| Rate limiting | On 429, honors Retry-After when present or uses exponential backoff. |
| Pagination | Provides shared page traversal for endpoints using page and page-size. |
| Multipart upload | Builds multipart bodies for Jamf Inventory Preload and other authenticated upload flows. |
flowchart TD
Request["Feature calls Gateway.request"] --> Credentials["Load credentials"]
Credentials --> URLCheck{"Valid server URL?"}
URLCheck -->|no| ConfigError["throw invalidServerURL + diagnostics"]
URLCheck -->|yes| Token["Get cached or fresh token"]
Token --> Build["Build URLRequest"]
Build --> Permit["Acquire connection permit"]
Permit --> Send["URLSession.data(for:)"]
Send --> Status{"HTTP status"}
Status -->|2xx or expected body| Unwrap["unwrap response"]
Status -->|401| Refresh["Invalidate token and retry once"]
Refresh --> RetryStatus{"Retry status"}
RetryStatus -->|401| Reject["credentialsRejected"]
RetryStatus -->|other| Unwrap
Status -->|429 and retry left| Backoff["Retry-After or exponential backoff"]
Backoff --> Permit
Status -->|4xx or 5xx| APIError["typed JamfFrameworkError"]
Unwrap --> Done["Return data"]
ConfigError --> Diag["Diagnostics event"]
Reject --> Diag
APIError --> Diag
DiagnosticsCenter records structured diagnostic events and exports support evidence.
flowchart LR
Event["report(source, category, severity, message, metadata)"] --> Redact["Redact sensitive metadata"]
Redact --> Ring["In-memory ring buffer, 2000 events"]
Redact --> File["NDJSON file"]
Redact --> Unified["os.Logger unified log"]
File --> Rotate["10 MB size rotation, 3 generations"]
Ring --> View["DiagnosticsView"]
File --> Reload["Load from disk on first read"]
Reload --> View
View --> JSON["JSON export"]
View --> MD["Markdown export"]
| Storage layer | Purpose |
|---|---|
| In-memory ring buffer | Fast UI rendering and export snapshot. Capped at 2,000 events. |
| NDJSON file | Persistent app-owned diagnostics under the app container. Survives quit/crash. |
| Rotated NDJSON generations | Prevents unbounded log growth. |
| Unified log mirror | Makes events visible in Console and log show using com.ravenforge.forsetti.diagnostics. |
The diagnostics view model can prepare:
- Pretty-printed JSON report data.
- Markdown report data.
- Token privilege introspection via
GET /api/v1/auth. - Clear-log operation that removes app-owned diagnostics while leaving unified-log entries intact.
| Guardrail | Practical effect |
|---|---|
| Keychain-only credential persistence | Credentials are not written to app preferences or export files. |
| Device-only Keychain accessibility | Credential blobs do not sync through iCloud Keychain. |
| Token cache in actor memory | Tokens are not persisted and are invalidated when credentials change. |
| Shared gateway | Every feature gets consistent auth, retry, diagnostics, and throttling behavior. |
| Redacted diagnostics metadata | Events can be exported for support without dumping secrets. |
| Typed confirmation for high-risk actions | Destructive support workflows require explicit user confirmation before dispatch. |
| Family | Used by |
|---|---|
api/v1/oauth/token |
API client authentication flow. |
api/v1/auth/token |
Username/password token exchange. |
api/v1/auth/keep-alive |
Token refresh. |
api/v1/auth/invalidate-token |
Token invalidation. |
api/v3/computers-inventory |
Computer inventory search and reporting. |
api/v3/computers-inventory-detail/{id} |
Computer detail retrieval. |
api/v2/mobile-devices/detail |
Mobile inventory search and reporting. |
api/v2/mobile-devices/{id}/detail |
Mobile detail retrieval. |
api/v2/mdm/commands |
Support command queueing and command history. |
api/v2/local-admin-password/... |
LAPS account and password workflows. |
api/v3/computers-inventory/{id}/filevault |
FileVault key retrieval where authorized. |
api/v3/computers-inventory/{id}/view-recovery-lock-password |
Recovery Lock password retrieval where authorized. |
api/v3/computers-inventory/{id}/view-device-lock-pin |
Device Lock PIN retrieval where authorized. |
Forsetti Jamf Pro documentation. Keep module IDs, build commands, and security behavior aligned with the repository source before changing this wiki.