Documentation
¶
Overview ¶
Package push is the vocabulary of an MDM push: a Pusher sends one notification per Target and reports a Result, Coalescer collapses bursts, and CertStore supplies the push certificate for a topic.
Why ¶
An MDM server cannot talk to a device; it can only ask APNs to wake it, after which the device connects and asks for work. Phase 3 of the plan of record needs that wake-up path to be reliable under bursts and honest about failure: a 410 from APNs marks the token invalid and publishes PushTokenInvalid instead of retrying forever, a burst of changes for one enrollment becomes one push, and a rotated push certificate is picked up without a restart (decision records 0007 and 0015).
This package holds only the vocabulary and the parts that need nothing but it, so that the APNs client can implement Pusher without acquiring a database: resolving an enrollment to a device token, and a topic to a stored certificate, is pushnotify's job (decision record 0044). StaticCertStore is here because a fixed map needs no storage, and serves tests and single-tenant deployments.
Result.Outcome is what a caller acts on. It separates a token APNs says is dead (410, and only 410) from a request APNs refused — a wrong topic, a mismatched or expired certificate, the sandbox environment — because the second is normally true of every device at once and must not be read as a fleet that has gone quiet (decision record 0042).
The HTTP/2 client that actually talks to Apple is appleplatformservices/push/apns, certificate parsing is pushcert (standard library only, so storage can validate an uploaded certificate without depending on push), and fakes are appleplatformservices/push/pushtest.
References ¶
- Decision record 0044: docs/research/decisions/0044-repository-layout.md
- Decision record 0007: docs/research/decisions/0007-apns-push.md
- Decision record 0015: docs/research/decisions/0015-push-cert-store.md
- Decision record 0042: docs/research/decisions/0042-push-failure-classification.md
- Plan of record: docs/research/implementation_plan.md (phase 3)
- Threat model: docs/security/threat-model.md (Push rows)
- End-to-end scenarios: docs/testing/e2e-scenarios.md (E2E-006, E2E-007)
- Apple: https://developer.apple.com/documentation/devicemanagement/setting-up-push-notifications-for-your-device-management-customers
- Apple: https://developer.apple.com/documentation/devicemanagement/dealing-with-inactive-managed-devices-and-invalid-push-tokens
- Schema: third_party/device-management/mdm/checkin/tokenupdate.yaml (Topic, PushMagic, Token)
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNoCertificate = errors.New("push: no push certificate for topic") ErrCertExpired = errors.New("push: push certificate expired") ErrInvalidToken = errors.New("push: device token invalid") ErrRejected = errors.New("push: APNs rejected the request") ErrRateLimited = errors.New("push: rate limited") ErrUpstream = errors.New("push: APNs error") )
Errors returned by this package.
var ErrCoalesced = errors.New("push: coalesced with a recent push")
ErrCoalesced marks a push that was skipped because one was sent recently.
var Outcomes = []Outcome{ OutcomeSent, OutcomeInvalidToken, OutcomeRejected, OutcomeRateLimited, OutcomeUnavailable, OutcomeSkipped, }
Outcomes lists every outcome, for exhaustiveness tests and label sets.
Functions ¶
This section is empty.
Types ¶
type CertStore ¶
type CertStore interface {
// PushCertificate returns the certificate with its private key. The
// certificate must contain the topic as its UID.
PushCertificate(ctx context.Context, topic string) (tls.Certificate, error)
}
CertStore provides the APNs push certificate for a topic.
type Coalescer ¶
type Coalescer struct {
// contains filtered or unexported fields
}
Coalescer drops repeated pushes to the same enrollment inside a window: a device that was just woken will fetch every queued command anyway.
type Outcome ¶
type Outcome string
Outcome classifies what happened to one push. It is a closed set, so a caller may switch on it exhaustively and use it as a metric label.
The distinction that matters is between OutcomeInvalidToken and OutcomeRejected. The first says this device will never receive a push again; the second says this request was wrong, which is usually a property of the topic, the certificate, or the environment rather than of the device, and so is usually true of every device at once. Collapsing them lets one misconfiguration read as a fleet that has gone silent.
const ( // OutcomeSent: APNs accepted the notification. OutcomeSent Outcome = "sent" // OutcomeInvalidToken: this token will not work again. Apple states this // only for status 410 ("there is no need to send further pushes to the // same device token"), so only 410 produces it. OutcomeInvalidToken Outcome = "invalid-token" // OutcomeRejected: APNs refused the request and will refuse an identical // one, but said nothing about the device. A wrong topic, an expired or // mismatched push certificate, the sandbox environment, or a malformed // request all land here. It needs an operator, not a retry, and it is // not grounds for treating the enrollment as gone. OutcomeRejected Outcome = "rejected" // OutcomeRateLimited: APNs asked for a pause. RetryAfter carries what it // asked for, when it said. OutcomeRateLimited Outcome = "rate-limited" // succeed on retry. OutcomeUnavailable Outcome = "unavailable" // OutcomeSkipped: nothing was sent to APNs, because the enrollment has // no usable push info or because a Coalescer dropped the push as a // duplicate. Err says which. OutcomeSkipped Outcome = "skipped" )
Push outcomes.
type Pusher ¶
type Pusher interface {
// Push sends to every target and returns a Result per enrollment id.
// The error is for failures that affect the whole batch (no
// certificate, context cancelled); per-target failures are in Results.
Push(ctx context.Context, targets []Target) (map[mdm.EnrollmentID]Result, error)
}
Pusher sends MDM pushes.
type Result ¶
type Result struct {
// Outcome classifies the result. The zero value is not a valid outcome;
// a Pusher always sets it.
Outcome Outcome
// RetryAfter is what APNs asked to wait, and zero when it asked for
// nothing. It is not a recommendation this package invents: a caller
// that wants a floor applies its own, or apns.DefaultRetryAfter.
RetryAfter time.Duration
// Status and Reason are the APNs HTTP status and reason string. Reason
// is one of the values in apns.Reasons when APNs sent one.
Status int
Reason string
// APNSID is the apns-id header of an accepted push.
APNSID string
Err error
}
Result is the outcome for one target.
func (Result) TokenInvalid ¶
TokenInvalid reports whether APNs said this token will never work again, which is the only outcome that justifies giving up on an enrollment.
type StaticCertStore ¶
type StaticCertStore map[string]tls.Certificate
StaticCertStore serves fixed certificates by topic.
func (StaticCertStore) PushCertificate ¶
func (s StaticCertStore) PushCertificate(_ context.Context, topic string) (tls.Certificate, error)
PushCertificate implements CertStore.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package apns is the APNs HTTP/2 client for MDM pushes.
|
Package apns is the APNs HTTP/2 client for MDM pushes. |
|
Package pushtest provides a scripted push.Pusher and an in-process APNs server so push behaviour is testable without Apple.
|
Package pushtest provides a scripted push.Pusher and an in-process APNs server so push behaviour is testable without Apple. |