app

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 68 Imported by: 0

Documentation

Overview

Package app wires the reference server: storage, the MDM core, the Declarative Device Management engine, the adapters between the roles, the change notifier, a health endpoint, and a minimal admin API.

Why

The library packages are deliberately independent; something has to assemble them into a process that CI can build into a container and run on the far side of a real network hop. Phase 5 of the plan of record brings that assembly forward in minimal form so E2E-010 (split deployment) exercises our own binary on both sides of the wire; phase 8 grows it into the documented reference server. Three roles exist: "mdm" serves check-in and connect and reaches DDM either in-process or through ddm/adapter/proxyclient, "ddm" serves the engine behind ddm/adapter/proxyserver plus the admin API, and "all" runs both in one process. Roles that split across processes share one storage DSN. Push delivery and TLS termination are left to the deployment (phase 8).

References

  • Decision record 0023: docs/research/decisions/0023-ddm-adapters-and-wire-contract.md
  • Decision record 0025: docs/research/decisions/0025-reference-server-roles-and-container.md
  • Plan of record: docs/research/implementation_plan.md (phase 5, phase 8)
  • Threat model: docs/security/threat-model.md (trust boundaries 5 and 6)
  • E2E scenarios: docs/testing/e2e-scenarios.md (E2E-010, E2E-015)
  • Container: Dockerfile, scripts/testdb.sh (ddm-up)

Index

Constants

View Source
const (
	// IdentitySCEP is the default: the profile carries a SCEP payload and
	// a challenge password.
	IdentitySCEP = "scep"
	// IdentityACME issues the identity through ACME with Managed Device
	// Attestation, which needs no secret in the profile.
	IdentityACME = "acme"
)

Identity methods for the enrollment profile.

View Source
const (
	// ACMEPolicyAny issues to any device that produced a valid attestation
	// for a recognised client identifier.
	ACMEPolicyAny = "any"
	// ACMEPolicyDEP issues only to devices assigned to this organisation in
	// the device enrollment service.
	ACMEPolicyDEP = "dep"
	// ACMEPolicySIP additionally requires System Integrity Protection.
	ACMEPolicySIP = "sip"
)

ACME policies selectable from the environment.

View Source
const (
	ActionPutDeclaration       = "putDeclaration"
	ActionGetDeclaration       = "getDeclaration"
	ActionDeleteDeclaration    = "deleteDeclaration"
	ActionAssignSet            = "assignSet"
	ActionReadEnrollment       = "readEnrollment"
	ActionReadEnrollmentStatus = "readEnrollmentStatus"
	ActionNotify               = "notify"
	ActionManageDEP            = "manageDEP"
	ActionManageBusinessMgr    = "manageBusinessManager"
	ActionReadACME             = "readACME"
	ActionManagePrincipals     = "managePrincipals"
	ActionReadAudit            = "readAudit"
	ActionDisableEnrollment    = "disableEnrollment"
	ActionEnqueueCommand       = "enqueueCommand"
	ActionReadCommands         = "readCommands"
	ActionClearCommands        = "clearCommands"
	ActionPushEnrollment       = "pushEnrollment"
	ActionManagePushCerts      = "managePushCertificates"
	ActionExportEnrollments    = "exportEnrollments"
	ActionImportEnrollments    = "importEnrollments"
	ActionManagePolicies       = "managePolicies"
)

Admin action ids. Every admin route declares one, and the set below is the registry a stored policy is validated against, so a policy naming an action that no route serves is refused when it is written rather than silently never granting (decision record 0034).

View Source
const (
	PathMDM     = "/mdm"
	PathDDM     = "/ddm"
	PathHealthz = "/healthz"
	PathAdmin   = "/admin/v1/"
)

Paths served by the handler.

