app

package
v0.9.1 Latest Latest
Warning

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

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

Documentation

Overview

Package app composes the reference server's protocol services, storage, workers and administrative routes.

Design

Build validates configuration and assembles mdm, ddm or all roles. The MDM role serves devices and uses a local declaration engine or authenticated proxyclient; the DDM role serves the engine through proxyserver. Administrative families depend on available components and credentials. Run supervises workers, and Close drains event delivery and releases owned resources.

Enrollment, push, principal/policy storage, audit, certificate revocation and quotas are configured here. Library defaults can differ from this composition, including re-enrollment policy. TLS termination, durable CA/secret material, admission policy and replica routing belong to the deployment.

References

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 and an issuance identifier bound to the requested assurance.
	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 (
	ActionManageAppPush          = "manageAppPushCredentials"
	ActionSendAppPush            = "sendAppPush"
	ActionIssueEnrollmentProfile = "issueEnrollmentProfile"
	ActionReplaceEnrollment      = "replaceEnrollmentProfile"
	ActionPutDeclaration         = "putDeclaration"
	ActionGetDeclaration         = "getDeclaration"
	ActionDeleteDeclaration      = "deleteDeclaration"
	ActionAssignSet              = "assignSet"
	ActionReadEnrollment         = "readEnrollment"
	ActionReadEnrollmentStatus   = "readEnrollmentStatus"
	ActionNotify                 = "notify"
	ActionManageDEP              = "manageDEP"
	ActionManageBusinessMgr      = "manageBusinessManager"
	ActionReadACME               = "readACME"
	ActionManagePrincipals       = "managePrincipals"
	ActionReadAudit              = "readAudit"
	ActionRetryEvents            = "retryEvents"
	ActionDisableEnrollment      = "disableEnrollment"
	ActionEnqueueCommand         = "enqueueCommand"
	ActionReadCommands           = "readCommands"
	ActionClearCommands          = "clearCommands"
	ActionPushEnrollment         = "pushEnrollment"
	ActionManagePushCerts        = "managePushCertificates"
	ActionExportEnrollments      = "exportEnrollments"
	ActionImportEnrollments      = "importEnrollments"
	ActionManagePolicies         = "managePolicies"
)

Administrative action IDs form the registry used to validate stored policy references. Each route declares its action (decision record 0034).

