storage

package
v0.3.3 Latest Latest
Warning

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

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

Documentation

Overview

Package storage defines MDM persistence contracts and shared sentinel errors.

Design

Store composes enrollment, command queue, push, certificate, bootstrap-token, user-authentication and migration interfaces. Shared contracts define lifecycle cleanup, NotNow backoff, certificate history and cursor behavior independently of a database. Pagination values come from paging.

storage/inmem supplies process-local storage; server/sqlstore supplies SQLite, PostgreSQL and MySQL implementations. storage/crypt handles selected secret values. Account-driven associations and revocation use separate state interfaces and are not included in enrollment export/import.

References

Index

Constants

View Source
const (
	ReplacementPending   = "pending"
	ReplacementCommitted = "committed"
	ReplacementFailed    = "failed"
	ReplacementCancelled = "cancelled"
	ReplacementExpired   = "expired"
)

Variables

View Source
var (
	ErrNotFound = errors.New("storage: not found")
	ErrDisabled = errors.New("storage: enrollment disabled")
	ErrConflict = errors.New("storage: conflict")
	ErrInvalid  = errors.New("storage: invalid argument")
)

Errors shared by every backend.

View Source
var ErrUserChannelRequired = errors.New("storage: user channel required")

ErrUserChannelRequired is wrapped in ErrInvalid by UserAuthStore methods called with a device channel.

Functions

func AdvanceReplacement added in v0.3.0

func AdvanceReplacement(r **Replacement, e *Enrollment, c ReplacementChange) (bool, error)

AdvanceReplacement is the shared state machine used under each backend's transaction lock. true means the backend must atomically commit candidate state.

func ApplyReplacementToken added in v0.3.0

func ApplyReplacementToken(e *Enrollment, t ReplacementToken, at time.Time)

ApplyReplacementToken updates only fields owned by TokenUpdate, retaining escrow and unrelated enrollment state. Call after validating the message.

func CheckAuthenticate added in v0.3.2

func CheckAuthenticate(id mdm.EnrollmentID, e *Enrollment, c AuthenticateChange) (bool, error)

CheckAuthenticate checks an enrollment under the backend's write lock. A true result denotes an idempotent retry whose existing state must be preserved.

func NotNowBackoff

func NotNowBackoff(attempt int) time.Duration

NotNowBackoff is the default retry delay after the nth NotNow (1-based): 30s, 1m, 2m, 4m, ... capped at 1h.

Types

type AuthenticateChange added in v0.3.2

type AuthenticateChange struct {
	ExpectedHash string
	Hash         string
	AllowReuse   bool
	Message      *checkin.Authenticate
	Raw          []byte
	At           time.Time
}

AuthenticateChange commits a policy-authorized Authenticate and its certificate pin together. ExpectedHash is the pin observed during policy evaluation; an empty value permits only an unpinned enrollment. AllowReuse permits historical reuse, never a certificate currently pinned by another device.

type BootstrapTokenStore

type BootstrapTokenStore interface {
	// StoreBootstrapToken escrows token for the device channel of id and
	// records at as Enrollment.BootstrapTokenAt. A nil or empty token clears
	// the escrow, including when no token was previously stored (Apple's
	// SetBootstrapToken protocol). The timestamp records the last change.
	StoreBootstrapToken(ctx context.Context, id mdm.EnrollmentID, token []byte, at time.Time) error
	// BootstrapToken returns the token or ErrNotFound.
	BootstrapToken(ctx context.Context, id mdm.EnrollmentID) ([]byte, error)
}

BootstrapTokenStore escrows Apple bootstrap tokens (device channel).

type Capabilities added in v0.3.2

type Capabilities struct {
	Supervised, DEP, UserApproved Capability
	Source                        string
	ObservedAt                    time.Time
}

Capabilities records device-reported management properties, distinct from ownership admission.

func CapabilitiesFromResult added in v0.3.2

func CapabilitiesFromResult(
	old Capabilities,
	id mdm.EnrollmentID,
	requestType string,
	r *mdm.Response,
	at time.Time,
) Capabilities

CapabilitiesFromResult extracts evidence only from an acknowledged, tracked device-channel command. requestType must come from the stored queue entry.

type Capability added in v0.3.2

type Capability uint8

Capability is a three-state observation. Unknown never grants eligibility.

