Documentation
¶
Index ¶
- Constants
- Variables
- func APIOnlyPayloadTypes(config json.RawMessage) []string
- func CanonicalPayloadType(payloadType string) string
- func ConfigHasPayloads(config json.RawMessage) error
- func ConvertMobileconfig(data []byte, filterUnsupported bool) (json.RawMessage, []string, error)
- func ConvertMobileconfigVerbatim(data []byte, filterUnsupported bool) (json.RawMessage, []string, error)
- func ConvertPlist(data []byte, payloadType, displayName string) (json.RawMessage, []string, error)
- func DroppedKeys(sent, stored json.RawMessage) []string
- func ExtractProfileIdentifiers(data []byte) (uuid, identifier string, err error)
- func ExtractSignedProfile(data []byte) ([]byte, error)
- func FormatComponentJSON(config json.RawMessage) ([]byte, error)
- func ImportDescription(profileKind, profileName, profileID string, report TakeoverReport, ...) string
- func InjectIdentifiers(newPlist, existingPlist []byte) ([]byte, error)
- func IsSignedProfile(data []byte) bool
- func MissingRequiredKeys(entry map[string]any, defaults *SchemaDefaults) []string
- func NormalizeXML(data []byte) ([]byte, error)
- func PayloadTypeSummary(data []byte) []string
- func ProfileDisplayName(data []byte) string
- func RedactClassicProfilePayloads(body []byte) ([]byte, error)
- func RedactPayloadSecrets(profile []byte) ([]byte, error)
- func StripConfigDefaults(config json.RawMessage, fetcher *SchemaFetcher) (json.RawMessage, []string)
- func StripDefaultKeys(entry map[string]any, defaults *SchemaDefaults) (int, []string)
- func UnlistedPayloadTypes(config json.RawMessage) []string
- func ValidatePayloads(config json.RawMessage, fetcher *SchemaFetcher) (json.RawMessage, []string)
- type DDMComponent
- type DDMConversionResult
- type PayloadDiff
- type SchemaDefaults
- type SchemaFetcher
- type TakeoverReport
Constants ¶
const ConflictWarning = `` /* 276-byte string literal not displayed */
ConflictWarning is printed to stderr when converting profiles to blueprint components.
const RedactedPayloadValue = "<redacted>"
RedactedPayloadValue is what a secret inside a profile payload is replaced by.
Variables ¶
var DisabledPayloadTypes = map[string]bool{ "com.apple.ADCertificate.managed": true, "com.apple.DirectoryService.managed": true, "com.apple.MCX.FileVault2": true, "com.apple.airplay": true, "com.apple.airplay.security": true, "com.apple.cellular": true, "com.apple.dnsSettings.managed": true, "com.apple.education": true, "com.apple.ews.account": true, "com.apple.extensiblesso": true, "com.apple.font": true, "com.apple.profileRemovalPassword": true, "com.apple.proxy.http.global": true, "com.apple.security.pem": true, "com.apple.security.pkcs1": true, "com.apple.security.pkcs12": true, "com.apple.security.root": true, "com.apple.security.scep": true, "com.apple.vpn.managed": true, "com.apple.vpn.managed.appmapping": true, "com.apple.webClip.managed": true, "com.apple.webcontent-filter": true, }
DisabledPayloadTypes lists the Apple MDM payload types that Jamf Platform blueprints explicitly refuses. Sending one of these in a com.jamf.ddm-configuration-profile component makes the API reject the whole blueprint with a 400 "Payload disabled: <type>" (independent of the payload's keys). Every other real Apple payload type is accepted and validated against Apple's published schema by the API itself, so this is a denylist rather than an allowlist.
Wire-probed against the blueprints API by POSTing every Apple MDM payloadtype (apple/device-management/mdm/profiles) 2026-07-17. This set is instance/version-specific and may drift — the API is the ultimate authority.
var SecretPayloadKeySuffixes = []string{"password", "secret", "token", "authkey", "apikey", "accesskey", "privatekey", "secretkey", "passcode", "credential"}
SecretPayloadKeySuffixes are the endings, compared case-insensitively, of a payload key whose value is a secret that works outside the profile. They cover the words namesASecret in the MCP guards counts as a secret name. A bare "pin" is not one: it ends far more keys than it protects.
var SupportedPayloadTypes = map[string]bool{ "com.apple.AssetCache.managed": true, "com.apple.Dictionary": true, "com.apple.DiscRecording": true, "com.apple.MCX.Accounts": true, "com.apple.MCX.EnergySaver": true, "com.apple.MCX.MobileAccounts": true, "com.apple.MCX.TimeMachine": true, "com.apple.MCX.TimeServer": true, "com.apple.ManagedClient.preferences": true, "com.apple.NSExtension": true, "com.apple.SetupAssistant.managed": true, "com.apple.SystemConfiguration": true, "com.apple.TCC.configuration-profile-policy": true, "com.apple.airprint": true, "com.apple.app.lock": true, "com.apple.applicationaccess": true, "com.apple.applicationaccess.new": true, "com.apple.appstore": true, "com.apple.asam": true, "com.apple.associated-domains": true, "com.apple.cellularprivatenetwork.managed": true, "com.apple.conferenceroomdisplay": true, "com.apple.desktop": true, "com.apple.dnsProxy.managed": true, "com.apple.dock": true, "com.apple.domains": true, "com.apple.familycontrols.contentfilter": true, "com.apple.familycontrols.timelimits.v2": true, "com.apple.fileproviderd": true, "com.apple.finder": true, "com.apple.firstactiveethernet.managed": true, "com.apple.firstethernet.managed": true, "com.apple.gamed": true, "com.apple.globalethernet.managed": true, "com.apple.homescreenlayout": true, "com.apple.loginitems.managed": true, "com.apple.loginwindow": true, "com.apple.lom": true, "com.apple.mcxMenuExtras": true, "com.apple.mcxprinting": true, "com.apple.networkusagerules": true, "com.apple.notificationsettings": true, "com.apple.preference.security": true, "com.apple.preference.users": true, "com.apple.relay.managed": true, "com.apple.screensaver": true, "com.apple.screensaver.user": true, "com.apple.secondactiveethernet.managed": true, "com.apple.secondethernet.managed": true, "com.apple.security.FDERecoveryKeyEscrow": true, "com.apple.security.acme": true, "com.apple.security.certificatepreference": true, "com.apple.security.certificaterevocation": true, "com.apple.security.certificatetransparency": true, "com.apple.security.firewall": true, "com.apple.security.identitypreference": true, "com.apple.security.smartcard": true, "com.apple.servicemanagement": true, "com.apple.shareddeviceconfiguration": true, "com.apple.syspolicy.kernel-extension-policy": true, "com.apple.system-extension-policy": true, "com.apple.systemmigration": true, "com.apple.systempolicy.control": true, "com.apple.systempolicy.managed": true, "com.apple.systempolicy.rule": true, "com.apple.thirdactiveethernet.managed": true, "com.apple.thirdethernet.managed": true, "com.apple.tvremote": true, "com.apple.universalaccess": true, "com.apple.vpn.managed.applayer": true, "com.apple.wifi.managed": true, "com.apple.xsan": true, "com.apple.xsan.preferences": true, "loginwindow": true, }
SupportedPayloadTypes lists the payload types the blueprints com.jamf.ddm-configuration-profile component accepts as standalone payloads.
The component matches payloadType against a fixed registry — it does NOT validate arbitrary Apple payloads. Wire-probed by POSTing a blueprint carrying one bare payload ({payloadType, payloadIdentifier}) per type: a type outside this set is rejected with the opaque error
steps[0].components[0].configuration: Failed to validate configuration.
which is byte-for-byte what an invented payload type produces. Keys are barely validated by comparison — a made-up key, or a string where the schema wants a boolean, is accepted for most types — so an import that fails validation is almost always carrying a payload type from outside this set.
A type outside this set is still deliverable: wrapped in a com.apple.ManagedClient.preferences (Custom Settings/MCX) payload, the API accepts any preference domain, including third-party ones. See wrapAsManagedPreferences — that is the recovery path, not a filter.
To re-derive after an API change, probe each candidate type bare and treat "Failed to validate configuration." as unsupported. Types the API knows but deliberately blocks report "Payload disabled: <type>" instead and belong in DisabledPayloadTypes.
var UIManageablePayloadTypes = map[string]bool{ "com.apple.Dictionary": true, "com.apple.DiscRecording": true, "com.apple.MCX.Accounts": true, "com.apple.MCX.MobileAccounts": true, "com.apple.MCX.TimeMachine": true, "com.apple.MCX.TimeServer": true, "com.apple.NSExtension": true, "com.apple.SystemConfiguration": true, "com.apple.TCC.configuration-profile-policy": true, "com.apple.airprint": true, "com.apple.app.lock": true, "com.apple.applicationaccess": true, "com.apple.appstore": true, "com.apple.asam": true, "com.apple.cellularprivatenetwork.managed": true, "com.apple.conferenceroomdisplay": true, "com.apple.desktop": true, "com.apple.dnsProxy.managed": true, "com.apple.domains": true, "com.apple.familycontrols.contentfilter": true, "com.apple.fileproviderd": true, "com.apple.finder": true, "com.apple.gamed": true, "com.apple.loginitems.managed": true, "com.apple.loginwindow": true, "com.apple.mcxprinting": true, "com.apple.notificationsettings": true, "com.apple.preference.security": true, "com.apple.preference.users": true, "com.apple.screensaver": true, "com.apple.screensaver.user": true, "com.apple.security.firewall": true, "com.apple.security.smartcard": true, "com.apple.servicemanagement": true, "com.apple.shareddeviceconfiguration": true, "com.apple.syspolicy.kernel-extension-policy": true, "com.apple.systempolicy.control": true, "com.apple.systempolicy.managed": true, "com.apple.tvremote": true, "com.apple.universalaccess": true, "loginwindow": true, }
UIManageablePayloadTypes lists the legacy payload types the Jamf Pro UI can manage directly (the "Legacy payload" picker). The blueprints API accepts many more payload types than these, but any accepted type NOT in this set is delivered as an API-only component: it shows as a read-only "Legacy payload" item in the UI and can only be edited through the blueprints API.
Sourced from the Jamf Pro blueprints UI legacy-payload list. Used only to tailor the API-only advisory on import — it is not a filter, so if it drifts the worst case is a slightly inaccurate warning, not a broken import.
Functions ¶
func APIOnlyPayloadTypes ¶ added in v1.24.0
func APIOnlyPayloadTypes(config json.RawMessage) []string
APIOnlyPayloadTypes returns, in sorted order, the payload types present in a DDMProfileDto configuration that the Jamf Pro UI cannot manage directly — i.e. accepted by the blueprints API but not in UIManageablePayloadTypes. These render as read-only "Legacy payload" items in the UI. Returns nil when every payload in the config is UI-manageable.
func CanonicalPayloadType ¶ added in v1.25.2
CanonicalPayloadType returns the payload type spelling the blueprints API expects, rewriting the Jamf Pro variants in canonicalPayloadTypes. Types with no known variant are returned unchanged.
func ConfigHasPayloads ¶
func ConfigHasPayloads(config json.RawMessage) error
ConfigHasPayloads returns an error if the configuration has an empty payloadContent array, which can happen after StripConfigDefaults removes all payloads.
func ConvertMobileconfig ¶
ConvertMobileconfig parses a mobileconfig (XML plist) and returns a DDMProfileDto configuration suitable for a com.jamf.ddm-configuration-profile blueprint component. When filterUnsupported is true, payloads with unsupported types are silently removed (with a warning). Otherwise they are included and the API will validate them.
func ConvertMobileconfigVerbatim ¶ added in v1.33.0
func ConvertMobileconfigVerbatim(data []byte, filterUnsupported bool) (json.RawMessage, []string, error)
ConvertMobileconfigVerbatim is ConvertMobileconfig without the two recoveries that change a profile's shape: payload types outside SupportedPayloadTypes are sent as themselves instead of being wrapped as Custom Settings (MCX), and payloads with no settings are kept instead of removed. The result is what the installed profile looks like, so a takeover is possible whenever the API accepts it. The API may reject it; ConvertMobileconfig is the fallback.
func ConvertPlist ¶
ConvertPlist parses a raw preference domain plist and wraps it as a single-payload DDMProfileDto configuration. The payloadType must be the Apple preference domain (e.g. "com.apple.dock").
func DroppedKeys ¶ added in v1.33.0
func DroppedKeys(sent, stored json.RawMessage) []string
DroppedKeys compares the configuration sent to the blueprints API with the one it stored and lists, per payload, the keys the API discarded. The API silently drops any key Apple's schema does not define for that payload type, and once a blueprint has taken over a profile those keys are no longer enforced on the device (wire-checked: InterfaceLevel on com.apple.finder).
Only top-level keys of each payload are compared. Payloads are matched by position, which holds because the API preserves their order.
func ExtractProfileIdentifiers ¶
ExtractProfileIdentifiers extracts the top-level PayloadUUID and PayloadIdentifier from a mobileconfig plist. Returns empty strings when the fields are absent (not an error condition).
func ExtractSignedProfile ¶ added in v1.26.0
ExtractSignedProfile returns the inner plist of a CMS-signed (DER/BER) mobileconfig. The signature cannot survive a Classic API upload anyway — the server re-serialises the plist on ingest — so callers extract and send the content, surfacing a note to the user.
func FormatComponentJSON ¶
func FormatComponentJSON(config json.RawMessage) ([]byte, error)
FormatComponentJSON wraps a configuration in a complete component block with the com.jamf.ddm-configuration-profile identifier.
func ImportDescription ¶ added in v1.33.0
func ImportDescription(profileKind, profileName, profileID string, report TakeoverReport, scopeNote string) string
ImportDescription is the blueprint description import-profile writes, saying where the blueprint came from and whether it can take over the installed profile. It stays one short line: the full reasons are in the command's own warning, and a description is read in a list.
func InjectIdentifiers ¶
InjectIdentifiers preserves the PayloadUUID and PayloadIdentifier from an existing mobileconfig plist into a new mobileconfig plist. This prevents macOS/iOS devices from treating a profile update as a new installation, which causes "ghost profiles" where the old profile lingers on devices even though the server considers it replaced.
Returns the modified new plist bytes serialised in the original plist format. If existingPlist is empty or cannot be parsed, newPlist is returned unchanged without error. If newPlist itself cannot be parsed, an error is returned.
func IsSignedProfile ¶ added in v1.26.0
IsSignedProfile reports whether data looks like a CMS/DER-signed mobileconfig (ASN.1 SEQUENCE header) rather than plist XML or a binary plist. A false positive is harmless — ExtractSignedProfile fails cleanly and callers fall back to the original bytes.
func MissingRequiredKeys ¶
func MissingRequiredKeys(entry map[string]any, defaults *SchemaDefaults) []string
MissingRequiredKeys returns the names of required keys that are absent from the payload entry. A payload missing required keys is invalid and should be removed — the DDM API will reject it regardless.
func NormalizeXML ¶ added in v1.26.0
NormalizeXML parses a plist (XML or binary) and re-serialises it as XML with standard entity escaping. Used to eliminate constructs that cannot survive CDATA-wrapping for the Classic API — most notably embedded CDATA sections, whose literal "]]>" terminator would end the wrapper early. Values round-trip exactly; only the byte representation changes.
func PayloadTypeSummary ¶
PayloadTypeSummary returns a human-readable summary of payload types in a mobileconfig.
func ProfileDisplayName ¶
ProfileDisplayName extracts the PayloadDisplayName from a mobileconfig without doing a full conversion. Useful for deriving blueprint names.
func RedactClassicProfilePayloads ¶ added in v1.32.0
RedactClassicProfilePayloads returns a Classic configuration profile body with RedactPayloadSecrets applied to the plist inside each <payloads> element, re-escaped as element text. A <payloads> that does not decode is replaced whole by RedactedPayloadValue. It fails when body is not well-formed XML, so a caller can refuse it rather than print it.
func RedactPayloadSecrets ¶ added in v1.32.0
RedactPayloadSecrets returns profile, a configuration profile plist, with the value of every secret key (isSecretPayloadKey) at any depth replaced by RedactedPayloadValue, and the PayloadContent of a com.apple.security.pkcs12 payload too. A profile with no secret is returned unchanged; one with a secret is re-serialised as XML. It fails when profile is not a plist whose top level is a dictionary.
func StripConfigDefaults ¶
func StripConfigDefaults(config json.RawMessage, fetcher *SchemaFetcher) (json.RawMessage, []string)
StripConfigDefaults removes keys from each payload in a DDMProfileDto configuration whose values match Apple's published defaults. This reduces noise from profiles that set every key even when the value is the Apple default (common with Jamf Pro's UI). The fetcher is used to retrieve Apple's schema for each payload type. Also validates and removes broken payloads (empty payloads, missing required fields).
Returns the modified configuration and a list of human-readable messages about what was stripped.
func StripDefaultKeys ¶
func StripDefaultKeys(entry map[string]any, defaults *SchemaDefaults) (int, []string)
StripDefaultKeys removes keys from a payload entry whose values match the Apple schema defaults. Returns the count of keys stripped. The payloadType and payloadIdentifier keys are never stripped.
func UnlistedPayloadTypes ¶ added in v1.33.0
func UnlistedPayloadTypes(config json.RawMessage) []string
UnlistedPayloadTypes returns, sorted, the payload types in a configuration that SupportedPayloadTypes does not list. A configuration the API accepted that still has some means the list is out of date.
func ValidatePayloads ¶
func ValidatePayloads(config json.RawMessage, fetcher *SchemaFetcher) (json.RawMessage, []string)
ValidatePayloads checks each payload in a DDMProfileDto configuration against Apple's schema and removes payloads that would be rejected by the DDM API (e.g. missing required fields, or payloads with no setting keys). This does not strip defaults — it only validates structural correctness.
Call this before uploading to the API even when --strip-defaults is not used.
Types ¶
type DDMComponent ¶
type DDMComponent struct {
Identifier string `json:"identifier"`
Configuration json.RawMessage `json:"configuration"`
}
DDMComponent represents a native DDM blueprint component produced by converting a legacy mobileconfig payload.
type DDMConversionResult ¶
type DDMConversionResult struct {
// NativeComponents are payloads successfully converted to native DDM.
NativeComponents []DDMComponent
// ProfileConfig is the DDMProfileDto wrapping payloads that could not be
// converted. Nil when every payload was converted to a native component.
ProfileConfig json.RawMessage
// DisplayName from the original mobileconfig's PayloadDisplayName.
DisplayName string
// Warnings about conversion issues (dropped keys, unsupported types, etc.).
Warnings []string
// Conversions describes each successful native DDM conversion
// (e.g. "com.apple.mobiledevice.passwordpolicy -> com.jamf.ddm.passcode-settings").
Conversions []string
}
DDMConversionResult holds the output of converting a mobileconfig where compatible payloads are automatically promoted to native DDM components.
func ConvertToDDMComponents ¶
func ConvertToDDMComponents(data []byte, filterUnsupported bool, fetcher *SchemaFetcher) (*DDMConversionResult, error)
ConvertToDDMComponents parses a mobileconfig and converts compatible payloads to native DDM components. Payloads without a converter are wrapped in a com.jamf.ddm-configuration-profile component. When filterUnsupported is true, unsupported payload types without a DDM converter are removed. When fetcher is non-nil, Apple schema defaults are stripped from payload settings before conversion so that default-valued keys are not actively managed.
type PayloadDiff ¶ added in v1.27.0
PayloadDiff pairs the path of a value the server did not store faithfully with a short explanation of how it differs.
func DiffPayloadValuesDetailed ¶ added in v1.27.0
func DiffPayloadValuesDetailed(intended, stored []byte) ([]PayloadDiff, error)
DiffPayloadValuesDetailed compares an intended mobileconfig plist against the server-stored form and classifies each difference, so callers can surface the remedy that actually applies instead of blaming the PI-827 entity layer for every divergence — the line-break deletion class is far more common and has a workaround. Metadata keys the server rewrites by design are masked (maskedPayloadMetaKeys); string comparison ignores leading/trailing whitespace because the server trims value edges on ingest, and CR/LF differences (see normalizeLineEndings). Keys present only in the stored form (server-injected defaults) are ignored — only the caller's intent is checked.
type SchemaDefaults ¶
SchemaDefaults holds the default values and required field info for an Apple profile schema. Defaults maps key name → default value (only keys that have a default in the schema). Required lists keys with presence: required.
func ParseSchemaDefaults ¶
func ParseSchemaDefaults(data []byte) (*SchemaDefaults, error)
ParseSchemaDefaults extracts default values from a raw Apple profile YAML schema. Exported for testing.
type SchemaFetcher ¶
type SchemaFetcher struct {
// contains filtered or unexported fields
}
SchemaFetcher fetches and caches Apple profile schemas for default stripping.
func NewSchemaFetcher ¶
func NewSchemaFetcher(client *http.Client) *SchemaFetcher
NewSchemaFetcher creates a SchemaFetcher with the given HTTP client. If client is nil, a default client with a 10-second timeout is used.
func (*SchemaFetcher) FetchDefaults ¶
func (f *SchemaFetcher) FetchDefaults(payloadType string) (*SchemaDefaults, error)
FetchDefaults retrieves the Apple schema for the given payload type and returns the default values for its keys. Results are cached per payload type. Returns nil defaults (not an error) if the schema is unavailable.
type TakeoverReport ¶ added in v1.33.0
type TakeoverReport struct {
// Supported is true when the blueprint carries the profile's identity and
// deploying it adopts the installed profile.
Supported bool
// Reasons lists why takeover is not supported. Empty when Supported.
Reasons []string
}
TakeoverReport says whether a blueprint built from a Classic profile can take over the copy of that profile already installed on a device, and if not, why.
Takeover is Apple's legacy-profile declaration (com.apple.configuration.legacy): a DDM profile adopts an MDM-installed one, without reinstalling it, only when the top-level PayloadIdentifier and PayloadUUID match, the payload count matches, and every payload has the same PayloadType, PayloadIdentifier and PayloadUUID in the same order. A profile that fails any of that is not adopted. Worse, if the identity is preserved but a type changed, the declaration is reported invalid and nothing is applied at all (wire-checked 2026-10-06: a 14-payload profile with seven MCX-wrapped payloads reported failed:1 on the device). So identity is only ever preserved when every condition holds.
func ApplyTakeoverIdentity ¶ added in v1.33.0
func ApplyTakeoverIdentity(config json.RawMessage, original []byte, nativeComponents int) (json.RawMessage, TakeoverReport, error)
ApplyTakeoverIdentity decides whether the converted configuration can take over the profile it came from and, when it can, stamps the profile's own identity onto it: the top-level payloadIdentifier/payloadUUID and each payload's.
It judges the converted output against the original profile rather than predicting from the input, so every way the converter can change the shape of a profile (wrapping a payload as MCX, skipping a disabled type, unwrapping an MCX payload, dropping an empty payload, rewriting a type's spelling, promoting payloads to native components) shows up as a mismatch.
nativeComponents is the number of native DDM components the conversion produced alongside the configuration-profile component; any at all means the profile has been split and cannot be adopted.
config is returned unchanged when takeover is not supported: preserving identity there would make the declaration invalid instead of installing alongside the Classic profile.
func NativeConversionReport ¶ added in v1.33.0
func NativeConversionReport(nativeComponents int) TakeoverReport
NativeConversionReport is the takeover verdict for a profile every payload of which was promoted to a native DDM component, leaving no legacy wrapper to stamp identity onto.