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 ¶
- Decision record 0023: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/research/decisions/0023-ddm-adapters-and-wire-contract.md
- Decision record 0025: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/research/decisions/0025-reference-server-roles-and-container.md
- Threat model: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/security/threat-model.md (trust boundaries 5 and 6)
- E2E scenarios: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/testing/e2e-scenarios.md (E2E-010, E2E-015)
- Container: Dockerfile, scripts/testdb.sh (ddm-up)
Index ¶
- Constants
- Variables
- func ACMEKeyFromEnvForTests(v string) ([]byte, error)
- func ACMEStatusForTests(err error) int
- func AdminActions() []adminauth.Action
- func AxMStatusForTests(err error) int
- func CertificateFromPEM(data []byte) (*x509.Certificate, error)
- func DEPStatusForTests(err error) int
- func InitSetupFile(o SetupInitOptions) (string, error)
- func ParseDiscovery(s string) (map[discovery.ModelFamily]string, error)
- type ACMEConfig
- type AccountAdmissionRule
- type AdmissionGrant
- type AdmissionPolicy
- type AdmissionRequest
- type App
- func (a *App) ACMEIdentifierForTests(b acme.Binding) (string, error)
- func (a *App) ACMEStoreForTests() acme.Store
- func (a *App) AdminRoutes() []adminRoute
- func (a *App) CertificateSetupStatus(ctx context.Context) (SetupStatus, error)
- func (a *App) Close() error
- func (a *App) DEPStoreForTests() dep.Store
- func (a *App) ExecuteSetup(ctx context.Context, kind lifecycle.Kind, operation string, req SetupRequest) (SetupResult, error)
- func (a *App) ExportEnrollmentProfile(ctx context.Context, req EnrollmentProfileRequest) ([]byte, error)
- func (a *App) LoadTLSCertificate(ctx context.Context) (*tls.Certificate, error)
- func (a *App) ManagedTLSConfig(hello *tls.ClientHelloInfo) (*tls.Config, error)
- func (a *App) Run(ctx context.Context) error
- func (a *App) SetupTrustProfile(ctx context.Context) ([]byte, error)
- func (a *App) TLSCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
- func (a *App) TLSClientRoots() *x509.CertPool
- func (a *App) Workers() []WorkerState
- type AppPushConfig
- type AxMConfig
- type Config
- type DEPConfig
- type DeviceAdmissionRule
- type EnrollConfig
- type EnrollmentAdmission
- type EnrollmentProfileRequest
- type IssuerFiles
- type OIDCConfig
- type PKIConfig
- type PushConfig
- type RateLimitConfig
- type Role
- type RouteQuota
- type SetupConfig
- type SetupFile
- type SetupInitOptions
- type SetupRequest
- type SetupResult
- type SetupStatus
- type SinkConfig
- type WorkerState
Constants ¶
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.
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.
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).
const ( ActionReadCertificates = "readCertificates" ActionImportCertificates = "importCertificates" ActionRevokeCertificates = "revokeCertificates" )
const ( ActionReadSetup = "readCertificateSetup" ActionManageVendor = "manageVendorSigning" ActionSignVendorCSR = "signCustomerPushRequests" ActionManageHTTPS = "manageHTTPSCertificates" ActionManageIssuer = "manageEnrollmentIssuers" )
const ( PathMDM = "/mdm" PathDDM = "/ddm" PathHealthz = "/healthz" PathAdmin = "/admin/v1/" )
Paths served by the handler.
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).
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.
const ( DefaultRole = RoleAll DefaultListen = "127.0.0.1:8080" DefaultStorage = "sqlite" DefaultDSN = "dm.db" )
Defaults applied by ParseEnv when a variable is unset.
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.
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.
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.
const ( PathServiceConfig = "/MDMServiceConfig" PathTrustAnchors = "/enroll/trust/anchors" PathTrustProfile = "/enroll/trust/profile" )
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.
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.
const DefaultAuditPruneInterval = time.Hour
DefaultAuditPruneInterval is how often the retention worker runs.
const DefaultAuditRetention = 90 * 24 * time.Hour
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.
const MaxAdminBody = 1 << 20
MaxAdminBody bounds admin request bodies.
const MaxAxMSerials = 1000
MaxAxMSerials bounds one assignment request.
Variables ¶
var ( ErrBodyTooLarge = errors.New("app: body too large") ErrBadChannel = errors.New("app: channel must be device or user") )
Admin API errors.
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.
var ( AxMWaitInterval = 2 * time.Second AxMWaitTimeout = 5 * time.Minute )
Waits used by the admin API when the caller asks to wait.
var ErrBadACMERequest = errors.New("app: invalid ACME request")
ErrBadACMERequest is a malformed admin request.
var ErrBadAxMRequest = errors.New("app: invalid Business Manager request")
ErrBadAxMRequest reports an invalid assignment body.
var (
ErrBadDEPRequest = errors.New("app: invalid DEP request")
)
DEP errors.
var ErrConfig = errors.New("app: invalid configuration")
ErrConfig reports an invalid configuration.
Functions ¶
func ACMEKeyFromEnvForTests ¶
ACMEKeyFromEnvForTests exposes the key reader to tests of the wiring.
func ACMEStatusForTests ¶
ACMEStatusForTests exposes the error mapping to tests of the wiring.
func AdminActions ¶
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 ¶
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 ¶
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 OpenSetup ¶
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 ¶
ACMEIdentifierForTests mints a client identifier, so a test can order without composing a whole enrollment profile.
func (*App) ACMEStoreForTests ¶
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 ¶
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 ¶
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 ¶
LoadTLSCertificate verifies the active HTTPS identity for startup and probes.
func (*App) ManagedTLSConfig ¶
ManagedTLSConfig refreshes device trust for new connections without mutating a certificate pool already in use by another TLS handshake.
func (*App) Run ¶
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 ¶
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 ¶
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.
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 ¶
LoadSetupFile combines file settings with explicit environment overrides.
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 ¶
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 ¶
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 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 ¶
WorkerState reports one supervised loop and whether it is running.
Source Files
¶
- acme.go
- acmecredential.go
- acmetarget.go
- admin.go
- adminaudit.go
- adminauthz.go
- adminbench.go
- adminevents.go
- adminintrospect.go
- adminmdm.go
- adminpki.go
- adminprincipals.go
- adminsetup.go
- admintransaction.go
- admission.go
- app.go
- apppush.go
- axm.go
- dep.go
- doc.go
- enroll.go
- env.go
- errors.go
- events.go
- eventstore.go
- issuance.go
- managedissuers.go
- ota.go
- outbound.go
- push.go
- renewhttpstrust.go
- renewidentities.go
- replacement.go
- security.go
- securityenv.go
- serviceconfig.go
- setup.go
- setupfile.go
- setupstatus.go
- userauth.go