const (
	CapabilityUnknown Capability = iota
	CapabilityFalse
	CapabilityTrue
)

Capability values distinguish unknown from explicitly false.

type CertAssociation

type CertAssociation struct {
	ID   mdm.EnrollmentID
	Hash string
	At   time.Time
}

CertAssociation is one row of the append-only pin history: a device channel pinned a certificate hash at a time (decision record 0014).

type CertAuthStore

type CertAuthStore interface {
	// AssociateCert pins hash to the device channel of id at the given time
	// and appends the pair to the history. ErrConflict when the hash is
	// currently pinned to a different enrollment, including when two
	// callers race to pin the same hash.
	AssociateCert(ctx context.Context, id mdm.EnrollmentID, hash string, at time.Time) error
	// CertHash returns the pinned hash for the device channel of id, or
	// "" when none.
	CertHash(ctx context.Context, id mdm.EnrollmentID) (string, error)
	// EnrollmentByCertHash resolves a hash to the device-channel enrollment
	// that currently pins it.
	EnrollmentByCertHash(ctx context.Context, hash string) (mdm.EnrollmentID, error)
	// CertHistory returns every hash ever pinned to the device channel of
	// id, oldest first. It is empty, not ErrNotFound, for an enrollment
	// that never pinned; ErrNotFound for an unknown enrollment.
	CertHistory(ctx context.Context, id mdm.EnrollmentID) ([]CertAssociation, error)
	// CertHashHistory returns every enrollment that ever pinned hash,
	// oldest first; empty when the hash was never seen.
	CertHashHistory(ctx context.Context, hash string) ([]CertAssociation, error)
}

CertAuthStore pins identity certificates to device-channel enrollments and keeps the history of every pin.

type ClearFilter

type ClearFilter struct {
	States      []State // default: every non-terminal state
	RequestType string
	Before      time.Time // enqueued before this time
}

ClearFilter selects commands for Clear. Zero values mean "any".

type CommandQuery

type CommandQuery struct {
	States      []State
	RequestType string
}

CommandQuery filters Commands.

type CommandQueue

type CommandQueue interface {
	// Enqueue queues cmd for each enrollment. Disabled or unknown
	// enrollments are reported in Skipped, not as an error.
	Enqueue(ctx context.Context, ids []mdm.EnrollmentID, cmd *mdm.Command, o EnqueueOptions) (EnqueueResult, error)
	// Next returns the next command to deliver, in enqueue order: pending
	// and sent commands, plus NotNow commands whose backoff elapsed unless
	// skipNotNow is set (the device just said NotNow). It marks the command
	// sent. nil, nil when the queue is empty.
	Next(ctx context.Context, id mdm.EnrollmentID, skipNotNow bool, now time.Time) (*mdm.Command, error)
	// StoreResult records the device's response for the command it names.
	// Unknown CommandUUIDs return ErrNotFound.
	StoreResult(ctx context.Context, id mdm.EnrollmentID, resp *mdm.Response, now time.Time) error
	// Commands pages through an enrollment's commands, newest first.
	Commands(ctx context.Context, id mdm.EnrollmentID, q CommandQuery, p paging.Page) (paging.Result[QueuedCommand], error)
	// Clear marks matching non-terminal commands cleared and returns how
	// many. Backends may apply it in batches without one enclosing
	// transaction: on error the count is what was applied so far and the
	// caller may simply retry.
	Clear(ctx context.Context, id mdm.EnrollmentID, f ClearFilter) (int64, error)
}

CommandQueue persists commands per enrollment.

type DeviceInfo

type DeviceInfo struct {
	SerialNumber string
	Model        string
	ModelName    string
	DeviceName   string
	ProductName  string
	OSVersion    string
	BuildVersion string
	IMEI         string
	MEID         string
	Topic        string
}

DeviceInfo is the subset of Authenticate worth indexing.

func DeviceInfoFromAuthenticate

func DeviceInfoFromAuthenticate(m *checkin.Authenticate) DeviceInfo

DeviceInfoFromAuthenticate extracts the indexed fields.

type EnqueueOptions

type EnqueueOptions struct {
	// DedupeKey skips enrollments that already have a non-terminal command
	// with the same key (for example one DeclarativeManagement kick).
	DedupeKey string
	// Now stamps EnqueuedAt; zero means time.Now().
	Now time.Time
}