View Source
const (
	ActionReadCertificates   = "readCertificates"
	ActionImportCertificates = "importCertificates"
	ActionRevokeCertificates = "revokeCertificates"
)
View Source
const (
	ActionReadSetup     = "readCertificateSetup"
	ActionManageVendor  = "manageVendorSigning"
	ActionSignVendorCSR = "signCustomerPushRequests"
	ActionManageHTTPS   = "manageHTTPSCertificates"
	ActionManageIssuer  = "manageEnrollmentIssuers"
)
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       = "DM_ROLE"
	EnvListen     = "DM_LISTEN"
	EnvStorage    = "DM_STORAGE"
	EnvDSN        = "DM_DSN"
	EnvDDMURL     = "DM_DDM_URL"
	EnvDDMSendKey = "DM_DDM_SEND_KEY"
	EnvDDMRecvKey = "DM_DDM_RECV_KEY"
	// EnvStorageKeys names the keys sealing the secret columns, active
	// first; material comes from DM_STORAGE_KEY_<NAME> or EnvSecretsDir.
	EnvStorageKeys       = "DM_STORAGE_KEYS" // #nosec G101 -- the variable name, not a credential
	EnvStorageKeysStrict = "DM_STORAGE_KEYS_STRICT"
	EnvSecretsDir        = "DM_SECRETS_DIR" // #nosec G101 -- the variable name, not a credential
	// EnvAllowReenroll opts back in to the library's permissive
	// re-enrollment behaviour; see Config.AllowReenroll.
	EnvAllowReenroll = "DM_ALLOW_REENROLL"
	EnvAdminToken    = "DM_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 = "DM_ADMIN_STORE"
	// EnvAudit writes a projected slog record for every event.
	EnvAudit = "DM_AUDIT_LOG"
	// EnvWebhookURL receives an event per POST in the MicroMDM envelope.
	EnvWebhookURL = "DM_WEBHOOK_URL"
	// EnvWebhookRootCAFile supplies a private trust bundle for HTTPS webhooks.
	EnvWebhookRootCAFile = "DM_WEBHOOK_ROOT_CA_FILE"
	// EnvAuditStore persists every event to the audit trail.
	EnvAuditStore = "DM_AUDIT_STORE"
	// EnvAuditRetention is how long audit records are kept.
	EnvAuditRetention = "DM_AUDIT_RETENTION"
	// EnvWebhookHMACKey signs the webhook body.
	EnvWebhookHMACKey = "DM_WEBHOOK_HMAC_KEY" // #nosec G101 -- the variable name, not a credential
	EnvCAFile         = "DM_CA_FILE"
	EnvCertHeader     = "DM_CERT_HEADER"
	// Enrollment routes (EnrollConfig).
	EnvPublicURL           = "DM_PUBLIC_URL"
	EnvPushTopic           = "DM_PUSH_TOPIC"
	EnvEnrollCACertFile    = "DM_ENROLL_CA_CERT_FILE"
	EnvEnrollCAKeyFile     = "DM_ENROLL_CA_KEY_FILE"
	EnvSCEPChallenge       = "DM_SCEP_CHALLENGE" // #nosec G101 -- the variable name, not a credential
	EnvSCEPHMACKey         = "DM_SCEP_HMAC_KEY"  // #nosec G101 -- the variable name, not a credential
	EnvProfileIdentifier   = "DM_PROFILE_IDENTIFIER"
	EnvEnrollTLSAnchorFile = "DM_ENROLL_TLS_ANCHOR_FILE"
	EnvOrganization        = "DM_ORGANIZATION"
	EnvDiscovery           = "DM_DISCOVERY"
	EnvAccountDrivenMethod = "DM_ACCOUNT_DRIVEN_METHOD"
	EnvOIDCIssuer          = "DM_OIDC_ISSUER"
	EnvOIDCClientID        = "DM_OIDC_CLIENT_ID"
	EnvOIDCClientSecret    = "DM_OIDC_CLIENT_SECRET" // #nosec G101 -- the variable name, not a credential
	EnvADEAnchorFile       = "DM_ADE_ANCHOR_FILE"
	EnvADEAudit            = "DM_ADE_AUDIT"
	EnvRequireUserAuth     = "DM_REQUIRE_USER_AUTH"
	EnvReturnToService     = "DM_RETURN_TO_SERVICE"
	// Apple Business Manager (AxMConfig).
	EnvAxMClientID   = "DM_AXM_CLIENT_ID"
	EnvAxMKeyID      = "DM_AXM_KEY_ID"
	EnvAxMKeyFile    = "DM_AXM_KEY_FILE"
	EnvAxMScope      = "DM_AXM_SCOPE"
	EnvAxMRootCAFile = "DM_AXM_ROOT_CA_FILE"
	EnvAxMBaseURL    = "DM_AXM_BASE_URL"
	EnvAxMTokenURL   = "DM_AXM_TOKEN_URL" // #nosec G101 -- the variable name, not a credential
	// Device enrollment service (DEPConfig).
	EnvDEPRootCAFile     = "DM_DEP_ROOT_CA_FILE"
	EnvDEPBaseURL        = "DM_DEP_BASE_URL"
	EnvDEPSyncInterval   = "DM_DEP_SYNC_INTERVAL"
	EnvDEPAssignInterval = "DM_DEP_ASSIGN_INTERVAL" // #nosec G101 -- the variable name, not a credential
	EnvDEPProfileURL     = "DM_DEP_PROFILE_URL"
	EnvDEPUsePUT         = "DM_DEP_USE_PUT"

	// Push: where APNs credentials come from, and how pushes are shaped.
	EnvPushSource   = "DM_PUSH_SOURCE"
	EnvPushCertFile = "DM_PUSH_CERT_FILE"
	EnvPushKeyFile  = "DM_PUSH_KEY_FILE"
	EnvPushHost     = "DM_PUSH_HOST"
	EnvPushCoalesce = "DM_PUSH_COALESCE"
	EnvPushCertTTL  = "DM_PUSH_CERT_TTL"
	// ACME and Managed Device Attestation (ACMEConfig).
	EnvIdentity       = "DM_IDENTITY"
	EnvACMEPolicy     = "DM_ACME_POLICY"
	EnvACMEKey        = "DM_ACME_KEY"
	EnvACMEHMACKey    = "DM_ACME_HMAC_KEY" // #nosec G101 -- the variable name, not a credential
	EnvACMEAnchorFile = "DM_ACME_ANCHOR_FILE"
	EnvACMEUnattested = "DM_ACME_ALLOW_UNATTESTED"
	EnvACMEIdentTTL   = "DM_ACME_IDENTIFIER_TTL"

	EnvSubscriptions = "DM_DDM_SUBSCRIPTIONS"
)