View Source
const (
	PathSCEP            = "/scep"
	PathACME            = "/acme"
	PathACMECredential  = "/enroll/acme-credential" // #nosec G101 -- a route, not a credential
	PathWellKnown       = discovery.WellKnownPath
	PathEnroll          = "/enroll/"             // + discovery version (mdm-byod, mdm-adde)
	PathADE             = "/enroll/ade"          // DEP profile url and configuration_web_url
	PathAuthenticate    = "/enroll/authenticate" // apple-as-web web-auth URL
	PathOAuth2Authorize = "/enroll/oauth2/authorize"
	PathOAuth2Token     = "/enroll/oauth2/token" // #nosec G101 -- a route, not a credential
	PathOIDCCallback    = "/enroll/oidc/callback"
	OAuth2ClientID      = "mdm"
	OAuth2RedirectURL   = accountdriven.CallbackScheme + ":/oauth2/redirection"
	OAuth2Scope         = "MDM"
)

Enrollment routes on the mdm and all roles (decision records 0027 to 0029).

View Source
const (
	EnvRole       = "MDM_ROLE"
	EnvListen     = "MDM_LISTEN"
	EnvStorage    = "MDM_STORAGE"
	EnvDSN        = "MDM_DSN"
	EnvDDMURL     = "MDM_DDM_URL"
	EnvDDMSendKey = "MDM_DDM_SEND_KEY"
	EnvDDMRecvKey = "MDM_DDM_RECV_KEY"
	EnvAdminToken = "MDM_ADMIN_TOKEN" // #nosec G101 -- the variable name, not a credential
	// EnvAdminStore opens the admin principal and policy store on the
	// process's own database. Off by default: it mounts the admin API.
	EnvAdminStore = "MDM_ADMIN_STORE"
	// EnvAudit writes a projected slog record for every event.
	EnvAudit = "MDM_AUDIT_LOG"
	// EnvWebhookURL receives an event per POST in the MicroMDM envelope.
	EnvWebhookURL = "MDM_WEBHOOK_URL"
	// EnvAuditStore persists every event to the audit trail.
	EnvAuditStore = "MDM_AUDIT_STORE"
	// EnvAuditRetention is how long audit records are kept.
	EnvAuditRetention = "MDM_AUDIT_RETENTION"
	// EnvWebhookHMACKey signs the webhook body.
	EnvWebhookHMACKey = "MDM_WEBHOOK_HMAC_KEY" // #nosec G101 -- the variable name, not a credential
	EnvCAFile         = "MDM_CA_FILE"
	EnvCertHeader     = "MDM_CERT_HEADER"
	// Enrollment routes (EnrollConfig).
	EnvPublicURL           = "MDM_PUBLIC_URL"
	EnvPushTopic           = "MDM_PUSH_TOPIC"
	EnvEnrollCACertFile    = "MDM_ENROLL_CA_CERT_FILE"
	EnvEnrollCAKeyFile     = "MDM_ENROLL_CA_KEY_FILE"
	EnvSCEPChallenge       = "MDM_SCEP_CHALLENGE" // #nosec G101 -- the variable name, not a credential
	EnvSCEPHMACKey         = "MDM_SCEP_HMAC_KEY"  // #nosec G101 -- the variable name, not a credential
	EnvProfileIdentifier   = "MDM_PROFILE_IDENTIFIER"
	EnvOrganization        = "MDM_ORGANIZATION"
	EnvDiscovery           = "MDM_DISCOVERY"
	EnvAccountDrivenMethod = "MDM_ACCOUNT_DRIVEN_METHOD"
	EnvOIDCIssuer          = "MDM_OIDC_ISSUER"
	EnvOIDCClientID        = "MDM_OIDC_CLIENT_ID"
	EnvOIDCClientSecret    = "MDM_OIDC_CLIENT_SECRET" // #nosec G101 -- the variable name, not a credential
	EnvADEAnchorFile       = "MDM_ADE_ANCHOR_FILE"
	EnvADEAudit            = "MDM_ADE_AUDIT"
	EnvRequireUserAuth     = "MDM_REQUIRE_USER_AUTH"
	// Apple Business Manager (AxMConfig).
	EnvAxMClientID = "MDM_AXM_CLIENT_ID"
	EnvAxMKeyID    = "MDM_AXM_KEY_ID"
	EnvAxMKeyFile  = "MDM_AXM_KEY_FILE"
	EnvAxMScope    = "MDM_AXM_SCOPE"
	EnvAxMBaseURL  = "MDM_AXM_BASE_URL"
	EnvAxMTokenURL = "MDM_AXM_TOKEN_URL" // #nosec G101 -- the variable name, not a credential
	// Device enrollment service (DEPConfig).
	EnvDEPBaseURL        = "MDM_DEP_BASE_URL"
	EnvDEPSyncInterval   = "MDM_DEP_SYNC_INTERVAL"
	EnvDEPAssignInterval = "MDM_DEP_ASSIGN_INTERVAL" // #nosec G101 -- the variable name, not a credential
	EnvDEPProfileURL     = "MDM_DEP_PROFILE_URL"
	EnvDEPUsePUT         = "MDM_DEP_USE_PUT"

	// Push: where APNs credentials come from, and how pushes are shaped.
	EnvPushSource   = "MDM_PUSH_SOURCE"
	EnvPushCertFile = "MDM_PUSH_CERT_FILE"
	EnvPushKeyFile  = "MDM_PUSH_KEY_FILE"
	EnvPushHost     = "MDM_PUSH_HOST"
	EnvPushCoalesce = "MDM_PUSH_COALESCE"
	EnvPushCertTTL  = "MDM_PUSH_CERT_TTL"
	// ACME and Managed Device Attestation (ACMEConfig).
	EnvIdentity       = "MDM_IDENTITY"
	EnvACMEPolicy     = "MDM_ACME_POLICY"
	EnvACMEKey        = "MDM_ACME_KEY"
	EnvACMEHMACKey    = "MDM_ACME_HMAC_KEY" // #nosec G101 -- the variable name, not a credential
	EnvACMEAnchorFile = "MDM_ACME_ANCHOR_FILE"
	EnvACMEUnattested = "MDM_ACME_ALLOW_UNATTESTED"
	EnvACMEIdentTTL   = "MDM_ACME_IDENTIFIER_TTL"

	EnvSubscriptions = "MDM_DDM_SUBSCRIPTIONS"
)