EnqueueOptions tune Enqueue.

type EnqueueResult

type EnqueueResult struct {
	Queued  []mdm.EnrollmentID
	Skipped map[mdm.EnrollmentID]error
}

EnqueueResult reports per-enrollment outcomes.

type Enrollment

type Enrollment struct {
	Capabilities Capabilities
	ID           mdm.EnrollmentID
	// Enabled becomes true on TokenUpdate and false on CheckOut or a new
	// Authenticate; only enabled enrollments receive commands and pushes.
	Enabled bool
	Push    mdm.Push
	Device  DeviceInfo
	// User channel fields from TokenUpdate: names, whether the user is
	// logged in without console access, and the EnrollmentUserID of a
	// User Enrollment's user channel (decision record 0029).
	UserShortName    string
	UserLongName     string
	NotOnConsole     bool
	EnrollmentUserID string
	// UnlockToken from TokenUpdate (macOS), if the device sent one.
	UnlockToken []byte
	// AuthenticateRaw is the last Authenticate plist as received.
	AuthenticateRaw []byte
	// TokenUpdateRaw is the last TokenUpdate plist as received, kept so an
	// enrollment can be replayed into another server (decision record 0017).
	TokenUpdateRaw []byte
	EnrolledAt     time.Time
	TokenUpdatedAt time.Time
	LastSeenAt     time.Time
	DisabledAt     time.Time
	// CertHash is the pinned identity certificate fingerprint (device channels).
	CertHash string
	// CertHashAt is when CertHash was pinned (zero when none).
	CertHashAt time.Time
	// BootstrapTokenAt is when the escrowed bootstrap token was stored
	// (zero when none). The token itself is read through BootstrapTokenStore.
	BootstrapTokenAt time.Time
}

Enrollment is one channel of one enrollment as the server knows it.

type EnrollmentExport

type EnrollmentExport struct {
	Enrollment
	BootstrapToken []byte
	CertHistory    []CertAssociation
}

EnrollmentExport is everything one enrollment channel needs to move to another backend (decision record 0017). Empty byte fields are nil.

type EnrollmentQuery

type EnrollmentQuery struct {
	Channel  mdm.Channel
	Enabled  *bool
	ParentID string
	// Serial matches Device.SerialNumber exactly. The SQL backends index
	// this column, so it is a lookup rather than a scan.
	Serial string
}

EnrollmentQuery filters List. Zero values mean "any".

type EnrollmentStore

type EnrollmentStore interface {
	// AuthenticateEnrollment atomically commits authentication, pinning, history,
	// and enrollment reset. Same-certificate retries preserve existing state.
	AuthenticateEnrollment(ctx context.Context, id mdm.EnrollmentID, change AuthenticateChange) error
	// UpsertAuthenticate records an Authenticate message. It creates the
	// record or resets an existing one: push info, unlock token, bootstrap
	// token, certificate association, and the pending command queue are
	// cleared so a re-enrollment never inherits the previous identity's
	// state. The enrollment stays disabled until TokenUpdate.
	UpsertAuthenticate(ctx context.Context, id mdm.EnrollmentID, msg *checkin.Authenticate, raw []byte, at time.Time) error
	// StoreTokenUpdate records push info, the raw plist, and enables the
	// enrollment. An unlock token in msg replaces the stored one; a missing
	// one keeps it.
	StoreTokenUpdate(ctx context.Context, id mdm.EnrollmentID, push mdm.Push, msg *checkin.TokenUpdate, raw []byte, at time.Time) error
	// Disable marks the enrollment as checked out. Disabling a device
	// channel also disables the user channels whose parent it is, because a
	// checked-out device cannot carry a user channel. Records are kept.
	Disable(ctx context.Context, id mdm.EnrollmentID, at time.Time) error
	// Get returns the record or ErrNotFound.
	Get(ctx context.Context, id mdm.EnrollmentID) (*Enrollment, error)
	// EnrollmentByID resolves a globally unique raw ID to its stored identity.
	// Authorization must use the returned identity, never the caller's channel.
	EnrollmentByID(ctx context.Context, id string) (*Enrollment, error)
	// List pages through enrollments ordered by id.
	List(ctx context.Context, q EnrollmentQuery, p paging.Page) (paging.Result[Enrollment], error)
	// TouchLastSeen records device activity.
	TouchLastSeen(ctx context.Context, id mdm.EnrollmentID, at time.Time) error
}