Environment variables read by ParseEnv.

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

Defaults applied by ParseEnv when a variable is unset.

View Source
const (
	EnvEventWorkers         = "DM_EVENT_WORKERS"
	EnvEventQueueCapacity   = "DM_EVENT_QUEUE_CAPACITY"
	EnvEventDeliveryTimeout = "DM_EVENT_DELIVERY_TIMEOUT"
)

Environment controls for the audit/webhook bus. Unset or zero uses defaults.

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 (
	EnvEnrollmentPolicy    = "DM_ENROLLMENT_POLICY_FILE"
	EnvPKIRevocation       = "DM_PKI_REVOCATION"
	EnvPKICRLTTL           = "DM_PKI_CRL_TTL"
	EnvPKICRLRefresh       = "DM_PKI_CRL_REFRESH"
	EnvPKIOCSPTTL          = "DM_PKI_OCSP_TTL"
	EnvPKIRetiredIssuers   = "DM_PKI_RETIRED_ISSUERS"
	EnvRateLimits          = "DM_RATE_LIMITS"
	EnvRateLimitMaxEntries = "DM_RATE_LIMIT_MAX_ENTRIES"
	EnvTrustedProxies      = "DM_TRUSTED_PROXIES"
)

Optional security service environment variables. Empty leaves the feature off.