Environment variables read by ParseEnv.

View Source
const (
	DefaultRole    = RoleAll
	DefaultListen  = ":8080"
	DefaultStorage = "sqlite"
	DefaultDSN     = "mdm.db"
)

Defaults applied by ParseEnv when a variable is unset.

View Source
const (
	// PushSourceOff sends no pushes. A declaration change still queues a
	// command, which the device collects on its next check-in.
	PushSourceOff = "off"
	// PushSourceFile loads one certificate and key from disk.
	PushSourceFile = "file"
	// PushSourceStore loads certificates from the push certificate store,
	// reloading when an admin uploads a new one.
	PushSourceStore = "store"
)

Push certificate sources.

View Source
const ActionReadConfig = "readConfig"

ActionReadConfig is declared by the introspection routes. They are authenticated but not policy-gated (adminRoute.Introspection), so the action exists for the route table's sake rather than to be granted.

View Source
const BreakGlassActor = "break-glass"

BreakGlassActor is the audit actor for a request authenticated by the static MDM_ADMIN_TOKEN rather than by a stored principal. It is a fixed string, so an operator can alert on it: after the first principals exist, a record carrying this actor means someone used the standing root credential that should have been removed.

View Source
const DefaultAuditPruneInterval = time.Hour

DefaultAuditPruneInterval is how often the retention worker runs.

View Source
const DefaultAuditRetention = 90 * 24 * time.Hour

DefaultAuditRetention is how long records are kept when a retention is configured without a window. Long enough to investigate an incident reported weeks late, short enough that the table is not a liability.

View Source
const DefaultPushCoalesce = 5 * time.Second

DefaultPushCoalesce collapses repeated pushes to one enrollment inside this window, so a burst of declaration changes wakes a device once.

View Source
const MaxAdminBody = 1 << 20

MaxAdminBody bounds admin request bodies.

View Source
const MaxAxMSerials = 1000

MaxAxMSerials bounds one assignment request.

Variables

View Source
var (
	ErrBodyTooLarge = errors.New("app: body too large")
	ErrUnauthorized = errors.New("app: unauthorized")
	ErrBadChannel   = errors.New("app: channel must be device or user")
)

Admin API errors.

