Skip to content

Jamf API Security and Diagnostics

Jim Daley edited this page Jul 2, 2026 · 1 revision

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.

Service Composition

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
Loading

Credential Storage

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.

Authentication Flow

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
Loading

Request Behavior

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
Loading

Diagnostics Pipeline

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"]
Loading

Diagnostic Storage

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.

Diagnostic Exports

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.

Security Guardrails

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.

Main Jamf Endpoint Families

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.

Clone this wiki locally