Documentation
¶
Overview ¶
Package ddm is the Declarative Device Management engine: declarations and their canonical form, sets and membership, per-enrollment manifests and synchronisation tokens, status reports, and the change notifier.
Why ¶
Apple's declarative management moves policy evaluation onto the device: the server publishes declarations and a token that summarises them, the device pulls what changed and reports status back. Phase 5 of the plan of record adds that engine on top of the MDM core. This package owns the protocol semantics (tokens, the four endpoints, 404-means-remove, empty status responses, full-report replacement) and the storage contract, and leaves transport to the ddm/adapter packages and persistence to ddm/inmem and ddm/sqlstore. It relies on nothing from NanoMDM or MicroMDM; their behaviour is cited in the decision records only as evidence.
References ¶
- Decision record 0019: docs/research/decisions/0019-canonical-json-and-ddm-tokens.md
- Decision record 0020: docs/research/decisions/0020-ddm-engine-membership-and-storage.md
- Decision record 0021: docs/research/decisions/0021-status-reports-and-subscriptions.md
- Decision record 0022: docs/research/decisions/0022-change-notifier.md
- Plan of record: docs/research/implementation_plan.md (phase 5)
- Threat model: docs/security/threat-model.md
- Apple: https://developer.apple.com/documentation/devicemanagement/integrating-declarative-management
- Apple: https://developer.apple.com/documentation/devicemanagement/declarativemanagementrequest
- Apple: https://developer.apple.com/documentation/devicemanagement/devicemanagement-declarations
- Apple: https://developer.apple.com/documentation/devicemanagement/status-items
- Schema: third_party/device-management/declarative/protocol/*.yaml
- Schema: third_party/device-management/declarative/declarations/**, declarative/status/**
- Schema: third_party/device-management/mdm/checkin/declarativemanagement.yaml
- RFC 8785 (JSON Canonicalization Scheme): https://www.rfc-editor.org/rfc/rfc8785
Index ¶
- Constants
- Variables
- func DeclarationsToken(refs []DeclarationRef) string
- func ParseKind(s string) (schemaddm.Kind, error)
- func RenderDeclaration(canonical []byte, serverToken string) ([]byte, error)
- func RenderDeclarationItems(snap *Snapshot) ([]byte, error)
- func RenderTokens(token string, at time.Time) ([]byte, error)
- func TokenFor(canonical []byte) string
- type AssignmentStore
- type Change
- type ChangeStore
- type Config
- type Declaration
- type DeclarationQuery
- type DeclarationRef
- type DeclarationStatus
- type DeclarationStore
- type DeclarationVersion
- type DrainResult
- type Endpoint
- type Engine
- func (e *Engine) AddToSet(ctx context.Context, set, identifier string) (bool, error)
- func (e *Engine) AssignDeclaration(ctx context.Context, id mdm.EnrollmentID, identifier string) (bool, error)
- func (e *Engine) AssignSet(ctx context.Context, id mdm.EnrollmentID, set string) (bool, error)
- func (e *Engine) ClearEnrollment(ctx context.Context, id mdm.EnrollmentID) error
- func (e *Engine) ClientCapabilities(ctx context.Context, id mdm.EnrollmentID) (*status.ManagementClientCapabilitiesCapabilities, error)
- func (e *Engine) Declaration(ctx context.Context, id mdm.EnrollmentID, kind schemaddm.Kind, ...) ([]byte, error)
- func (e *Engine) DeclarationItems(ctx context.Context, id mdm.EnrollmentID) ([]byte, error)
- func (e *Engine) DeclarationSets(ctx context.Context, identifier string) ([]string, error)
- func (e *Engine) DeclarationStatus(ctx context.Context, id mdm.EnrollmentID) ([]DeclarationStatus, error)
- func (e *Engine) DeclarationStatusByIdentifier(ctx context.Context, identifier string, p storage.Page) (storage.Result[EnrollmentDeclarationStatus], error)
- func (e *Engine) DeleteDeclaration(ctx context.Context, identifier string) error
- func (e *Engine) DeleteSet(ctx context.Context, name string) error
- func (e *Engine) EnrollmentDeclarations(ctx context.Context, id mdm.EnrollmentID) ([]string, error)
- func (e *Engine) EnrollmentSets(ctx context.Context, id mdm.EnrollmentID) ([]string, error)
- func (e *Engine) GetDeclaration(ctx context.Context, identifier string) (*Declaration, error)
- func (e *Engine) GetSet(ctx context.Context, name string) (*Set, error)
- func (e *Engine) Handle(ctx context.Context, id mdm.EnrollmentID, endpoint string, data []byte) (Response, error)
- func (e *Engine) ListDeclarations(ctx context.Context, q DeclarationQuery, p storage.Page) (storage.Result[Declaration], error)
- func (e *Engine) ListSets(ctx context.Context, p storage.Page) (storage.Result[Set], error)
- func (e *Engine) Manifest(ctx context.Context, id mdm.EnrollmentID) (*Snapshot, error)
- func (e *Engine) PruneVersions(ctx context.Context) (int64, error)
- func (e *Engine) PutDeclaration(ctx context.Context, raw []byte) (*Declaration, bool, error)
- func (e *Engine) PutSet(ctx context.Context, name string) (bool, error)
- func (e *Engine) RemoveFromSet(ctx context.Context, set, identifier string) (bool, error)
- func (e *Engine) SetDeclarations(ctx context.Context, set string) ([]string, error)
- func (e *Engine) SetEnrollments(ctx context.Context, set string, p storage.Page) (storage.Result[mdm.EnrollmentID], error)
- func (e *Engine) Status(ctx context.Context, id mdm.EnrollmentID, body []byte) (*StatusOutcome, error)
- func (e *Engine) StatusErrors(ctx context.Context, id mdm.EnrollmentID, p storage.Page) (storage.Result[StatusError], error)
- func (e *Engine) StatusReports(ctx context.Context, id mdm.EnrollmentID, p storage.Page) (storage.Result[StatusReportRecord], error)
- func (e *Engine) StatusValues(ctx context.Context, id mdm.EnrollmentID, q StatusValueQuery, p storage.Page) (storage.Result[StatusValue], error)
- func (e *Engine) Store() Store
- func (e *Engine) Tokens(ctx context.Context, id mdm.EnrollmentID) ([]byte, error)
- func (e *Engine) Touch(ctx context.Context, ids []mdm.EnrollmentID, reason string) error
- func (e *Engine) UnassignDeclaration(ctx context.Context, id mdm.EnrollmentID, identifier string) (bool, error)
- func (e *Engine) UnassignSet(ctx context.Context, id mdm.EnrollmentID, set string) (bool, error)
- type Enqueuer
- type EnrollmentDeclarationStatus
- type Expander
- type Notifier
- type NotifierConfig
- type Op
- type Pusher
- type Resolver
- type Response
- type ServiceHook
- type Set
- type SetStore
- type Snapshot
- type SnapshotItem
- type SnapshotStore
- type StatusError
- type StatusOutcome
- type StatusReportRecord
- type StatusStore
- type StatusUpdate
- type StatusValue
- type StatusValueQuery
- type Store
- type Subscriptions
- type TokenSource
- type Tx
Constants ¶
const ( ReasonDeclaration = "declaration" ReasonSet = "set" ReasonAssignment = "assignment" ReasonTouch = "touch" )
Change reasons recorded on ddm_changes rows.
const ( DefaultMaxStatusBytes = 1 << 20 DefaultKeepReports = 10 )
Defaults.
const ( DefaultNotifyWindow = 2 * time.Second DefaultNotifyPoll = time.Second DefaultNotifyBatch = 500 )
Defaults for NotifierConfig.
const DefaultDedupeKey = "ddm"
DefaultDedupeKey is the dedupe key NotifierConfig.DedupeKey uses when it is not set. It suppresses a second DeclarativeManagement while one is still pending for the same enrollment.
Suppression is safe for this command specifically, and only for it: the command is a doorbell, not a payload. A device that receives it fetches the current tokens and declaration items, so a pending command already carries every change made since it was queued. It would not be safe for a command whose payload is the instruction, which is why the key is opt-in per caller rather than a property of the queue.
const MaxIdentifierBytes = 64
MaxIdentifierBytes is Apple's guidance for Identifier and ServerToken values ("should not exceed 64 octets").
const StatusItemClientCapabilities = status.StatusItemTypeManagementClientCapabilities
StatusItemClientCapabilities is the status item devices always report.
const StatusItemDeclarations = status.StatusItemTypeManagementDeclarations
StatusItemDeclarations is the status item carrying per-declaration state.
const SubscriptionIdentifier = "com.deploymenttheory.mdm.status-subscriptions"
SubscriptionIdentifier names the synthesised status-subscriptions declaration. An admin-supplied declaration with this identifier replaces the synthesised one.
const TimestampLayout = "2006-01-02T15:04:05Z"
TimestampLayout is the SyncTokens Timestamp format: whole seconds, UTC.
Variables ¶
var ( ErrNotFound = errors.New("ddm: not found") ErrConflict = errors.New("ddm: conflict") ErrInvalid = errors.New("ddm: invalid argument") ErrUnknownType = errors.New("ddm: unknown declaration type") ErrInvalidDeclaration = errors.New("ddm: declaration failed validation") ErrBadEndpoint = errors.New("ddm: malformed endpoint") ErrStatusTooLarge = errors.New("ddm: status report exceeds limit") ErrStatusMalformed = errors.New("ddm: malformed status report") ErrResolver = errors.New("ddm: membership resolver failed") ErrExpander = errors.New("ddm: expander failed") ErrNotifier = errors.New("ddm: notifier") )
Errors shared by the engine and every store backend.
var DefaultSubscriptionBaseline = []string{
"device.identifier.serial-number", "device.identifier.udid",
"device.model.family", "device.model.identifier", "device.model.marketing-name",
"device.operating-system.build-version", "device.operating-system.family",
"device.operating-system.marketing-name", "device.operating-system.version",
"management.client-capabilities", "management.declarations",
}
DefaultSubscriptionBaseline is used until a device reports which status items it supports.
var DefaultSubscriptionExclude = []string{"test."}
DefaultSubscriptionExclude drops Apple's test items from subscriptions.
var ErrNoStore = errors.New("ddm: store is required")
ErrNoStore is returned by New when Config.Store is nil.
var ErrNotifierConfig = errors.New("ddm: notifier needs Store, Tokens, and Enqueuer")
ErrNotifierConfig reports a missing required dependency.
var StandaloneKinds = []schemaddm.Kind{schemaddm.KindActivation, schemaddm.KindAsset, schemaddm.KindConfiguration, schemaddm.KindManagement}
StandaloneKinds are the declaration families a device fetches by name.
Functions ¶
func DeclarationsToken ¶
func DeclarationsToken(refs []DeclarationRef) string
DeclarationsToken derives the manifest token from the sorted refs: sha256 over each kind, identifier, and server token written with a 4-byte length prefix. It is independent of input order and of the wall clock, distinguishes ("ab","c") from ("a","bc"), and is 64 hex characters, within Apple's 64-octet guidance (decision record 0019).
func RenderDeclaration ¶
RenderDeclaration returns the wire form of a declaration: its canonical members plus the ServerToken the device must echo back.
func RenderDeclarationItems ¶
RenderDeclarationItems renders a snapshot as a DeclarationItemsResponse.
func RenderTokens ¶
RenderTokens renders {"SyncTokens":{"DeclarationsToken":..,"Timestamp":..}}.
Types ¶
type AssignmentStore ¶
type AssignmentStore interface {
AssignSet(ctx context.Context, id mdm.EnrollmentID, set string, at time.Time) (changed bool, err error)
UnassignSet(ctx context.Context, id mdm.EnrollmentID, set string) (changed bool, err error)
EnrollmentSets(ctx context.Context, id mdm.EnrollmentID) ([]string, error)
SetEnrollments(ctx context.Context, set string, p storage.Page) (storage.Result[mdm.EnrollmentID], error)
AssignDeclaration(ctx context.Context, id mdm.EnrollmentID, identifier string, at time.Time) (changed bool, err error)
UnassignDeclaration(ctx context.Context, id mdm.EnrollmentID, identifier string) (changed bool, err error)
// EnrollmentDeclarations lists direct assignments only, sorted.
EnrollmentDeclarations(ctx context.Context, id mdm.EnrollmentID) ([]string, error)
// StaticDeclarations is the union of direct assignments and set members,
// deduplicated and sorted by identifier. Empty for an unknown id.
StaticDeclarations(ctx context.Context, id mdm.EnrollmentID) ([]Declaration, error)
// AffectedEnrollments lists every enrollment whose static membership
// includes any of the identifiers or sets, deduplicated and sorted.
AffectedEnrollments(ctx context.Context, identifiers, sets []string) ([]mdm.EnrollmentID, error)
}
AssignmentStore binds enrollments to sets and to single declarations.
type Change ¶
type Change struct {
Seq int64
ID mdm.EnrollmentID
Reason string
CreatedAt time.Time
Attempts int
LastError string
NextAttemptAt time.Time
}
Change is a pending notification for one enrollment.
type ChangeStore ¶
type ChangeStore interface {
// RecordChanges appends one row per id.
RecordChanges(ctx context.Context, ids []mdm.EnrollmentID, reason string, at time.Time) error
// PendingChanges returns rows due at or before now, oldest first.
PendingChanges(ctx context.Context, now time.Time, limit int) ([]Change, error)
CompleteChanges(ctx context.Context, seqs []int64) error
// FailChanges records the error and the next attempt time; rows are
// never deleted by a failure.
FailChanges(ctx context.Context, seqs []int64, msg string, nextAttempt time.Time) error
// ChangeStats counts rows due now and rows that have failed at least
// once.
ChangeStats(ctx context.Context, now time.Time) (pending, failed int64, err error)
}
ChangeStore queues notifications.
type Config ¶
type Config struct {
Store Store
Resolvers []Resolver
Expander Expander
Bus *event.Bus
Clock clock.Clock
Logger *slog.Logger
// Target supplies the validation target for uploads; nil validates for
// any OS.
Target func(ctx context.Context) support.Target
// MaxStatusBytes bounds a status report; default 1 MiB.
MaxStatusBytes int
// KeepReports bounds raw status reports kept per enrollment; default 10.
KeepReports int
Subscriptions Subscriptions
}
Config builds an Engine.
type Declaration ¶
type Declaration struct {
Identifier string
Type string
Kind schemaddm.Kind
ServerToken string
Canonical []byte
CreatedAt time.Time
// UpdatedAt is the last time ServerToken changed.
UpdatedAt time.Time
}
Declaration is a stored declaration: the canonical JSON of {Identifier, Payload, Type} and the token derived from it.
func ParseDeclaration ¶
func ParseDeclaration(raw []byte, target support.Target) (*Declaration, error)
ParseDeclaration validates an uploaded declaration and derives its canonical bytes and ServerToken (decision record 0019). The upload's own ServerToken is ignored: tokens are derived, never authored. Type must be one of the standalone families in schema/ddm and the generated Validate must pass for target.
type DeclarationQuery ¶
DeclarationQuery filters ListDeclarations. Zero values mean "any".
type DeclarationRef ¶
DeclarationRef names one declaration in a manifest.
func SortRefs ¶
func SortRefs(refs []DeclarationRef) []DeclarationRef
SortRefs orders refs by (kind, identifier, token), the order every manifest and token computation uses.
type DeclarationStatus ¶
type DeclarationStatus struct {
Kind schemaddm.Kind
Identifier string
ServerToken string
Active bool
// Valid is unknown, invalid, or valid as the device reported it.
Valid string
// Reasons is the raw JSON array of reasons, nil when none.
Reasons []byte
FirstSeen time.Time
LastSeen time.Time
}
DeclarationStatus is what a device last reported about one declaration.
type DeclarationStore ¶
type DeclarationStore interface {
// PutDeclaration upserts by Identifier. changed is false when the stored
// ServerToken already equals d.ServerToken; nothing is written then.
// ErrConflict when the identifier exists with a different Kind. Every
// accepted change also records a DeclarationVersion.
PutDeclaration(ctx context.Context, d *Declaration) (changed bool, err error)
// GetDeclaration returns the current revision or ErrNotFound.
GetDeclaration(ctx context.Context, identifier string) (*Declaration, error)
// GetDeclarationVersion returns one revision or ErrNotFound.
GetDeclarationVersion(ctx context.Context, identifier, serverToken string) (*DeclarationVersion, error)
// DeleteDeclaration removes the declaration, its versions, set
// memberships, and direct assignments. ErrNotFound when absent.
DeleteDeclaration(ctx context.Context, identifier string) error
// ListDeclarations pages by identifier.
ListDeclarations(ctx context.Context, q DeclarationQuery, p storage.Page) (storage.Result[Declaration], error)
// PruneVersions deletes revisions that are neither current nor named by
// a snapshot item and returns how many.
PruneVersions(ctx context.Context) (int64, error)
}
DeclarationStore persists declarations and their revisions.
type DeclarationVersion ¶
type DeclarationVersion struct {
Identifier string
Type string
ServerToken string
Canonical []byte
CreatedAt time.Time
}
DeclarationVersion is one revision of a declaration, kept so a fetch can serve the exact bytes a manifest advertised.
type DrainResult ¶
type DrainResult struct {
// Deferred enrollments were left for a later drain because a change
// arrived within Window.
Deferred int
// Queued enrollments received a new command; Deduped ones already had
// one pending and were pushed anyway; Skipped ones cannot be
// commanded (disabled or unknown) and were completed without a push.
Queued, Deduped, Skipped int
// Failed enrollments had their change rows scheduled for a retry.
Failed int
// Pushed counts enrollments handed to the Pusher.
Pushed int
}
DrainResult counts what one drain did.// DrainResult counts what one drain did.
func (DrainResult) Empty ¶
func (r DrainResult) Empty() bool
Empty reports a drain that did nothing, so a caller can skip reporting it.
type Endpoint ¶
type Endpoint struct {
Op Op
// Kind and Identifier are set for OpDeclaration only.
Kind schemaddm.Kind
Identifier string
}
Endpoint is a parsed DeclarativeManagement Endpoint value.
func ParseEndpoint ¶
ParseEndpoint parses "tokens", "declaration-items", "status", or "declaration/<kind>/<identifier>" where kind is activation, asset, configuration, or management. Anything else is ErrBadEndpoint.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine serves declarative management for enrollments.
func (*Engine) AssignDeclaration ¶
func (e *Engine) AssignDeclaration(ctx context.Context, id mdm.EnrollmentID, identifier string) (bool, error)
AssignDeclaration binds one declaration directly to an enrollment.
func (*Engine) ClearEnrollment ¶
ClearEnrollment forgets everything the engine holds for one enrollment: set and declaration assignments, the snapshot, status, and pending changes. Declarations and sets themselves are untouched.
func (*Engine) ClientCapabilities ¶
func (e *Engine) ClientCapabilities(ctx context.Context, id mdm.EnrollmentID) (*status.ManagementClientCapabilitiesCapabilities, error)
ClientCapabilities decodes the last reported management.client-capabilities item, or ErrNotFound when the device never reported it. The item is read defensively (decision record 0021, claim 7): a member that does not fit Apple's schema is logged and left empty rather than failing the caller, because devices have sent partial or oddly shaped capabilities and the check-in path must keep serving them.
func (*Engine) Declaration ¶
func (e *Engine) Declaration(ctx context.Context, id mdm.EnrollmentID, kind schemaddm.Kind, identifier string) ([]byte, error)
Declaration serves one declaration as the enrollment's manifest advertised it. ErrNotFound (404 on the wire) when the declaration is not in the manifest, has the wrong kind, or was deleted since.
func (*Engine) DeclarationItems ¶
DeclarationItems renders the DeclarationItemsResponse for an enrollment with all four arrays present, as Apple's schema requires.
func (*Engine) DeclarationSets ¶
DeclarationSets lists the sets containing a declaration.
func (*Engine) DeclarationStatus ¶
func (e *Engine) DeclarationStatus(ctx context.Context, id mdm.EnrollmentID) ([]DeclarationStatus, error)
DeclarationStatus lists what an enrollment last reported per declaration.
func (*Engine) DeclarationStatusByIdentifier ¶
func (e *Engine) DeclarationStatusByIdentifier(ctx context.Context, identifier string, p storage.Page) (storage.Result[EnrollmentDeclarationStatus], error)
DeclarationStatusByIdentifier pages through every enrollment's status for one declaration.
func (*Engine) DeleteDeclaration ¶
DeleteDeclaration removes a declaration and notifies every enrollment that had it, so devices receive 404 on their next fetch and drop it.
func (*Engine) EnrollmentDeclarations ¶
EnrollmentDeclarations lists an enrollment's direct assignments.
func (*Engine) EnrollmentSets ¶
EnrollmentSets lists an enrollment's sets.
func (*Engine) GetDeclaration ¶
GetDeclaration returns the current revision.
func (*Engine) Handle ¶
func (e *Engine) Handle(ctx context.Context, id mdm.EnrollmentID, endpoint string, data []byte) (Response, error)
Handle serves one DeclarativeManagement check-in for the adapters. Malformed endpoints are ErrBadEndpoint; a status endpoint needs data.
func (*Engine) ListDeclarations ¶
func (e *Engine) ListDeclarations(ctx context.Context, q DeclarationQuery, p storage.Page) (storage.Result[Declaration], error)
ListDeclarations pages through declarations.
func (*Engine) PruneVersions ¶
PruneVersions deletes declaration revisions nothing references.
func (*Engine) PutDeclaration ¶
PutDeclaration validates and stores a declaration. changed is false when an equivalent declaration was already stored, in which case no enrollment is notified. Every affected enrollment is queued for notification inside the same transaction.
func (*Engine) RemoveFromSet ¶
RemoveFromSet removes a declaration from a set and notifies the set's enrollments.
func (*Engine) SetDeclarations ¶
SetDeclarations lists a set's members.
func (*Engine) SetEnrollments ¶
func (e *Engine) SetEnrollments(ctx context.Context, set string, p storage.Page) (storage.Result[mdm.EnrollmentID], error)
SetEnrollments pages through a set's enrollments.
func (*Engine) Status ¶
func (e *Engine) Status(ctx context.Context, id mdm.EnrollmentID, body []byte) (*StatusOutcome, error)
Status stores a device's status report (decision record 0021): the raw report, every status item as canonical JSON keyed by its nested path, the typed management.declarations rows, and the Errors array. A full report replaces the enrollment's status; declarations absent from it are removed.
func (*Engine) StatusErrors ¶
func (e *Engine) StatusErrors(ctx context.Context, id mdm.EnrollmentID, p storage.Page) (storage.Result[StatusError], error)
StatusErrors pages through an enrollment's reported errors, newest first.
func (*Engine) StatusReports ¶
func (e *Engine) StatusReports(ctx context.Context, id mdm.EnrollmentID, p storage.Page) (storage.Result[StatusReportRecord], error)
StatusReports pages through retained raw reports, newest first.
func (*Engine) StatusValues ¶
func (e *Engine) StatusValues(ctx context.Context, id mdm.EnrollmentID, q StatusValueQuery, p storage.Page) (storage.Result[StatusValue], error)
StatusValues pages through an enrollment's status item values.
func (*Engine) Touch ¶
Touch queues a notification for enrollments without changing their declarations: the first DeclarativeManagement command that enables the engine on a device, or a resolver-driven change.
func (*Engine) UnassignDeclaration ¶
func (e *Engine) UnassignDeclaration(ctx context.Context, id mdm.EnrollmentID, identifier string) (bool, error)
UnassignDeclaration removes a direct binding.
func (*Engine) UnassignSet ¶
UnassignSet removes a set binding and notifies the enrollment.
type Enqueuer ¶
type Enqueuer interface {
Enqueue(ctx context.Context, ids []mdm.EnrollmentID, cmd *mdm.Command, o storage.EnqueueOptions) (storage.EnqueueResult, error)
}
Enqueuer queues a command for enrollments; *service.Core satisfies it.
type EnrollmentDeclarationStatus ¶
type EnrollmentDeclarationStatus struct {
ID mdm.EnrollmentID
DeclarationStatus
}
EnrollmentDeclarationStatus is DeclarationStatus for one enrollment, returned by identifier-centred queries.
type Expander ¶
type Expander interface {
Expand(ctx context.Context, id mdm.EnrollmentID, d *Declaration) ([]byte, error)
}
Expander may rewrite a declaration's canonical bytes for one enrollment (variable substitution). Returning nil, or bytes equal to d.Canonical, means unchanged. The bytes returned must be a JSON object; they are canonicalised and the served token is derived from them.
type Notifier ¶
type Notifier struct {
// contains filtered or unexported fields
}
Notifier turns committed change rows into DeclarativeManagement commands and pushes (decision record 0022).
func NewNotifier ¶
func NewNotifier(cfg NotifierConfig) (*Notifier, error)
NewNotifier validates the configuration and applies defaults.
func (*Notifier) DrainOnce ¶
func (n *Notifier) DrainOnce(ctx context.Context) (DrainResult, error)
DrainOnce processes the due change rows once. Store failures are returned; per-enrollment failures are recorded on the rows with backoff.
type NotifierConfig ¶
type NotifierConfig struct {
Store ChangeStore
Tokens TokenSource
Enqueuer Enqueuer
Pusher Pusher
Bus *event.Bus
Clock clock.Clock
Logger *slog.Logger
// Window defers an enrollment while its newest change is younger than
// this, so a burst of uploads becomes one command. Default 2s.
Window time.Duration
// Poll is how often Run drains without a Kick. Default 1s.
Poll time.Duration
// Batch bounds the change rows read per drain. Default 500.
Batch int
// Backoff maps the attempt count to the retry delay. Default
// storage.NotNowBackoff.
Backoff func(attempt int) time.Duration
// DedupeKey suppresses a new DeclarativeManagement while one is still
// pending for the same enrollment. Nil uses DefaultDedupeKey; an empty
// string turns suppression off, so every drain queues a command.
//
// It is a pointer for the same reason service.Config.ValidateTargets is:
// the zero value has to mean "not set" so that "" can mean "off".
// Whether to suppress is the consumer's decision, not this package's.
DedupeKey *string
// OnDrain, when set, is called with the outcome of every drain. A
// suppressed command is counted in DrainResult.Deduped and is otherwise
// invisible, which is the whole problem with suppression: it has to be
// observable to be defensible.
OnDrain func(ctx context.Context, res DrainResult)
}
NotifierConfig configures NewNotifier. Store, Tokens, and Enqueuer are required; Pusher, Bus, and Logger are optional.
type Op ¶
type Op int
Op is the operation a DeclarativeManagement check-in requests.
Operations, in the order Apple lists the Endpoint values.
type Pusher ¶
type Pusher interface {
Notify(ctx context.Context, ids []mdm.EnrollmentID) (map[mdm.EnrollmentID]push.Result, error)
}
Pusher sends APNs wake-ups; *push.Notifier satisfies it.
type Resolver ¶
Resolver adds declarations to an enrollment's manifest dynamically, for example by device attribute or by an external group. Errors fail closed: serving that enrollment returns ErrResolver rather than a partial manifest.
type Response ¶
type Response struct {
Body []byte
// Status is 200, or 404 when a declaration is not part of the
// enrollment's manifest (Apple: the device then removes it).
Status int
}
Response is what a device-facing adapter writes back.
type ServiceHook ¶
type ServiceHook struct {
// contains filtered or unexported fields
}
ServiceHook clears DDM state when an enrollment checks out or re-authenticates (the device's user channels included), so a wiped or re-enrolled device never inherits declarations (KMFDDM #41).
func NewServiceHook ¶
func NewServiceHook(e *Engine, enrollments storage.EnrollmentStore, log *slog.Logger) *ServiceHook
NewServiceHook builds the hook; enrollments is used to find a device's user channels.
type SetStore ¶
type SetStore interface {
// PutSet creates the set; created is false when it already existed.
PutSet(ctx context.Context, name string, at time.Time) (created bool, err error)
// DeleteSet removes the set, its memberships, and its assignments.
DeleteSet(ctx context.Context, name string) error
GetSet(ctx context.Context, name string) (*Set, error)
ListSets(ctx context.Context, p storage.Page) (storage.Result[Set], error)
// AddSetDeclaration returns ErrNotFound when either side is unknown.
AddSetDeclaration(ctx context.Context, set, identifier string, at time.Time) (changed bool, err error)
RemoveSetDeclaration(ctx context.Context, set, identifier string) (changed bool, err error)
// SetDeclarations lists member identifiers, sorted; ErrNotFound for an
// unknown set.
SetDeclarations(ctx context.Context, set string) ([]string, error)
// DeclarationSets lists the sets containing identifier, sorted.
DeclarationSets(ctx context.Context, identifier string) ([]string, error)
}
SetStore persists sets and their membership.
type Snapshot ¶
type Snapshot struct {
ID mdm.EnrollmentID
DeclarationsToken string
Items []SnapshotItem
TokenChangedAt time.Time
RefreshedAt time.Time
}
Snapshot is the manifest an enrollment was last served, with the token it was told. TokenChangedAt is the Timestamp in the tokens response.
type SnapshotItem ¶
type SnapshotItem struct {
DeclarationRef
// BaseToken is the stored declaration's token; ServerToken differs only
// when an Expander rewrote the bytes for this enrollment.
BaseToken string
// Expanded holds the per-enrollment canonical bytes when they differ
// from the stored declaration; nil otherwise.
Expanded []byte
}
SnapshotItem is one manifest entry as advertised to an enrollment.
func SortRefsItems ¶
func SortRefsItems(items []SnapshotItem) []SnapshotItem
SortRefsItems orders snapshot items the way manifests are rendered.
type SnapshotStore ¶
type SnapshotStore interface {
// PutSnapshot replaces the snapshot and its items atomically.
PutSnapshot(ctx context.Context, s *Snapshot) error
Snapshot(ctx context.Context, id mdm.EnrollmentID) (*Snapshot, error)
}
SnapshotStore keeps the manifest last served per enrollment.
type StatusError ¶
StatusError is one entry of a report's Errors array.
type StatusOutcome ¶
type StatusOutcome struct {
Seq int64
Removed []DeclarationRef
RemovedValues []string
PrunedReports int64
}
StatusOutcome reports what PutStatus changed.
type StatusReportRecord ¶
StatusReportRecord is one raw report as received.
type StatusStore ¶
type StatusStore interface {
// PutStatus applies one report atomically: appends the raw report,
// upserts declaration rows and values (LastSeen bumped, FirstSeen kept),
// appends errors, and for a full report deletes declaration rows and
// values absent from the update. It prunes raw reports beyond
// KeepReports, oldest first.
PutStatus(ctx context.Context, id mdm.EnrollmentID, u StatusUpdate) (StatusOutcome, error)
// DeclarationStatus lists rows sorted by (kind, identifier).
DeclarationStatus(ctx context.Context, id mdm.EnrollmentID) ([]DeclarationStatus, error)
DeclarationStatusByIdentifier(ctx context.Context, identifier string, p storage.Page) (storage.Result[EnrollmentDeclarationStatus], error)
// StatusValues pages by path.
StatusValues(ctx context.Context, id mdm.EnrollmentID, q StatusValueQuery, p storage.Page) (storage.Result[StatusValue], error)
// StatusErrors pages newest first.
StatusErrors(ctx context.Context, id mdm.EnrollmentID, p storage.Page) (storage.Result[StatusError], error)
// StatusReports pages newest first.
StatusReports(ctx context.Context, id mdm.EnrollmentID, p storage.Page) (storage.Result[StatusReportRecord], error)
}
StatusStore persists what devices report.
type StatusUpdate ¶
type StatusUpdate struct {
Raw []byte
ReceivedAt time.Time
FullReport bool
// HasDeclarations is false when the report carried no
// management.declarations item, in which case declaration rows are
// left untouched even for a full report.
HasDeclarations bool
Declarations []DeclarationStatus
Values []StatusValue
Errors []StatusError
// KeepReports bounds the raw reports retained per enrollment.
KeepReports int
}
StatusUpdate is a parsed report ready for PutStatus.
type StatusValue ¶
StatusValue is one status item value as canonical JSON, keyed by its dotted path (array elements keep their index as a path segment).
type StatusValueQuery ¶
type StatusValueQuery struct {
PathPrefix string
}
StatusValueQuery filters StatusValues by path prefix.
type Store ¶
type Store interface {
Tx
// Update runs fn in one transaction; an error rolls everything back.
Update(ctx context.Context, fn func(tx Tx) error) error
}
Store is one backend. Methods called outside Update commit on their own.
type Subscriptions ¶
type Subscriptions struct {
Enabled bool
// Baseline is used until a device reports its capabilities; nil means
// DefaultSubscriptionBaseline.
Baseline []string
// Exclude drops reported status items with these prefixes; nil means
// DefaultSubscriptionExclude.
Exclude []string
}
Subscriptions configures the synthesised status-subscriptions declaration (decision record 0021).
type TokenSource ¶
TokenSource renders an enrollment's TokensResponse; *Engine satisfies it.
type Tx ¶
type Tx interface {
DeclarationStore
SetStore
AssignmentStore
SnapshotStore
StatusStore
ChangeStore
// ClearEnrollment deletes the enrollment's sets, assignments, snapshot,
// status, and pending changes. Absent state is not an error.
ClearEnrollment(ctx context.Context, id mdm.EnrollmentID) error
}
Tx is the view every store exposes inside Update.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
adapter
|
|
|
inproc
Package inproc adapts a ddm.Engine to service.DMHandler for a server that runs the mdm and ddm roles in one process.
|
Package inproc adapts a ddm.Engine to service.DMHandler for a server that runs the mdm and ddm roles in one process. |
|
internal/proxywire
Package proxywire is the wire contract between our mdm role and our ddm role when they run as separate processes.
|
Package proxywire is the wire contract between our mdm role and our ddm role when they run as separate processes. |
|
proxyclient
Package proxyclient is the mdm role's egress to a separate ddm role.
|
Package proxyclient is the mdm role's egress to a separate ddm role. |
|
proxyserver
Package proxyserver is the ddm role's ingress for check-ins forwarded by the mdm role.
|
Package proxyserver is the ddm role's ingress for check-ins forwarded by the mdm role. |
|
Package ddmtest is the contract every ddm.Store backend must satisfy: suites a backend's own test runs through RunAll with a constructor returning a fresh, empty store, fixture helpers, and a Failing wrapper that injects errors by method name, inside transactions too.
|
Package ddmtest is the contract every ddm.Store backend must satisfy: suites a backend's own test runs through RunAll with a constructor returning a fresh, empty store, fixture helpers, and a Failing wrapper that injects errors by method name, inside transactions too. |
|
Package inmem is the reference ddm.Store: a mutex-protected map store whose behaviour the contract suite in ddm/ddmtest defines.
|
Package inmem is the reference ddm.Store: a mutex-protected map store whose behaviour the contract suite in ddm/ddmtest defines. |
|
Package predicate parses and evaluates the subset of Apple's NSPredicate format-string syntax that Declarative Device Management activation predicates use.
|
Package predicate parses and evaluates the subset of Apple's NSPredicate format-string syntax that Declarative Device Management activation predicates use. |
|
Package sqlstore is the SQL ddm.Store: one implementation over database/sql for the SQLite, PostgreSQL, and MySQL dialects.
|
Package sqlstore is the SQL ddm.Store: one implementation over database/sql for the SQLite, PostgreSQL, and MySQL dialects. |