View Source
var (
	// ErrForbidden is a caller authenticated but not permitted.
	ErrForbidden = errors.New("app: forbidden")
	// ErrAdminUnconfigured is an admin API with neither a principal store nor
	// a static token. It is a Build error rather than a silently disabled
	// API, so a deployment cannot believe it is serving one when it is not
	// (decision record 0034).
	ErrAdminUnconfigured = errors.New("app: admin API needs MDM_ADMIN_TOKEN or an admin principal store")
)

Admin authorization errors.

View Source
var (
	AxMWaitInterval = 2 * time.Second
	AxMWaitTimeout  = 5 * time.Minute
)

Waits used by the admin API when the caller asks to wait.

View Source
var ErrBadACMERequest = errors.New("app: invalid ACME request")

ErrBadACMERequest is a malformed admin request.

View Source
var ErrBadAxMRequest = errors.New("app: invalid Business Manager request")

ErrBadAxMRequest reports an invalid assignment body.

View Source
var (
	ErrBadDEPRequest = errors.New("app: invalid DEP request")
)

DEP errors.

View Source
var ErrConfig = errors.New("app: invalid configuration")

ErrConfig reports an invalid configuration.

Functions

func ACMEKeyFromEnvForTests

func ACMEKeyFromEnvForTests(v string) ([]byte, error)

ACMEKeyFromEnvForTests exposes the key reader to tests of the wiring.

func ACMEStatusForTests

func ACMEStatusForTests(err error) int

ACMEStatusForTests exposes the error mapping to tests of the wiring.

func AdminActions

func AdminActions() []adminauth.Action

AdminActions describes every action, with operator-facing prose naming the consequence. `mdmctl policy actions` prints these, so an operator granting an action knows what they are granting rather than guessing from its name.

func AxMStatusForTests

func AxMStatusForTests(err error) int

AxMStatusForTests exposes the error mapping to tests of the wiring.

func CertificateFromPEM

func CertificateFromPEM(data []byte) (*x509.Certificate, error)

CertificateFromPEM reads the first certificate of a PEM bundle; the admin keypair route returns one for the portal upload.

func DEPStatusForTests

func DEPStatusForTests(err error) int

DEPStatusForTests exposes the error mapping to tests of the wiring.

func ParseDiscovery

func ParseDiscovery(s string) (map[discovery.ModelFamily]string, error)

ParseDiscovery reads "Mac=mdm-adde,iPhone=mdm-byod".

Types

type ACMEConfig

type ACMEConfig struct {
	// Policy is which devices may enroll: ACMEPolicyAny, ACMEPolicyDEP, or
	// ACMEPolicySIP. Empty means ACMEPolicyAny.
	Policy string
	// AllowUnattested issues on the client identifier alone to a device
	// that produced no attestation. Off by default.
	AllowUnattested bool
	// KeyType and KeySize are what the profile asks the device to generate.
	// Empty means an attestable elliptic curve key of 384 bits.
	KeyType string
	KeySize int64
	// HMACKey mints client identifiers. Empty falls back to the SCEP HMAC
	// key, and then to a key generated at startup, which is fine for one
	// process and wrong for several.
	HMACKey []byte
	// IdentifierTTL is how long a client identifier in a profile stays
	// usable. It has to cover the gap between handing a device its profile
	// and the device acting on it.
	IdentifierTTL time.Duration
	// AnchorFile is a PEM bundle of attestation anchors, for a lab with a
	// simulated device. Empty trusts Apple alone.
	AnchorFile string
	// Anchors is the parsed AnchorFile; tests set it directly.
	Anchors []*x509.Certificate
	// Store overrides the ACME store; default follows the storage backend.
	Store acme.Store
	// NonceTTL and OrderTTL default to the acme package's values.
	NonceTTL, OrderTTL time.Duration
}

ACMEConfig configures the ACME server and the identities it issues.

type App

type App struct {
	Handler  http.Handler
	Core     *service.Core
	Engine   *ddm.Engine
	Notifier *ddm.Notifier
	Store    storage.Store
	// AxM is the Business Manager client when configured.
	AxM *axm.Client
	// DEP is the device enrollment service; nil on the mdm role.
	DEP *dep.Client
	// Push wakes devices; nil when no push source is configured.
	Push *push.Notifier
	// contains filtered or unexported fields
}