EnrollmentStore persists enrollment records.

type MigrationStore

type MigrationStore interface {
	// Export pages through every enrollment with device channels before the
	// user channels that belong to them.
	Export(ctx context.Context, p paging.Page) (paging.Result[EnrollmentExport], error)
	// Import writes rec exactly as given (Enabled, timestamps, pin, tokens,
	// history) in one transaction, upserting by id. ErrInvalid for a user
	// channel whose parent is absent or for history rows naming another
	// enrollment; ErrConflict when CertHash is currently pinned elsewhere.
	// The command queue is not touched.
	Import(ctx context.Context, rec EnrollmentExport) error
}

MigrationStore exports and imports enrollment records between backends.

type PushCert

type PushCert struct {
	Topic    string
	CertPEM  []byte
	KeyPEM   []byte
	NotAfter time.Time
	// Version increments on every StorePushCert for the topic, so caches
	// can detect a renewal with one cheap read.
	Version   int64
	UpdatedAt time.Time
}

PushCert is a stored APNs push certificate for one topic (decision record 0015). KeyPEM is empty in listings.

func ValidatePushCert

func ValidatePushCert(topic string, certPEM, keyPEM []byte, at time.Time) (PushCert, error)

ValidatePushCert checks a PEM certificate and key pair the way every backend's StorePushCert must: the key matches the certificate, the subject carries an APNs topic, the topic matches when one is given, and the certificate is valid at the given time. It returns the record to store, with copies of the PEM bytes and Version unset.

type PushCertStore

type PushCertStore interface {
	// StorePushCert validates the PEM pair (key matches certificate, topic
	// in the subject UID, not expired at the given time) and upserts it.
	// An empty topic accepts the certificate's own topic; otherwise the two
	// must match. ErrInvalid for anything that fails validation. The
	// returned record carries the new Version and no KeyPEM.
	StorePushCert(ctx context.Context, topic string, certPEM, keyPEM []byte, at time.Time) (PushCert, error)
	// PushCert returns the certificate and key for topic, or ErrNotFound.
	PushCert(ctx context.Context, topic string) (*PushCert, error)
	// PushCerts lists every stored certificate by topic, without keys.
	PushCerts(ctx context.Context) ([]PushCert, error)
	// PushCertVersion returns the current Version for topic, or ErrNotFound.
	PushCertVersion(ctx context.Context, topic string) (int64, error)
}

PushCertStore keeps push certificates and their private keys.

type PushStore

type PushStore interface {
	// PushInfo returns push details for the enabled enrollments among ids.
	PushInfo(ctx context.Context, ids []mdm.EnrollmentID) (map[mdm.EnrollmentID]mdm.Push, error)
}

PushStore returns what the push layer needs.

type QueuedCommand

type QueuedCommand struct {
	Command     mdm.Command
	State       State
	DedupeKey   string
	EnqueuedAt  time.Time
	LastSentAt  time.Time
	NotNowUntil time.Time
	// Attempts counts deliveries; NotNowCount counts NotNow answers and
	// drives the backoff.
	Attempts    int
	NotNowCount int
	CompletedAt time.Time
	Result      *mdm.Response
}

QueuedCommand is a command with its delivery state for one enrollment.

type Replacement added in v0.3.0

type Replacement struct {
	ID, Method, OldHash, CandidateHash, SecretHash, PublicKeyHash string
	State                                                         string
	ExpiresAt, CompletedAt                                        time.Time
	Command                                                       mdm.Command
	Delivered, Acknowledged, Authenticated                        bool
	AuthenticateRaw                                               []byte
	Tokens                                                        []ReplacementToken
}

Replacement contains private handshake state. Administrative APIs must expose a redacted view, not this record (the command contains issuance credentials).

func CloneReplacement added in v0.3.0

func CloneReplacement(r *Replacement) *Replacement

CloneReplacement isolates callback/transport buffers from stored state.

type ReplacementChange added in v0.3.0

type ReplacementChange struct {
	Op, ID, Hash, Method, SecretHash, PublicKeyHash string
	At                                              time.Time
	Begin                                           *Replacement
	Raw                                             []byte
	Token                                           *ReplacementToken
	Response                                        *mdm.Response
}