View Source
const (
	PathServiceConfig = "/MDMServiceConfig"
	PathTrustAnchors  = "/enroll/trust/anchors"
	PathTrustProfile  = "/enroll/trust/profile"
)
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 DM_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
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 reports that an enabled admin API has neither a
	// principal store nor a static token. Build returns this configuration error.
	ErrAdminUnconfigured = errors.New(
		"app: admin API needs DM_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. `dmctl 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 InitSetupFile

func InitSetupFile(o SetupInitOptions) (string, error)

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 {
	// MacHardware resolves trusted inventory capabilities. Unknown leaves initial
	// profile flags explicit; secondary credentials use software keys until known.
	// T2 supports hardware keys, but only Apple silicon supports attestation.
	MacHardware func(context.Context, acme.Binding) (enroll.MacHardware, error)
	// 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 AccountAdmissionRule

type AccountAdmissionRule struct {
	Issuer              string   `json:"issuer"`
	Subject             string   `json:"subject"`
	Email               string   `json:"email"`
	ManagedAppleAccount string   `json:"managedAppleAccount"`
	Groups              []string `json:"groups"`
}

AccountAdmissionRule maps an authenticated IdP identity to a managed account.

type AdmissionGrant

type AdmissionGrant struct {
	Account   string           `json:"account,omitempty"`
	ExpiresAt time.Time        `json:"expiresAt"`
	Identity  AdmissionRequest `json:"identity"`
	Mode      string           `json:"mode"`
}

AdmissionGrant authorizes one enrollment attempt. Account is the managed Apple account selected by policy. The reference admission gate records the verified Identity and enrollment Mode before issuing a credential.

type AdmissionPolicy

type AdmissionPolicy struct {
	Devices     []DeviceAdmissionRule  `json:"devices"`
	DEPAccounts []string               `json:"depAccounts"`
	Accounts    []AccountAdmissionRule `json:"accounts"`
}

AdmissionPolicy is the JSON format of DM_ENROLLMENT_POLICY_FILE. Each device rule requires every nonempty identifier to match. Accounts require an exact issuer and subject or verified email; Groups, when supplied, requires membership in at least one listed group. An empty policy admits nobody.

type AdmissionRequest

type AdmissionRequest struct {
	Serial        string   `json:"serial,omitempty"`
	UDID          string   `json:"udid,omitempty"`
	Issuer        string   `json:"issuer,omitempty"`
	Subject       string   `json:"subject,omitempty"`
	Email         string   `json:"email,omitempty"`
	EmailVerified bool     `json:"emailVerified,omitempty"`
	Groups        []string `json:"groups,omitempty"`
}

AdmissionRequest contains authenticated device or account properties.

type App

type App struct {
	Certificates *lifecycle.Manager

	Handler  http.Handler
	Core     *service.Core
	Engine   *ddm.Engine
	Notifier *ddmsync.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 *pushnotify.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 OpenSetup

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

OpenSetup opens only storage and certificate management, for local bootstrap. The returned App must be closed. It does not start listeners or workers.

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 dmctl reads to explain a 404.

func (*App) CertificateSetupStatus

func (a *App) CertificateSetupStatus(ctx context.Context) (SetupStatus, error)

func (*App) Close

func (a *App) Close() error

Close releases storage after a bounded drain of a bus created by Build. A drain timeout cancels active deliveries and abandons queued events.

func (*App) DEPStoreForTests

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

DEPStoreForTests exposes the DEP store to tests of the wiring.

func (*App) ExecuteSetup

func (a *App) ExecuteSetup(
	ctx context.Context,
	kind lifecycle.Kind,
	operation string,
	req SetupRequest,
) (SetupResult, error)

ExecuteSetup keeps CLI bootstrap and remote operations on the same workflow.

func (*App) ExportEnrollmentProfile

func (a *App) ExportEnrollmentProfile(
	ctx context.Context,
	req EnrollmentProfileRequest,
) ([]byte, error)

ExportEnrollmentProfile issues and records the same profile used by the API.

func (*App) LoadTLSCertificate

func (a *App) LoadTLSCertificate(ctx context.Context) (*tls.Certificate, error)

LoadTLSCertificate verifies the active HTTPS identity for startup and probes.

func (*App) ManagedTLSConfig

func (a *App) ManagedTLSConfig(hello *tls.ClientHelloInfo) (*tls.Config, error)

ManagedTLSConfig refreshes device trust for new connections without mutating a certificate pool already in use by another TLS handshake.

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/dmserver, 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 -- ddmsync.Notifier.Run returns ctx.Err(), depService.Run returns nil -- so cancellation is normalised here rather than in each loop.

func (*App) SetupTrustProfile

func (a *App) SetupTrustProfile(ctx context.Context) ([]byte, error)

SetupTrustProfile exports public lab HTTPS trust before enrollment is enabled.

func (*App) TLSCertificate

func (a *App) TLSCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)

TLSCertificate loads the active revision for each new TLS handshake.

func (*App) TLSClientRoots

func (a *App) TLSClientRoots() *x509.CertPool

TLSClientRoots returns the configured device identity trust roots.

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 AppPushConfig

type AppPushConfig struct{ DevelopmentHost, ProductionHost, RootCAFile string }

AppPushConfig configures provider trust and optional alternative endpoints. Environments are always explicit on sends; credentials are never MDM credentials.

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
	// RootCAFile supplies private HTTPS trust instead of HTTPClient.
	RootCAFile string
}

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 {
	Setup *SetupConfig

	PKI                     PKIConfig
	RateLimits              RateLimitConfig
	Role                    Role
	TLSCertFile, TLSKeyFile string
	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
	// DDMRootCAFile configures HTTPS trust for the private DDM server.
	DDMRootCAFile string
	// DDMAllowInsecureForTests enables loopback-only cleartext adapters.
	DDMAllowInsecureForTests bool
	// DDMSendKey signs what this role sends across the hop; DDMRecvKey
	// verifies what it receives.
	DDMSendKey, DDMRecvKey []byte
	// AllowReenroll accepts Authenticate with a different certificate and replaces
	// the enrollment pin. The library defaults to allowing this; the reference
	// server defaults to DenyReenroll.
	//
	// Enable only with appropriate enrollment admission. CA trust alone does not
	// bind a certificate to an enrollment identifier; account-driven issuance
	// associations provide an additional binding for those sessions.
	AllowReenroll bool
	// StorageKeys names the keys that seal the secret columns of a persistent
	// store: unlock tokens, bootstrap tokens, APNs push keys and user auth
	// tokens. The first is the active key every write seals under, and the
	// rest are retired keys reads still accept, so a rotation is a prepended
	// name followed by Rewrap.
	//
	// Without a keyring those columns are written in clear, and a stolen
	// backup, replica or volume yields the push key, which wakes and
	// impersonates the whole fleet. A persistent store therefore requires one.
	StorageKeys []string
	// StorageKeysStrict refuses to read a secret column that is not sealed.
	// It belongs on once Rewrap has run everywhere; before that it would
	// reject rows written before the keyring existed.
	StorageKeysStrict bool
	// SecretsDir resolves StorageKeys from files in one directory, the shape
	// Docker and Kubernetes secret mounts take. Empty reads them from the
	// environment as DM_STORAGE_KEY_<NAME>.
	SecretsDir string
	// Secrets overrides both, for tests and embedding.
	Secrets secrets.Provider
	// 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
	// TrustedProxies are socket peers allowed to assert client certificates and IPs.
	TrustedProxies []netip.Prefix
	// 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.
	AppPush AppPushConfig
	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
	// contains filtered or unexported fields
}

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

func LoadSetupFile

func LoadSetupFile(path string, getenv func(string) string) (Config, error)

LoadSetupFile combines file settings with explicit environment overrides.

func ParseEnv

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

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
	// RootCAFile supplies private HTTPS trust instead of HTTPClient.
	RootCAFile string
	// 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 DeviceAdmissionRule

type DeviceAdmissionRule struct {
	Serial string `json:"serial"`
	UDID   string `json:"udid"`
}

DeviceAdmissionRule requires every configured device identifier to match.

type EnrollConfig

type EnrollConfig struct {
	Admission                   EnrollmentAdmission
	AdmissionFile               string
	UserAuthHA1File             string
	OTAAnchorFile, OTAChallenge string
	// 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
	// TLSAnchorFile contains public CA certificates needed to trust the HTTPS
	// enrollment endpoints. Empty means publicly trusted HTTPS. It is independent
	// of the identity issuer and the incoming ADE/OTA signing anchors.
	TLSAnchorFile string
	// SCEPChallenge does not authorize reference-server certificate issuance.
	// SCEPHMACKey also supplies the fallback key for ACME client identifiers.
	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
	// ReturnToService allows a supervised Automated Device Enrollment device
	// that asks to erase itself and re-enrol. Off by default: the answer to a
	// return-to-service request erases a device, so it is not something a
	// server should agree to because nobody said otherwise (0045).
	ReturnToService 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 EnrollmentAdmission

type EnrollmentAdmission func(context.Context, AdmissionRequest) (AdmissionGrant, error)

EnrollmentAdmission decides eligibility before credential issuance.

type EnrollmentProfileRequest

type EnrollmentProfileRequest struct {
	DeviceID     string              `json:"DeviceID"`
	Serial       string              `json:"Serial"`
	Product      string              `json:"Product"`
	OSVersion    string              `json:"OSVersion"`
	MacHardware  enroll.MacHardware  `json:"MacHardware"`
	Identity     string              `json:"Identity"`
	AccessRights enroll.AccessRights `json:"AccessRights"`
	Scope        string              `json:"Scope"`
}

EnrollmentProfileRequest selects the device and enrollment identity method.

type IssuerFiles

type IssuerFiles struct {
	Certificate string `json:"certificate"`
	Key         string `json:"key"`
}

IssuerFiles names an operator-managed CA certificate and private key.

type OIDCConfig

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

OIDCConfig is the relying-party configuration.

type PKIConfig

type PKIConfig struct {
	// Disabled explicitly opts out of issuer status enforcement.
	Disabled                    bool
	Enabled                     bool
	CRLTTL, CRLRefresh, OCSPTTL time.Duration
	// Retired holds old issuer keys while their issued certificates remain valid.
	Retired []IssuerFiles
}

PKIConfig enables issuer status registration, enforcement and publication. All lifetimes are required when enabled. Existing DER must be imported before enabling enforcement on devices issued before the registry existed.

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
	RootCAFile 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 RateLimitConfig

type RateLimitConfig struct {
	Routes     map[string]RouteQuota
	MaxEntries int
}

RateLimitConfig is off when Routes is empty. TrustedProxies is opt-in.

type Role

type Role string

Role selects what a process serves.

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

Roles.

type RouteQuota

type RouteQuota struct {
	Interval       time.Duration
	Burst          int
	GlobalInterval time.Duration
	GlobalBurst    int
}

RouteQuota is explicit per-peer and aggregate admission for one route family.

type SetupConfig

type SetupConfig struct {
	HTTP01Listen    string `json:"http01Listen,omitempty"`
	Role            string `json:"role"`
	VendorID        string `json:"vendorId"`
	PushID          string `json:"pushId"`
	HTTPSID         string `json:"httpsId"`
	IssuerID        string `json:"issuerId"`
	HTTPSCAID       string `json:"httpsCaId,omitempty"`
	VendorURL       string `json:"vendorUrl,omitempty"`
	VendorTokenFile string `json:"vendorTokenFile,omitempty"`
}

SetupConfig selects identities in encrypted persistent storage. It contains references and operational policy, never private key material.

type SetupFile

type SetupFile struct {
	Version     int               `json:"version"`
	Environment map[string]string `json:"environment"`
	SecretFiles map[string]string `json:"secretFiles"`
	Setup       SetupConfig       `json:"setup"`
}

SetupFile is a versioned bootstrap document. Secret values live in the referenced files or environment, outside the database they protect.

type SetupInitOptions

type SetupInitOptions struct {
	Directory, Role, Storage, DSN, PublicURL, Listen, Organization string
	StorageKeyFile, StorageKeyName                                 string
	HTTP01Listen                                                   string
	AdminTokenFile, IssuanceKeyFile, ACMEKeyFile                   string
	// AdditionalSecrets preserves deployment settings during an explicit import.
	// Values are written to protected files and referenced by environment name.
	AdditionalSecrets map[string][]byte
	StorageKeyAliases []string
}

InitSetupFile creates protected bootstrap files without overwriting identities or secrets on retry. Database creation is performed by OpenSetup.

type SetupRequest

type SetupRequest struct {
	Force      bool                         `json:"force,omitempty"`
	PublicACME *lifecycle.PublicACMEOptions `json:"publicAcme,omitempty"`
	lifecycle.Request
	Device        string `json:"device,omitempty"`
	Cursor        string `json:"cursor,omitempty"`
	Limit         int    `json:"limit,omitempty"`
	Revision      string `json:"revision,omitempty"`
	Certificate   []byte `json:"certificate,omitempty"`
	Key           []byte `json:"key,omitempty"`
	CSR           []byte `json:"csr,omitempty"`
	SignedRequest []byte `json:"signedRequest,omitempty"`
	Vendor        string `json:"vendor,omitempty"`
	Artifact      string `json:"artifact,omitempty"`
	ValidityDays  int    `json:"validityDays,omitempty"`
}

SetupRequest is shared by the local CLI and authenticated setup API.

type SetupResult

type SetupResult struct {
	History    []lifecycle.Activity  `json:"history,omitempty"`
	Rollover   *lifecycle.Rollover   `json:"rollover,omitempty"`
	Migrations []lifecycle.Migration `json:"migrations,omitempty"`
	NextCursor string                `json:"nextCursor,omitempty"`
	Identity   *lifecycle.Identity   `json:"identity,omitempty"`
	Data       []byte                `json:"data,omitempty"`
}

SetupResult exposes public metadata and explicitly requested public artifacts.

type SetupStatus

type SetupStatus struct {
	Role              string                      `json:"role"`
	Ready             bool                        `json:"ready"`
	EnrollmentEnabled bool                        `json:"enrollmentEnabled"`
	Issues            []string                    `json:"issues"`
	Identities        []lifecycle.Identity        `json:"identities"`
	PublicACME        *lifecycle.PublicACMEStatus `json:"publicAcme,omitempty"`
}

SetupStatus reports certificate readiness separately from listeners running in this process. Bootstrap can complete before the first server start.

type SinkConfig

type SinkConfig struct {
	// Dispatch bounds the application-owned in-process event bus.
	// It does not configure DDM synchronization or device command workers.
	Dispatch event.AsyncConfig
	// Audit writes a projected slog record for each event delivered to the sink.
	// Retention depends on the configured log destination.
	Audit bool
	// WebhookURL receives projected events in a MicroMDM-compatible envelope without
	// raw_payload.
	WebhookURL string
	// WebhookRootCAFile configures private HTTPS trust for the receiver.
	WebhookRootCAFile string
	// WebhookHMACKey signs the webhook body when set.
	WebhookHMACKey []byte
	// Persist delivers captured occurrences to the SQL audit trail. The event
	// store retains pending deliveries until this destination acknowledges them.
	Persist bool
	// AuditStore overrides Persist with a caller's own trail.
	AuditStore audit.Store
	// Retention is the maximum record age. Zero disables age-based pruning.
	Retention time.Duration
	// PruneInterval is how often retention runs; DefaultAuditPruneInterval
	// when unset.
	PruneInterval time.Duration
}

SinkConfig enables event logging, webhooks and audit delivery. SQL deployments always capture projected occurrences, with independent destination retries.

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