App is a built process.

func Build

func Build(ctx context.Context, cfg Config) (*App, error)

Build validates cfg, opens storage, and wires the role.

func (*App) ACMEIdentifierForTests

func (a *App) ACMEIdentifierForTests(b acme.Binding) (string, error)

ACMEIdentifierForTests mints a client identifier, so a test can order without composing a whole enrollment profile.

func (*App) ACMEStoreForTests

func (a *App) ACMEStoreForTests() acme.Store

ACMEStoreForTests exposes the ACME store to tests of the wiring.

func (*App) AdminRoutes

func (a *App) AdminRoutes() []adminRoute

AdminRoutes returns the mounted admin route table: pattern, action, and family, with no handlers. It is what GET /routes serves and what mdmctl reads to explain a 404.

func (*App) Close

func (a *App) Close() error

Close releases storage, draining the event bus first when Build created it, so an asynchronous sink finishes delivering before the process exits.

func (*App) DEPStoreForTests

func (a *App) DEPStoreForTests() dep.Store

DEPStoreForTests exposes the DEP store to tests of the wiring.

func (*App) Run

func (a *App) Run(ctx context.Context) error

Run supervises every registered background loop until ctx is cancelled or one of them fails, whichever comes first. The HTTP listener is the caller's (cmd/mdmserver, or httptest in tests).

The first failure cancels its siblings so Run returns promptly rather than waiting for loops that only stop on cancellation. A loop that stops because the context ended is not a failure, and the two existing loops disagree on how they say so -- ddm.Notifier.Run returns ctx.Err(), depService.Run returns nil -- so cancellation is normalised here rather than in each loop.

func (*App) Workers

func (a *App) Workers() []WorkerState

Workers reports every supervised loop and whether it is running, in registration order. Readiness reads this; a worker that has stopped while the process keeps serving is exactly the state /healthz could not see.

type AxMConfig

type AxMConfig struct {
	ClientID string
	KeyID    string
	// KeyFile is the PEM private key downloaded from the portal; KeyPEM is
	// its content (tests set it directly).
	KeyFile string
	KeyPEM  []byte
	// Scope, BaseURL, and TokenURL override the defaults derived from the
	// client id (tests point them at a fake).
	Scope, BaseURL, TokenURL string
	HTTPClient               *http.Client
}

AxMConfig connects the reference server to Apple Business Manager or Apple School Manager (decision record 0030). Inactive until ClientID and KeyID are set with a key.

func (AxMConfig) Enabled

func (c AxMConfig) Enabled() bool

Enabled reports whether the client is configured.

type Config