type ReplacementStore added in v0.3.0

type ReplacementStore interface {
	TransitionReplacement(context.Context, mdm.EnrollmentID, ReplacementChange) (*Replacement, error)
}

ReplacementStore is an optional extension. Every transition locks the device enrollment and its replacement together. A successful terminal transition commits the candidate pin, token and certificate history in the same transaction; it never resets the enrollment, user channels, escrow or command queue.

type ReplacementToken added in v0.3.0

type ReplacementToken struct {
	ID      mdm.EnrollmentID
	Message *checkin.TokenUpdate
	Raw     []byte
}

type State

type State string

State of a queued command.

const (
	StatePending      State = "pending"      // never delivered
	StateSent         State = "sent"         // delivered, awaiting a result
	StateNotNow       State = "not-now"      // device answered NotNow; retry after NotNowUntil
	StateAcknowledged State = "acknowledged" // terminal
	StateError        State = "error"        // terminal: Error or CommandFormatError
	StateCleared      State = "cleared"      // terminal: removed by Clear
)

Command states.

func (State) Terminal

func (s State) Terminal() bool

Terminal reports whether the state is final.

type Store

Store is everything the service layer needs from one backend.

type UserAuthState

type UserAuthState struct {
	ID mdm.EnrollmentID
	// Challenge is the outstanding DigestChallenge, "" once answered or
	// cleared.
	Challenge   string
	ChallengeAt time.Time
	// AuthToken is the issued token, "" until the digest was accepted.
	AuthToken string
	TokenAt   time.Time
	// AuthenticateRaw is the first UserAuthenticate plist; DigestRaw the
	// second one carrying DigestResponse.
	AuthenticateRaw []byte
	DigestRaw       []byte
}

UserAuthState is the UserAuthenticate handshake state of one user channel (decision record 0016). The user's own enrollment row may not exist yet: the handshake precedes the user channel's TokenUpdate.

type UserAuthStore

type UserAuthStore interface {
	// StoreUserAuthChallenge records a new challenge and clears any token.
	StoreUserAuthChallenge(ctx context.Context, id mdm.EnrollmentID, challenge string, raw []byte, at time.Time) error
	// StoreUserAuthToken records the issued token and clears the challenge.
	// ErrNotFound when no challenge was issued for the user.
	StoreUserAuthToken(ctx context.Context, id mdm.EnrollmentID, token string, raw []byte, at time.Time) error
	// UserAuth returns the state or ErrNotFound.
	UserAuth(ctx context.Context, id mdm.EnrollmentID) (*UserAuthState, error)
	// ClearUserAuth removes the state; absent state is not an error.
	ClearUserAuth(ctx context.Context, id mdm.EnrollmentID) error
}

UserAuthStore persists UserAuthenticate challenges and tokens per user channel. Every method returns ErrInvalid for a device channel and ErrNotFound when the parent device enrollment does not exist. The state is removed when the device re-enrolls.

Directories

Path Synopsis
acme
acmetest
Package acmetest defines ACME store contracts, fixtures and controlled failures.
Package acmetest defines ACME store contracts, fixtures and controlled failures.
inmem
Package inmem implements a mutex-protected in-memory acme.Store.
Package inmem implements a mutex-protected in-memory acme.Store.
Package crypt seals byte values with AES-256-GCM using named keys from secrets.Provider.
Package crypt seals byte values with AES-256-GCM using named keys from secrets.Provider.
ddm
ddmtest
Package ddmtest defines transactional declaration-store contracts and fixtures.
Package ddmtest defines transactional declaration-store contracts and fixtures.
inmem
Package inmem implements a mutex-protected in-memory ddm.Store.
Package inmem implements a mutex-protected in-memory ddm.Store.
dep
inmem
Package inmem implements a mutex-protected in-memory device enrollment service store.
Package inmem implements a mutex-protected in-memory device enrollment service store.
Package inmem implements a mutex-protected MDM store for tests and development.
Package inmem implements a mutex-protected MDM store for tests and development.
Package storagetest defines the contract suites every MDM storage backend runs.
Package storagetest defines the contract suites every MDM storage backend runs.

Jump to

Keyboard shortcuts

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