type Config struct {
	Role    Role
	Listen  string
	Storage string // sqlite, postgres, mysql, inmem
	DSN     string // file path for sqlite
	// DDMURL, on the mdm role, forwards DeclarativeManagement check-ins to
	// a ddm role through proxyclient; empty means the local engine.
	DDMURL string
	// DDMSendKey signs what this role sends across the hop; DDMRecvKey
	// verifies what it receives.
	DDMSendKey, DDMRecvKey []byte
	// AdminToken enables the admin API on the ddm and all roles with a single
	// static credential that authenticates as root and bypasses policy.
	//
	// Alongside a principal store it is the break-glass credential, and it
	// keeps working rather than being superseded: an empty principal store
	// authenticates nobody, and the route that creates the first principal is
	// itself authorized, so without it there is no way in. Its use is audited
	// under the actor "break-glass" and logged at warn on every request.
	//
	// It has no expiry and cannot be revoked without restarting the process.
	// While it is set, every least-privilege property record 0034 claims is
	// void for whoever holds it, so a deployment sets it to create real
	// principals and then unsets it. An audit record with the actor
	// "break-glass" after that point is an incident. See
	// docs/operations/deployment.md.
	AdminToken string
	// AdminStore holds admin principals and Cedar policies. When set, an admin
	// request that does not present AdminToken is authenticated against it and
	// authorized by policy (decision record 0034). Injecting a store here
	// overrides AdminStoreEnabled, which is how tests supply a fake.
	AdminStore adminauth.Store
	// AdminStoreEnabled opens the principal and policy store on the process's
	// own database, so principals work in the shipped binary rather than only
	// where a caller injects AdminStore. Off by default: turning it on mounts
	// the admin API, which is a security change rather than a convenience.
	AdminStoreEnabled bool
	// CAFile is a PEM bundle of roots that device identities chain to;
	// the mdm role then verifies Mdm-Signature on every check-in and
	// connect. CARoots is the parsed form (tests set it directly).
	CAFile  string
	CARoots *x509.CertPool
	// CertHeader names a header carrying the client certificate from a
	// TLS-terminating proxy (httpapi.CertFromHeader). Used when no CA is
	// configured. With neither, the certificate must come from TLS on
	// this process (httpapi.CertFromTLS).
	CertHeader string
	// Subscriptions enables the synthesised status-subscriptions
	// declaration (decision record 0021).
	Subscriptions bool
	// Enroll turns the enrollment routes on (SCEP, discovery,
	// account-driven, ADE).
	Enroll EnrollConfig
	// AxM connects Apple Business Manager or Apple School Manager; its
	// admin routes live under the admin API on the ddm and all roles.
	AxM AxMConfig
	// DEP configures the device enrollment service client and worker;
	// its admin routes live under the admin API too.
	DEP DEPConfig
	// Push selects where APNs credentials come from. With no source the
	// server queues commands and never wakes a device.
	Push   PushConfig
	Logger *slog.Logger
	Clock  clock.Clock
	// Bus carries the typed events every state change publishes. When nil,
	// Build creates one so the sinks below have something to subscribe to;
	// pass one to observe events from outside the process.
	Bus *event.Bus
	// Sinks configures what subscribes to the bus.
	Sinks SinkConfig
}

Config is the process configuration; see ParseEnv for the MDM_* variables and cmd/mdmserver for the flags.

func ParseEnv

func ParseEnv(get func(string) string) (Config, error)

ParseEnv builds a Config from MDM_* variables through get (os.Getenv in the binary). Booleans accept strconv.ParseBool forms.

type DEPConfig

type DEPConfig struct {
	// BaseURL overrides https://mdmenrollment.apple.com (tests point it
	// at the fake service).
	BaseURL string
	// SyncInterval and AssignInterval drive the background worker;
	// zero disables it (the admin API can still sync and assign).
	SyncInterval, AssignInterval time.Duration
	// ProfileURL is the DEP profile url; default PublicURL + /enroll/ade.
	ProfileURL string
	// UsePUT sends PUT for profile assignment (simulators).
	UsePUT     bool
	HTTPClient *http.Client
	// Store overrides the DEP store (embedders with their own backend,
	// tests with a failing one); default follows Storage.
	Store dep.Store
}

DEPConfig connects the reference server to Apple's device enrollment service (decision record 0026). It is always available: accounts are created through the admin API, so nothing is required to enable it.

type EnrollConfig

type EnrollConfig struct {
	// PublicURL is the https base devices reach (profiles, discovery,
	// redirects).
	PublicURL string
	// Topic is the APNs topic written into enrollment profiles.
	Topic string
	// CACertFile and CAKeyFile are the PEM files of the enrollment CA that
	// issues device identities through SCEP; both empty means a
	// self-signed CA is generated at start (development only) and logged.
	CACertFile, CAKeyFile string
	// SCEPChallenge is the shared SCEP challenge (development); an HMAC
	// challenge derives one-time passwords when SCEPHMACKey is set instead.
	SCEPChallenge     string
	SCEPHMACKey       []byte
	ProfileIdentifier string
	Organization      string
	// Discovery maps a model family to a discovery version, for example
	// Mac=mdm-adde,iPhone=mdm-byod. Families absent are rejected.
	Discovery map[discovery.ModelFamily]string
	// AccountDrivenMethod is apple-as-web (default) or apple-oauth2.
	AccountDrivenMethod string
	// OIDC is the identity provider behind the web view, the apple-as-web
	// page, and the apple-oauth2 sign-in. Unset leaves only the
	// token-based ADE lane.
	OIDC OIDCConfig
	// ADEAnchorFile is a PEM bundle of MachineInfo signing anchors; empty
	// uses Apple's. ADEAudit logs verification failures and continues.
	ADEAnchorFile string
	ADEAudit      bool
	// RequireUserAuth gates user channels on UserAuthenticate (0029).
	RequireUserAuth bool
	// Identity is where an enrolled device's identity certificate comes
	// from: IdentitySCEP (the default) or IdentityACME. The ACME endpoints
	// are mounted either way, so a declarative credential can use them even
	// when enrollment profiles still carry SCEP.
	Identity string
	// ACME configures the ACME server and the identities it issues.
	ACME ACMEConfig
	// Anchors is the parsed ADEAnchorFile (tests set it directly).
	Anchors []*x509.Certificate
}

EnrollConfig turns the enrollment routes on. It is inactive until PublicURL and Topic are set.

func (EnrollConfig) Enabled

func (e EnrollConfig) Enabled() bool

Enabled reports whether enrollment routes are configured.

type OIDCConfig

type OIDCConfig struct {
	Issuer, ClientID, ClientSecret string
	// HTTPClient reaches the provider; tests point it at a fake.
	HTTPClient *http.Client
}

OIDCConfig is the relying-party configuration.

type PushConfig

type PushConfig struct {
	// Source is off, file, or store.
	Source string
	// CertFile and KeyFile are the PEM pair for the file source.
	CertFile, KeyFile string
	// Topic is the APNs topic for the file source. Empty derives it from the
	// certificate, which is what an operator should prefer: the topic lives
	// in the certificate's subject and typing it by hand is how it goes
	// wrong.
	Topic string
	// Host overrides the APNs endpoint, for the lab and for tests.
	Host string
	// Coalesce is the window repeated pushes collapse into; zero uses
	// DefaultPushCoalesce, negative disables coalescing.
	Coalesce time.Duration
	// CertTTL is how long a store-backed certificate is cached before its
	// version is rechecked.
	CertTTL time.Duration
	// Transport overrides the HTTP client per certificate, for tests.
	Transport func(tls.Certificate) *http.Client
	// Pusher overrides everything, for tests and for an embedder with its
	// own APNs path.
	Pusher push.Pusher
}

PushConfig selects where APNs credentials come from.

With no source the notifier is built without a pusher, so a declaration change queues a command and never wakes the device. The device collects it on its next check-in, which for an idle Mac can be hours.

func (PushConfig) Enabled

func (p PushConfig) Enabled() bool

Enabled reports whether pushes are sent.

type Role

type Role string

Role selects what a process serves.

const (
	RoleMDM Role = "mdm"
	RoleDDM Role = "ddm"
	RoleAll Role = "all"
)

Roles.

type SinkConfig

type SinkConfig struct {
	// Audit writes a projected slog record for every event. It is the
	// cheapest form of the threat model's repudiation control: attributable,
	// but only as durable as the log stream it is shipped to.
	Audit bool
	// WebhookURL receives a POST per event in the MicroMDM envelope, minus
	// the raw payload those servers include (event/sink explains why).
	WebhookURL string
	// WebhookHMACKey signs the webhook body when set.
	WebhookHMACKey []byte
	// Persist writes every event to the audit trail on the process's own
	// database. This is what makes the threat model's repudiation control
	// real: an slog record is only as durable as the log stream someone
	// remembered to ship, and proving who erased a device three weeks ago
	// needs a table.
	Persist bool
	// AuditStore overrides Persist with a caller's own trail.
	AuditStore audit.Store
	// Retention is how long records are kept. Zero keeps them forever, which
	// is a choice a deployment should make deliberately rather than inherit.
	Retention time.Duration
	// PruneInterval is how often retention runs; DefaultAuditPruneInterval
	// when unset.
	PruneInterval time.Duration
}

SinkConfig turns on the event sinks. Both are off by default: an audit log and a webhook are deployment choices, and a library consumer supplies its own subscribers.

func (SinkConfig) Enabled

func (s SinkConfig) Enabled() bool

Enabled reports whether anything subscribes.

type WorkerState

type WorkerState struct {
	Name    string
	Running bool
}

WorkerState reports one supervised loop and whether it is running.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL