fleet/server/fleet/apple_mdm.go
melpike 75982f44de
Rename Apple Business Manager in UI (#42584)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #42512

---------

Co-authored-by: Luke Heath <luke@fleetdm.com>
Co-authored-by: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
2026-04-08 11:14:19 -06:00

1253 lines
55 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fleet
import (
"context"
"crypto/md5" // nolint: gosec
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/fleetdm/fleet/v4/server/mdm"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
)
// Sentinel errors for recovery lock rotation
var (
// ErrRecoveryLockRotationPending indicates a rotation is already in progress for the host.
ErrRecoveryLockRotationPending = errors.New("recovery lock rotation already pending")
// ErrRecoveryLockNotEligible indicates the host is not eligible for rotation
// (e.g., wrong status, operation type, or no existing password).
ErrRecoveryLockNotEligible = errors.New("host not eligible for recovery lock rotation")
)
type MDMAppleCommandIssuer interface {
InstallProfile(ctx context.Context, hostUUIDs []string, profile mobileconfig.Mobileconfig, uuid string) error
RemoveProfile(ctx context.Context, hostUUIDs []string, identifier string, uuid string) error
DeviceLock(ctx context.Context, host *Host, uuid string) (unlockPIN string, err error)
EnableLostMode(ctx context.Context, host *Host, commandUUID string, orgName string) error
DisableLostMode(ctx context.Context, host *Host, commandUUID string) error
EraseDevice(ctx context.Context, host *Host, uuid string) error
InstallEnterpriseApplication(ctx context.Context, hostUUIDs []string, uuid string, manifestURL string) error
DeviceConfigured(ctx context.Context, hostUUID, cmdUUID string) error
SetRecoveryLock(ctx context.Context, hostUUIDs []string, cmdUUID string) error
RotateRecoveryLock(ctx context.Context, hostUUID string, cmdUUID string) error
ClearPasscode(ctx context.Context, hostUUID []string, cmdUUID string) error
}
// MDMAppleEnrollmentType is the type for Apple MDM enrollments.
type MDMAppleEnrollmentType string
const (
// MDMAppleEnrollmentTypeAutomatic is the value for automatic enrollments.
MDMAppleEnrollmentTypeAutomatic MDMAppleEnrollmentType = "automatic"
// MDMAppleEnrollmentTypeManual is the value for manual enrollments.
MDMAppleEnrollmentTypeManual MDMAppleEnrollmentType = "manual"
)
// Well-known status responses
const (
MDMAppleStatusAcknowledged = "Acknowledged"
MDMAppleStatusError = "Error"
MDMAppleStatusCommandFormatError = "CommandFormatError"
MDMAppleStatusIdle = "Idle"
MDMAppleStatusNotNow = "NotNow"
)
// MDMAppleEnrollmentProfilePayload contains the data necessary to create
// an enrollment profile in Fleet.
type MDMAppleEnrollmentProfilePayload struct {
// Type is the type of the enrollment.
Type MDMAppleEnrollmentType `json:"type"`
// DEPProfile is the JSON object with the following Apple-defined fields:
// https://developer.apple.com/documentation/devicemanagement/profile
//
// DEPProfile is nil when Type is MDMAppleEnrollmentTypeManual.
DEPProfile *json.RawMessage `json:"dep_profile"`
// Token should be auto-generated.
Token string `json:"-"`
}
// MDMAppleEnrollmentProfile represents an Apple MDM enrollment profile in Fleet.
// Such enrollment profiles are used to enroll Apple devices to Fleet.
type MDMAppleEnrollmentProfile struct {
// ID is the unique identifier of the enrollment in Fleet.
ID uint `json:"id" db:"id"`
// Token is a random identifier for an enrollment. Currently as the authentication
// token to protect access to the enrollment.
Token string `json:"token" db:"token"`
// Type is the type of the enrollment.
Type MDMAppleEnrollmentType `json:"type" db:"type"`
// DEPProfile is the JSON object with the following Apple-defined fields:
// https://developer.apple.com/documentation/devicemanagement/profile
//
// DEPProfile is nil when Type is MDMAppleEnrollmentTypeManual.
DEPProfile *json.RawMessage `json:"dep_profile" db:"dep_profile"`
// EnrollmentURL is the URL where an enrollment is served.
EnrollmentURL string `json:"enrollment_url" db:"-"`
UpdateCreateTimestamps
}
// AuthzType implements authz.AuthzTyper.
func (m MDMAppleEnrollmentProfile) AuthzType() string {
return "mdm_apple_enrollment_profile"
}
// MDMAppleManualEnrollmentProfile is used for authorization checks to get the standard Fleet manual
// enrollment profile. The actual data is returned as raw bytes.
type MDMAppleManualEnrollmentProfile struct{}
// AuthzType implements authz.AuthzTyper
func (m MDMAppleManualEnrollmentProfile) AuthzType() string {
return "mdm_apple_manual_enrollment_profile"
}
// MDMAppleDEPKeyPair contains the DEP public key certificate and private key pair. Both are PEM encoded.
type MDMAppleDEPKeyPair struct {
PublicKey []byte `json:"public_key"`
PrivateKey []byte `json:"private_key"`
}
// MDMAppleInstaller holds installer packages for Apple devices.
type MDMAppleInstaller struct {
// ID is the unique identifier of the installer in Fleet.
ID uint `json:"id" db:"id"`
// Name is the name of the installer (usually the package file name).
Name string `json:"name" db:"name"`
// Size is the size of the installer package.
Size int64 `json:"size" db:"size"`
// Manifest is the manifest of the installer. Generated from the installer
// contents and ready to use in `InstallEnterpriseApplication` commands.
Manifest string `json:"manifest" db:"manifest"`
// Installer is the actual installer contents.
Installer []byte `json:"-" db:"installer"`
// URLToken is a random token used for authentication to protect access to installers.
// Applications deployede via InstallEnterpriseApplication must be publicly accessible,
// this hard to guess token provides some protection.
URLToken string `json:"url_token" db:"url_token"`
// URL is the full URL where the installer is served.
URL string `json:"url"`
}
// AuthzType implements authz.AuthzTyper.
func (m MDMAppleInstaller) AuthzType() string {
return "mdm_apple_installer"
}
// MDMAppleDevice represents an MDM enrolled Apple device.
type MDMAppleDevice struct {
// ID is the device hardware UUID.
ID string `json:"id" db:"id"`
// SerialNumber is the serial number of the Apple device.
SerialNumber string `json:"serial_number" db:"serial_number"`
// Enabled indicates whether the device is currently enrolled.
// It's set to false when a device unenrolls from Fleet.
Enabled bool `json:"enabled" db:"enabled"`
}
// AuthzType implements authz.AuthzTyper.
func (m MDMAppleDevice) AuthzType() string {
return "mdm_apple_device"
}
// MDMAppleDEPDevice represents an Apple device in Apple Business (AB).
type MDMAppleDEPDevice struct {
godep.Device
}
// AuthzType implements authz.AuthzTyper.
func (m MDMAppleDEPDevice) AuthzType() string {
return "mdm_apple_dep_device"
}
// These following types are copied from nanomdm.
// EnrolledAPIResult is a per-enrollment API result.
type EnrolledAPIResult struct {
PushError string `json:"push_error,omitempty"`
PushResult string `json:"push_result,omitempty"`
CommandError string `json:"command_error,omitempty"`
}
// EnrolledAPIResults is a map of enrollments to a per-enrollment API result.
type EnrolledAPIResults map[string]*EnrolledAPIResult
type MDMAppleCommandTimeoutError struct{}
func (e MDMAppleCommandTimeoutError) Error() string {
return "Timeout waiting for MDM device to acknowledge command"
}
func (e MDMAppleCommandTimeoutError) StatusCode() int {
return http.StatusGatewayTimeout
}
type PayloadScope string
const (
PayloadScopeUser PayloadScope = "User"
PayloadScopeSystem PayloadScope = "System"
)
// MDMAppleConfigProfile represents an Apple MDM configuration profile in Fleet.
// Configuration profiles are used to configure Apple devices .
// See also https://developer.apple.com/documentation/devicemanagement/configuring_multiple_devices_using_profiles.
type MDMAppleConfigProfile struct {
// ProfileUUID is the unique identifier of the configuration profile in
// Fleet. For Apple profiles, it is the letter "a" followed by a uuid.
ProfileUUID string `db:"profile_uuid" json:"profile_uuid"`
// Deprecated: ProfileID is the old unique id of the configuration profile in
// Fleet. It is still maintained and generated for new profiles, but only
// used in legacy API endpoints.
ProfileID uint `db:"profile_id" json:"profile_id"`
// TeamID is the id of the team with which the configuration is associated. A nil team id
// represents a configuration profile that is not associated with any team.
TeamID *uint `db:"team_id" json:"team_id" renameto:"fleet_id"`
// Identifier corresponds to the payload identifier of the associated mobileconfig payload.
// Fleet requires that Identifier must be unique in combination with the Name and TeamID.
Identifier string `db:"identifier" json:"identifier"`
// Scope is the PayloadScope attribute of the profile. It is used to determine how the profile
// is applied. Valid values are "User" and "System". System profiles are applied to the
// device channel whereas User scoped profiles are applied to the user channel if it exists for
// a given host, otherwise the device channel.
Scope PayloadScope `db:"scope" json:"scope"`
// Name corresponds to the payload display name of the associated mobileconfig payload.
// Fleet requires that Name must be unique in combination with the Identifier and TeamID.
Name string `db:"name" json:"name"`
// Mobileconfig is the byte slice corresponding to the XML property list (i.e. plist)
// representation of the configuration profile. It must be XML or PKCS7 parseable.
Mobileconfig mobileconfig.Mobileconfig `db:"mobileconfig" json:"-"`
// Checksum is an MD5 hash of the Mobileconfig bytes
Checksum []byte `db:"checksum" json:"checksum,omitempty"`
LabelsIncludeAll []ConfigurationProfileLabel `db:"-" json:"labels_include_all,omitempty"`
LabelsIncludeAny []ConfigurationProfileLabel `db:"-" json:"labels_include_any,omitempty"`
LabelsExcludeAny []ConfigurationProfileLabel `db:"-" json:"labels_exclude_any,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UploadedAt time.Time `db:"uploaded_at" json:"updated_at"` // NOTE: JSON field is still `updated_at` for historical reasons, would be an API breaking change
SecretsUpdatedAt *time.Time `db:"secrets_updated_at" json:"-"`
}
// MDMProfilesUpdates flags updates that were done during batch processing of profiles.
type MDMProfilesUpdates struct {
AppleConfigProfile bool
WindowsConfigProfile bool
AppleDeclaration bool
AndroidConfigProfile bool
}
// ConfigurationProfileLabel represents the many-to-many relationship between
// profiles and labels.
//
// NOTE: json representation of the fields is a bit awkward to match the
// required API response, as this struct is returned within profile
// responses.
//
// NOTE The fields in this struct other than LabelName and LabelID
// MAY NOT BE SET CORRECTLY, dependong on where they're being ingested from.
type ConfigurationProfileLabel struct {
ProfileUUID string `db:"profile_uuid" json:"-"`
LabelName string `db:"label_name" json:"name"`
LabelID uint `db:"label_id" json:"id,omitempty"` // omitted if 0 (which is impossible if the label is not broken)
Broken bool `db:"broken" json:"broken,omitempty"` // omitted (not rendered to JSON) if false
Exclude bool `db:"exclude" json:"-"` // not rendered in JSON, used to store the profile in LabelsIncludeAll, LabelsIncludeAny, or LabelsExcludeAny on the parent profile
RequireAll bool `db:"require_all" json:"-"` // not rendered in JSON, used to store the profile in LabelsIncludeAll, LabelsIncludeAny, or LabelsIncludeAny on the parent profile
}
func NewMDMAppleConfigProfile(raw []byte, teamID *uint) (*MDMAppleConfigProfile, error) {
mc := mobileconfig.Mobileconfig(raw)
cp, err := mc.ParseConfigProfile()
if err != nil {
return nil, fmt.Errorf("new MDMAppleConfigProfile: %w", err)
}
return &MDMAppleConfigProfile{
TeamID: teamID,
Identifier: cp.PayloadIdentifier,
Name: cp.PayloadDisplayName,
Mobileconfig: mc,
Scope: PayloadScope(cp.PayloadScope),
}, nil
}
// payloadDisplayNameRegex is used to extract PayloadDisplayName values from raw XML content
var payloadDisplayNameRegex = regexp.MustCompile(`<key>PayloadDisplayName</key>\s*<string>([^<]*)</string>`)
// ValidateNoSecretsInProfileName checks if PayloadDisplayName contains FLEET_SECRET_ variables
// in the raw XML content of a profile.
func ValidateNoSecretsInProfileName(xmlContent []byte) error {
matches := payloadDisplayNameRegex.FindAllSubmatch(xmlContent, -1)
for _, match := range matches {
if len(match) > 1 {
displayName := string(match[1])
if len(ContainsPrefixVars(displayName, ServerSecretPrefix)) > 0 {
return errors.New("PayloadDisplayName cannot contain FLEET_SECRET variables")
}
}
}
return nil
}
func (cp MDMAppleConfigProfile) ValidateUserProvided(allowCustomOSUpdatesAndFileVault bool) error {
// first screen the top-level object for reserved identifiers and names
if _, ok := mobileconfig.FleetPayloadIdentifiers()[cp.Identifier]; ok {
return fmt.Errorf("payload identifier %s is not allowed", cp.Identifier)
}
fleetNames := mdm.FleetReservedProfileNames()
if _, ok := fleetNames[cp.Name]; ok {
return fmt.Errorf("payload display name %s is not allowed", cp.Name)
}
// then screen the payload content for reserved identifiers, names, and types
return cp.Mobileconfig.ScreenPayloads(allowCustomOSUpdatesAndFileVault)
}
// HostMDMAppleProfile represents the status of an Apple MDM profile in a host.
type HostMDMAppleProfile struct {
HostUUID string `db:"host_uuid" json:"-"`
CommandUUID string `db:"command_uuid" json:"-"`
ProfileUUID string `db:"profile_uuid" json:"profile_uuid"`
Name string `db:"name" json:"name"`
Identifier string `db:"identifier" json:"-"`
Status *MDMDeliveryStatus `db:"status" json:"status"`
OperationType MDMOperationType `db:"operation_type" json:"operation_type"`
Detail string `db:"detail" json:"detail"`
VariablesUpdatedAt *time.Time `db:"variables_updated_at" json:"-"`
Scope PayloadScope `db:"scope" json:"scope"`
ManagedLocalAccount string `db:"managed_local_account" json:"managed_local_account"`
}
// ToHostMDMProfile converts the HostMDMAppleProfile to a HostMDMProfile.
func (p HostMDMAppleProfile) ToHostMDMProfile(platform string) HostMDMProfile {
scope := "device"
if p.Scope == PayloadScopeUser {
scope = "user"
}
return HostMDMProfile{
HostUUID: p.HostUUID,
ProfileUUID: p.ProfileUUID,
Name: p.Name,
Identifier: p.Identifier,
Status: p.Status.StringPtr(),
OperationType: p.OperationType,
Detail: p.Detail,
Platform: platform,
Scope: &scope,
ManagedLocalAccount: &p.ManagedLocalAccount,
}
}
// HostMDMCertificateProfile represents the status of an MDM certificate profile (SCEP payload) along with the
// associated certificate metadata.
type HostMDMCertificateProfile struct {
HostUUID string `db:"host_uuid"`
ProfileUUID string `db:"profile_uuid"`
Status *MDMDeliveryStatus `db:"status"`
ChallengeRetrievedAt *time.Time `db:"challenge_retrieved_at"`
NotValidBefore *time.Time `db:"not_valid_before"`
NotValidAfter *time.Time `db:"not_valid_after"`
Type CAConfigAssetType `db:"type"`
CAName string `db:"ca_name"`
Serial *string `db:"serial"`
}
type HostMDMProfileDetail string
const (
HostMDMProfileDetailFailedWasVerified HostMDMProfileDetail = "Failed, was verified"
HostMDMProfileDetailFailedWasVerifying HostMDMProfileDetail = "Failed, was verifying"
)
// Message returns a human-friendly message for the detail.
func (d HostMDMProfileDetail) Message() string {
switch d {
case HostMDMProfileDetailFailedWasVerified:
return "This setting had been verified by osquery, but has since been found missing on the host."
case HostMDMProfileDetailFailedWasVerifying:
return "The MDM protocol returned a success but the setting couldnt be verified by osquery."
default:
return string(d)
}
}
type MDMAppleProfilePayload struct {
ProfileUUID string `db:"profile_uuid"`
ProfileIdentifier string `db:"profile_identifier"`
ProfileName string `db:"profile_name"`
HostUUID string `db:"host_uuid"`
HostPlatform string `db:"host_platform"`
Checksum []byte `db:"checksum"`
SecretsUpdatedAt *time.Time `db:"secrets_updated_at"`
Status *MDMDeliveryStatus `db:"status" json:"status"`
OperationType MDMOperationType `db:"operation_type"`
Detail string `db:"detail"`
CommandUUID string `db:"command_uuid"`
IgnoreError bool `db:"ignore_error"`
Scope PayloadScope `db:"scope"`
DeviceEnrolledAt *time.Time `db:"device_enrolled_at"`
}
// DidNotInstallOnHost indicates whether this profile was not installed on the host (and
// therefore is not, as far as Fleet knows, currently on the host).
// The profile in Pending status could be on the host, but Fleet has not received an Acknowledged status yet.
func (p *MDMAppleProfilePayload) DidNotInstallOnHost() bool {
return p.Status != nil && (*p.Status == MDMDeliveryFailed || *p.Status == MDMDeliveryPending) && p.OperationType == MDMOperationTypeInstall
}
// FailedInstallOnHost indicates whether this profile failed to install on the host.
func (p *MDMAppleProfilePayload) FailedInstallOnHost() bool {
return p.Status != nil && *p.Status == MDMDeliveryFailed && p.OperationType == MDMOperationTypeInstall
}
// PendingInstallOnHost indicates whether this profile is pending to install on the host.
// The profile in Pending status could be on the host, but Fleet has not received an Acknowledged status yet.
func (p *MDMAppleProfilePayload) PendingInstallOnHost() bool {
return p.Status != nil && *p.Status == MDMDeliveryPending && p.OperationType == MDMOperationTypeInstall
}
type MDMAppleBulkUpsertHostProfilePayload struct {
ProfileUUID string
ProfileIdentifier string
ProfileName string
HostUUID string
CommandUUID string
OperationType MDMOperationType
Status *MDMDeliveryStatus
Detail string
Checksum []byte
SecretsUpdatedAt *time.Time
IgnoreError bool
VariablesUpdatedAt *time.Time
Scope PayloadScope
}
// MDMAppleFileVaultSummary reports the number of macOS hosts being managed with Apples disk
// encryption profiles. Each host may be counted in only one of six mutually-exclusive categories:
// Verified, Verifying, ActionRequired, Enforcing, Failed, RemovingEnforcement.
type MDMAppleFileVaultSummary struct {
Verified uint `json:"verified" db:"verified"`
Verifying uint `json:"verifying" db:"verifying"`
ActionRequired uint `json:"action_required" db:"action_required"`
Enforcing uint `json:"enforcing" db:"enforcing"`
Failed uint `json:"failed" db:"failed"`
RemovingEnforcement uint `json:"removing_enforcement" db:"removing_enforcement"`
}
// MDMAppleBootstrapPackageSummary reports the number of hosts that are targeted to install the
// MDM bootstrap package. Each host may be counted in only one of three mutually-exclusive categories:
// Failed, Pending, or Installed.
type MDMAppleBootstrapPackageSummary struct {
// Installed includes each host that has acknowledged the MDM command to install the bootstrap
// package.
Installed uint `json:"installed" db:"installed"`
// Pending includes each host that has not acknowledged the MDM command to install the bootstrap
// package or reported an error for such command.
Pending uint `json:"pending" db:"pending"`
// Failed includes each host that has reported an error for the MDM command to install the
// bootstrap package.
Failed uint `json:"failed" db:"failed"`
}
// MDMAppleFleetdConfig contains the fields used to configure
// `fleetd` in macOS devices via a configuration profile.
type MDMAppleFleetdConfig struct {
FleetURL string
EnrollSecret string
EnableScripts bool
}
// MDMCustomEnrollmentProfileItem represents an MDM enrollment profile item that
// contains custom fields.
type MDMCustomEnrollmentProfileItem struct {
EndUserEmail string
}
// MDMApplePreassignProfilePayload is the payload accepted by the endpoint that
// preassigns profiles to hosts before generating corresponding teams for each
// unique set of profiles and assigning hosts to those teams and profiles. For
// example, puppet scripts use this.
type MDMApplePreassignProfilePayload struct {
ExternalHostIdentifier string `json:"external_host_identifier"`
HostUUID string `json:"host_uuid"`
Profile []byte `json:"profile"`
Group string `json:"group"`
Exclude bool `json:"exclude"`
}
// HexMD5Hash returns the hex-encoded MD5 hash of the profile. Note that MD5 is
// broken and we should consider moving to a better hash, but it needs to match
// the hashing algorithm used by the Mysql database for profiles (SHA2 would be
// an option: https://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html#function_sha2).
func (p MDMApplePreassignProfilePayload) HexMD5Hash() string {
sum := md5.Sum(p.Profile) //nolint: gosec
// mysql's HEX function returns uppercase
return strings.ToUpper(hex.EncodeToString(sum[:]))
}
// MDMApplePreassignHostProfiles represents the set of profiles that were
// pre-assigned to a given host identified by its UUID.
type MDMApplePreassignHostProfiles struct {
HostUUID string
Profiles []MDMApplePreassignProfile
}
// MDMApplePreassignProfile represents a single profile pre-assigned to a host.
type MDMApplePreassignProfile struct {
Profile []byte
Group string
HexMD5Hash string
Exclude bool
}
// MDMAppleSettingsPayload describes the payload accepted by the endpoint to
// update specific MDM macos settings for a team (or no team).
type MDMAppleSettingsPayload struct {
TeamID *uint `json:"team_id" renameto:"fleet_id"`
EnableDiskEncryption *bool `json:"enable_disk_encryption"`
}
// AuthzType implements authz.AuthzTyper.
func (p MDMAppleSettingsPayload) AuthzType() string {
return "mdm_apple_settings"
}
// MDMAppleSetupPayload describes the payload accepted by the endpoint to
// update specific MDM macos setup values for a team (or no team).
type MDMAppleSetupPayload struct {
TeamID *uint `json:"team_id" renameto:"fleet_id"`
EnableEndUserAuthentication *bool `json:"enable_end_user_authentication"`
EnableReleaseDeviceManually *bool `json:"enable_release_device_manually" renameto:"apple_enable_release_device_manually"`
ManualAgentInstall *bool `json:"manual_agent_install" renameto:"macos_manual_agent_install"`
RequireAllSoftware *bool `json:"require_all_software_macos"`
RequireAllSoftwareWindows *bool `json:"require_all_software_windows"`
LockEndUserInfo *bool `json:"lock_end_user_info"`
}
// AuthzType implements authz.AuthzTyper.
func (p MDMAppleSetupPayload) AuthzType() string {
return "mdm_apple_settings"
}
// HostDEPAssignment represents a row in the host_dep_assignments table.
type HostDEPAssignment struct {
// HostID is the id of the host in Fleet.
HostID uint `db:"host_id" json:"-"`
// AddedAt is the timestamp when Fleet was notified that device was added to the Fleet MDM
// server in Apple Busines Manager (AB).
AddedAt time.Time `db:"added_at" json:"added_at"`
// DeletedAt is the timestamp when Fleet was notified that device was deleted from the Fleet
// MDM server in Apple Busines Manager (AB).
DeletedAt *time.Time `db:"deleted_at" json:"deleted_at"`
// ABMTokenID is the ID of the ABM token that was used to make this DEP assignment.
ABMTokenID *uint `db:"abm_token_id" json:"abm_token_id"`
// MDMMigrationDeadline is the deadline for the MDM migration received from ABM on the host's
// most recent sync.
MDMMigrationDeadline *time.Time `db:"mdm_migration_deadline" json:"mdm_migration_deadline,omitempty"`
// MDMMigrationCompleted is the value of MDMMigrationDeadline when the host completed its last
// Migration. Not a timestamp but a marker that the host completed the Migration for a given
// date.
MDMMigrationCompleted *time.Time `db:"mdm_migration_completed" json:"mdm_migration_completed,omitempty"`
// ProfileUUID is the UUID of the enrollment profile last assigned by Fleet via ABM.
ProfileUUID *string `db:"profile_uuid" json:"profile_uuid,omitempty"`
// AssignProfileResponse is the status returned by Apple when Fleet last
// assigned the enrollment profile (SUCCESS, FAILED, NOT_ACCESSIBLE, THROTTLED).
AssignProfileResponse *DEPAssignProfileResponseStatus `db:"assign_profile_response" json:"assign_profile_response,omitempty"`
// ResponseUpdatedAt is the timestamp when AssignProfileResponse was last updated.
ResponseUpdatedAt *time.Time `db:"response_updated_at" json:"response_updated_at,omitempty"`
}
func (h *HostDEPAssignment) IsDEPAssignedToFleet() bool {
if h == nil {
return false
}
return h.HostID > 0 && !h.AddedAt.IsZero() && h.DeletedAt == nil
}
type DEPAssignProfileResponseStatus string
const (
DEPAssignProfileResponseSuccess DEPAssignProfileResponseStatus = "SUCCESS"
DEPAssignProfileResponseNotAccessible DEPAssignProfileResponseStatus = "NOT_ACCESSIBLE"
DEPAssignProfileResponseFailed DEPAssignProfileResponseStatus = "FAILED"
DEPAssignProfileResponseThrottled DEPAssignProfileResponseStatus = "THROTTLED"
)
// NanoEnrollment represents a row in the nano_enrollments table managed by
// nanomdm. It is meant to be used internally by the server, not to be returned
// as part of endpoints, and as a precaution its json-encoding is explicitly
// ignored.
type NanoEnrollment struct {
ID string `json:"-" db:"id"`
DeviceID string `json:"-" db:"device_id"`
Type string `json:"-" db:"type"`
Enabled bool `json:"-" db:"enabled"`
TokenUpdateTally int `json:"-" db:"token_update_tally"`
}
// NanoUser represents a row in the nano_users table of Managed Users, managed by
// nanomdm. It is meant to be used internally by the server, not to be returned
// as part of endpoints, and as a precaution its json-encoding is explicitly
// ignored.
type NanoUser struct {
ID string `json:"-" db:"id"`
DeviceID string `json:"-" db:"device_id"`
UserShortName string `json:"-" db:"user_short_name"`
UserLongName string `json:"-" db:"user_long_name"`
}
// MDMAppleCommand represents an MDM Apple command that has been enqueued for
// execution. It is similar to MDMAppleCommandResult, but a separate struct is
// used as there are plans to evolve the `fleetctl get mdm-commands` command
// output in the future to list one row per command instead of one per
// command-host combination, and this fleetctl command is the only use of this
// struct at the moment. Also, it is filled a bit differently than what we do
// in MDMAppleCommandResult, since it needs to join with the hosts in the
// query to make authorization (retrieving the team id) manageable.
//
// https://github.com/fleetdm/fleet/issues/11008#issuecomment-1503466119
type MDMAppleCommand struct {
// DeviceID is the MDM enrollment ID. This is the same as the host UUID.
DeviceID string `json:"device_id" db:"device_id"`
// CommandUUID is the unique identifier of the command.
CommandUUID string `json:"command_uuid" db:"command_uuid"`
// UpdatedAt is the last update timestamp of the command result.
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
// RequestType is the command's request type, which is basically the
// command name.
RequestType string `json:"request_type" db:"request_type"`
// Status is the command status. One of Acknowledged, Error, or NotNow.
Status string `json:"status" db:"status"`
// Hostname is the hostname of the host that executed the command.
Hostname string `json:"hostname" db:"hostname"`
// TeamID is the host's team, null if the host is in no team. This is used
// to authorize the user to see the command, it is not returned as part of
// the response payload.
TeamID *uint `json:"-" db:"team_id"`
}
// MDMAppleSetupAssistant represents the setup assistant set for a given team
// or no team.
type MDMAppleSetupAssistant struct {
ID uint `json:"-" db:"id"`
TeamID *uint `json:"team_id" renameto:"fleet_id" db:"team_id"`
Name string `json:"name" db:"name"`
Profile json.RawMessage `json:"enrollment_profile" db:"profile"`
UploadedAt time.Time `json:"uploaded_at" db:"uploaded_at"`
}
// AuthzType implements authz.AuthzTyper.
func (a MDMAppleSetupAssistant) AuthzType() string {
return "mdm_apple_setup_assistant"
}
// ProfileMatcher defines the methods required to preassign and retrieve MDM
// profiles for matching with teams and associating with hosts. A Redis-based
// implementation is used in production.
type ProfileMatcher interface {
PreassignProfile(ctx context.Context, payload MDMApplePreassignProfilePayload) error
RetrieveProfiles(ctx context.Context, externalHostIdentifier string) (MDMApplePreassignHostProfiles, error)
}
// SCEPIdentityCertificate represents a certificate issued during MDM
// enrollment.
type SCEPIdentityCertificate struct {
Serial string `db:"serial"`
NotValidAfter time.Time `db:"not_valid_after"`
CertificatePEM []byte `db:"certificate_pem"`
}
// SCEPIdentityAssociation represents an association between an identity
// certificate an a specific host.
type SCEPIdentityAssociation struct {
HostUUID string `db:"host_uuid"`
SHA256 string `db:"sha256"`
EnrollReference string `db:"enroll_reference"`
RenewCommandUUID string `db:"renew_command_uuid"`
// EnrolledFromMigration is used for devices migrated via datababse
// dumps (ie: "touchless")
EnrolledFromMigration bool `db:"enrolled_from_migration"`
// EnrollmentType is nano_enrollment.type and should be examined to determine
// the proper enrollment profile.
EnrollmentType string `db:"type"`
}
type DeviceInfoForACMERenewal struct {
HostUUID string `db:"host_uuid"`
HardwareSerial string `db:"hardware_serial"`
HardwareModel string `db:"hardware_model"`
OSVersion string `db:"os_version"`
}
// MDMAppleDeclaration represents a DDM JSON declaration.
type MDMAppleDeclaration struct {
// DeclarationUUID is the unique identifier of the declaration in
// Fleet. Since we use the same endpoints for declarations and profiles:
// - This is marshalled as profile_uuid
// - The value has a prefix (TODO: @jahzielv to determine and document this)
DeclarationUUID string `db:"declaration_uuid" json:"profile_uuid"`
// TeamID is the id of the team with which the declaration is associated. A nil team id
// represents a declaration that is not associated with any team.
TeamID *uint `db:"team_id" json:"team_id" renameto:"fleet_id"`
// Identifier corresponds to the "Identifier" key of the associated declaration.
// Fleet requires that Identifier must be unique in combination with the Name and TeamID.
Identifier string `db:"identifier" json:"identifier"`
// Name corresponds to the file name of the associated JSON declaration payload.
// Fleet requires that Name must be unique in combination with the Identifier and TeamID.
Name string `db:"name" json:"name"`
// RawJSON is the raw JSON content of the declaration
RawJSON json.RawMessage `db:"raw_json" json:"-"`
// Token is used to identify if declaration needs to be re-applied.
// It contains the checksum of the JSON contents and secrets updated timestamp (if secret variables are present).
Token string `db:"token" json:"-"`
// labels associated with this Declaration
LabelsIncludeAll []ConfigurationProfileLabel `db:"-" json:"labels_include_all,omitempty"`
LabelsIncludeAny []ConfigurationProfileLabel `db:"-" json:"labels_include_any,omitempty"`
LabelsExcludeAny []ConfigurationProfileLabel `db:"-" json:"labels_exclude_any,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UploadedAt time.Time `db:"uploaded_at" json:"uploaded_at"`
SecretsUpdatedAt *time.Time `db:"secrets_updated_at" json:"-"`
}
type MDMAppleRawDeclaration struct {
// Type is the "Type" field on the raw declaration JSON.
Type string `json:"Type"`
Identifier string `json:"Identifier"`
}
// ForbiddenDeclTypes is a set of declaration types that are not allowed to be
// added by users into Fleet.
var ForbiddenDeclTypes = map[string]struct{}{
"com.apple.configuration.account.caldav": {},
"com.apple.configuration.account.carddav": {},
"com.apple.configuration.account.exchange": {},
"com.apple.configuration.account.google": {},
"com.apple.configuration.account.ldap": {},
"com.apple.configuration.account.mail": {},
"com.apple.configuration.screensharing.connection": {},
"com.apple.configuration.security.certificate": {},
"com.apple.configuration.security.identity": {},
"com.apple.configuration.security.passkey.attestation": {},
"com.apple.configuration.services.configuration-files": {},
"com.apple.configuration.watch.enrollment": {},
}
func (r *MDMAppleRawDeclaration) ValidateUserProvided(allowCustomOSUpdatesAndFileVault bool) error {
var err error
// Check against types we don't allow
if r.Type == `com.apple.configuration.softwareupdate.enforcement.specific` {
if !allowCustomOSUpdatesAndFileVault {
return NewInvalidArgumentError(r.Type, "Declaration profile cant include OS updates settings. To control these settings, go to OS updates.")
}
}
if _, forbidden := ForbiddenDeclTypes[r.Type]; forbidden {
return NewInvalidArgumentError(r.Type, "Only configuration declarations that dont require an asset reference are supported.")
}
if r.Type == "com.apple.configuration.management.status-subscriptions" {
return NewInvalidArgumentError(r.Type, "Declaration profile cant include status subscription type. To get hosts vitals, please use queries and policies.")
}
if !strings.HasPrefix(r.Type, "com.apple.configuration.") {
return NewInvalidArgumentError(r.Type, "Only configuration declarations (com.apple.configuration.) are supported.")
}
return err
}
func GetRawDeclarationValues(raw []byte) (*MDMAppleRawDeclaration, error) {
var rawDecl MDMAppleRawDeclaration
if err := json.Unmarshal(raw, &rawDecl); err != nil {
return nil, NewInvalidArgumentError("declaration", fmt.Sprintf("Couldn't add. The file should include valid JSON: %s", err)).WithStatus(http.StatusBadRequest)
}
return &rawDecl, nil
}
// MDMAppleHostDeclaration represents the state of a declaration on a host
type MDMAppleHostDeclaration struct {
// HostUUID is the uuid of the host affected by this declaration
HostUUID string `db:"host_uuid" json:"-"`
// DeclarationUUID is the unique identifier of the declaration in
// Fleet. Since we use the same endpoints for declarations and profiles:
// - This is marshalled as profile_uuid
// - The value has a prefix (TODO: @jahzielv to determine and document this)
DeclarationUUID string `db:"declaration_uuid" json:"profile_uuid"`
// Name corresponds to the file name of the associated JSON declaration payload.
Name string `db:"declaration_name" json:"name"`
// Identifier corresponds to the "Identifier" key of the associated declaration.
Identifier string `db:"declaration_identifier" json:"-"`
// Status represent the current state of the declaration, as known by the Fleet server.
Status *MDMDeliveryStatus `db:"status" json:"status"`
// Operation type represents the operation being performed.
OperationType MDMOperationType `db:"operation_type" json:"operation_type"`
// Detail contains any messages that must be surfaced to the user,
// either by the MDM protocol or the Fleet server.
Detail string `db:"detail" json:"detail"`
// Token is used to identify if declaration needs to be re-applied.
// It contains the checksum of the JSON contents and secrets updated timestamp (if secret variables are present).
Token string `db:"token" json:"-"`
// SecretsUpdatedAt is the timestamp when the secrets were last updated or when this declaration was uploaded.
SecretsUpdatedAt *time.Time `db:"secrets_updated_at" json:"-"`
}
func (p MDMAppleHostDeclaration) Equal(other MDMAppleHostDeclaration) bool {
statusEqual := p.Status == nil && other.Status == nil || p.Status != nil && other.Status != nil && *p.Status == *other.Status
secretsEqual := p.SecretsUpdatedAt == nil && other.SecretsUpdatedAt == nil || p.SecretsUpdatedAt != nil && other.SecretsUpdatedAt != nil && p.SecretsUpdatedAt.Equal(*other.SecretsUpdatedAt)
return statusEqual &&
p.HostUUID == other.HostUUID &&
p.DeclarationUUID == other.DeclarationUUID &&
p.Name == other.Name &&
p.Identifier == other.Identifier &&
p.OperationType == other.OperationType &&
p.Detail == other.Detail &&
p.Token == other.Token &&
secretsEqual
}
func NewMDMAppleDeclaration(raw []byte, teamID *uint, name string, declType, ident string) *MDMAppleDeclaration {
var decl MDMAppleDeclaration
decl.Identifier = ident
decl.Name = name
decl.RawJSON = raw
decl.TeamID = teamID
return &decl
}
// MDMAppleDDMTokensResponse is the response from the DDM tokens endpoint.
//
// https://developer.apple.com/documentation/devicemanagement/tokensresponse
type MDMAppleDDMTokensResponse struct {
SyncTokens MDMAppleDDMDeclarationsToken
}
// MDMAppleDDMDeclarationsToken is dictionary describes the state of declarations on the server.
//
// https://developer.apple.com/documentation/devicemanagement/synchronizationtokens
type MDMAppleDDMDeclarationsToken struct {
DeclarationsToken string `db:"token"`
// Timestamp must JSON marshal to format YYYY-mm-ddTHH:MM:SSZ
Timestamp time.Time `db:"latest_created_timestamp"`
}
// MDMAppleDDMDeclarationItemsResponse is the response from the DDM declaration items endpoint.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
type MDMAppleDDMDeclarationItemsResponse struct {
Declarations MDMAppleDDMManifestItems
DeclarationsToken string
}
// MDMAppleDDMManifestItems is a dictionary that contains the lists of declarations available on the
// server.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse/manifestdeclarationitems
type MDMAppleDDMManifestItems struct {
Activations []MDMAppleDDMManifest
Assets []MDMAppleDDMManifest
Configurations []MDMAppleDDMManifest
Management []MDMAppleDDMManifest
}
// MDMAppleDDMManifest is a dictionary that describes a declaration.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse/manifestdeclarationitems
type MDMAppleDDMManifest struct {
Identifier string
ServerToken string
}
// MDMAppleDDMDeclarationItem represents a declaration item in the datastore. It is used to
// construct the DDM `declaration-items` endpoint response.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
type MDMAppleDDMDeclarationItem struct {
DeclarationUUID string `db:"declaration_uuid"`
Identifier string `db:"identifier"`
ServerToken string `db:"token"`
Status *string `db:"status"`
OperationType *string `db:"operation_type"`
UploadedAt time.Time `db:"uploaded_at"`
}
// MDMAppleDDMDeclarationResponse represents a declaration in the datastore. It is used for the DDM
// `declaration/.../...` enpoint response.
//
// https://developer.apple.com/documentation/devicemanagement/declarationresponse
type MDMAppleDDMDeclarationResponse struct {
Identifier string `db:"identifier"`
Type string `db:"type"`
Payload json.RawMessage `db:"payload"`
ServerToken string `db:"server_token"`
}
// MDMAppleDDMStatusReport represents a report of the device's current state.
//
// https://developer.apple.com/documentation/devicemanagement/statusreport
type MDMAppleDDMStatusReport struct {
StatusItems MDMAppleDDMStatusItems `json:"StatusItems"`
Errors []MDMAppleDDMErrors `json:"Errors"`
}
// MDMAppleDDMStatusItems are the status items for a report.
//
// https://developer.apple.com/documentation/devicemanagement/statusreport/statusitems
type MDMAppleDDMStatusItems struct {
Management MDMAppleDDMStatusManagement `json:"management"`
}
// MDMAppleDDMStatusManagement represents status report of the client's
// processed declarations.
//
// https://developer.apple.com/documentation/devicemanagement/statusmanagementdeclarations
type MDMAppleDDMStatusManagement struct {
Declarations MDMAppleDDMStatusDeclarations `json:"declarations"`
}
// MDMAppleDDMStatusDeclarations represents a collection of the client's
// processed declarations.
//
// https://developer.apple.com/documentation/devicemanagement/statusmanagementdeclarationsdeclarationsobject
type MDMAppleDDMStatusDeclarations struct {
// Activations is an array of declarations that represent the client's
// processed activation types.
Activations []MDMAppleDDMStatusDeclaration `json:"activations"`
// Configurations is an array of declarations that represent the
// client's processed configuration types.
Configurations []MDMAppleDDMStatusDeclaration `json:"configurations"`
// Assets is an array of declarations that represent the client's
// processed assets.
Assets []MDMAppleDDMStatusDeclaration `json:"assets"`
// Management is an array of declarations that represent the client's
// processed declaration types.
Management []MDMAppleDDMStatusDeclaration `json:"management"`
}
type MDMAppleDeclarationValidity string
const (
MDMAppleDeclarationValid MDMAppleDeclarationValidity = "valid"
MDMAppleDeclarationInvalid MDMAppleDeclarationValidity = "invalid"
MDMAppleDeclarationUnknown MDMAppleDeclarationValidity = "unknown"
)
// MDMAppleDDMStatusDeclaration represents a processed declaration for the client.
//
// https://developer.apple.com/documentation/devicemanagement/statusmanagementdeclarationsdeclarationobject
type MDMAppleDDMStatusDeclaration struct {
// Active signals if the declaration is active on the device.
Active bool `json:"active"`
// Identifier is the identifier of the declaration this status report refers to.
Identifier string `json:"identifier"`
// Valid defines the validity of the declaration. If it's invalid, the
// reasons property contains more details.
Valid MDMAppleDeclarationValidity `json:"valid"`
// ServerToken of the declaration this status report refers to.
ServerToken string `json:"server-token"`
// Reasons are the details of any client errors.
Reasons []MDMAppleDDMStatusErrorReason `json:"reasons,omitempty"`
}
// A status report's error that contains the status item and the reasons for
// the error.
//
// https://developer.apple.com/documentation/devicemanagement/statusreport/error
type MDMAppleDDMErrors struct {
// StatusItem is the status item that this error pertains to.
StatusItem string `json:"StatusItem"`
// Reasons is an array of reasons for the error.
Reasons []MDMAppleDDMStatusErrorReason `json:"Reasons"`
}
// A status report that contains details about an error.
//
// https://developer.apple.com/documentation/devicemanagement/statusreason
type MDMAppleDDMStatusErrorReason struct {
// Code is the error code for this error.
Code string `json:"Code"`
// Description is a short error description.
Description string `json:"Description"`
// Details is a dictionary that contains further details about this
// error.
Details map[string]any `json:"Details"`
}
// MDMAppleDDMActivationPayload represents the payload of an activation declaration.
//
// https://developer.apple.com/documentation/devicemanagement/activationsimple
type MDMAppleDDMActivationPayload struct {
Predicate string `json:"Predicate"`
StandardConfigurations []string `json:"StandardConfigurations"`
}
// MDMAppleDDMActivation represents the declaration of an activation. It combines the base
// declaation with the activation payload.
//
// https://developer.apple.com/documentation/devicemanagement/declarationbase
// https://developer.apple.com/documentation/devicemanagement/activationsimple
type MDMAppleDDMActivation struct {
Identifier string `json:"Identifier"`
Payload MDMAppleDDMActivationPayload `json:"Payload"`
ServerToken string `json:"ServerToken"`
Type string `json:"Type"` // "com.apple.activation.simple"
}
const BootstrapPackageSignedURLExpiry = 6 * time.Hour
// MDMBootstrapPackageStore is the interface to store and retrieve bootstrap
// package files. Fleet supports storing to the database and to an S3 bucket.
type MDMBootstrapPackageStore interface {
Get(ctx context.Context, packageID string) (io.ReadCloser, int64, error)
Put(ctx context.Context, packageID string, content io.ReadSeeker) error
Exists(ctx context.Context, packageID string) (bool, error)
Cleanup(ctx context.Context, usedPackageIDs []string, removeCreatedBefore time.Time) (int, error)
Sign(ctx context.Context, fileID string, expiresIn time.Duration) (string, error)
}
// MDMAppleMachineInfo is a [device's information][1] sent as part of an MDM enrollment profile request
//
// [1]: https://developer.apple.com/documentation/devicemanagement/machineinfo
type MDMAppleMachineInfo struct {
IMEI string `plist:"IMEI,omitempty"`
Language string `plist:"LANGUAGE,omitempty"`
MDMCanRequestSoftwareUpdate bool `plist:"MDM_CAN_REQUEST_SOFTWARE_UPDATE"`
MEID string `plist:"MEID,omitempty"`
OSVersion string `plist:"OS_VERSION"`
PairingToken string `plist:"PAIRING_TOKEN,omitempty"`
Product string `plist:"PRODUCT"`
Serial string `plist:"SERIAL"`
SoftwareUpdateDeviceID string `plist:"SOFTWARE_UPDATE_DEVICE_ID,omitempty"`
SupplementalBuildVersion string `plist:"SUPPLEMENTAL_BUILD_VERSION,omitempty"`
SupplementalOSVersionExtra string `plist:"SUPPLEMENTAL_OS_VERSION_EXTRA,omitempty"`
UDID string `plist:"UDID"`
Version string `plist:"VERSION"`
}
// macProductRe matches a macOS model identifier such as "MacBookPro18,3", capturing the
// alphabetic family prefix (group 1) and the numeric major version (group 2).
var macProductRe = regexp.MustCompile(`^([A-Za-z]+)(\d+),\d+$`)
// appleSiliconMajorThreshold maps each traditional Mac product family to the first major
// version number that corresponds to an Apple Silicon model. Any major version equal to or
// greater than the threshold is Apple Silicon; lower versions are x86.
var appleSiliconMajorThreshold = map[string]int{
// MacBookAir10,1 was the first Apple Silicon MacBook Air (M1, Late 2020).
"MacBookAir": 10,
// MacBookPro17,1 was the first Apple Silicon MacBook Pro (M1, Late 2020).
"MacBookPro": 17,
// Macmini9,1 was the first Apple Silicon Mac mini (M1, Late 2020).
"Macmini": 9,
// iMac21,1 was the first Apple Silicon iMac (M1, Early 2021).
"iMac": 21,
}
// IsMacAppleSilicon determines whether the device is an Apple Silicon Mac. If the model identifier
// starts with iPhone, iPod, or iPad, it returns false with no error; however, other non-Mac Apple
// devices like AppleTV will return an error.
func IsMacAppleSilicon(modelIdentifier string) (bool, error) {
if strings.HasPrefix(modelIdentifier, "iPhone") ||
strings.HasPrefix(modelIdentifier, "iPod") ||
strings.HasPrefix(modelIdentifier, "iPad") {
// If the model identifier starts with iPhone, iPod, or iPad, we'll return false with no
// error; however, other non-Mac Apple devices like AppleTV will return an error
return false, nil
}
matches := macProductRe.FindStringSubmatch(modelIdentifier)
if matches == nil {
return false, fmt.Errorf("unrecognized product identifier format: %q", modelIdentifier)
}
family := matches[1]
major, _ := strconv.Atoi(matches[2])
// Model identifiers starting with "Mac" immediately followed by a digit (e.g. "Mac13,1")
// represent the unified naming scheme Apple adopted for Apple Silicon products such as the
// Mac Studio and the M2/M3/M4-era Mac Pro. All such identifiers are Apple Silicon.
if family == "Mac" {
return true, nil
}
// MacBook (no suffix), iMacPro, and MacPro were all discontinued before Apple Silicon
// was introduced; every model in these families is x86.
switch family {
case "MacBook", "iMacPro", "MacPro":
return false, nil
}
threshold, ok := appleSiliconMajorThreshold[family]
if !ok {
return false, fmt.Errorf("unrecognized Mac product family in identifier: %q", modelIdentifier)
}
return major >= threshold, nil
}
// MDMAppleAccountDrivenUserEnrollDeviceInfo is a more minimal version of DeviceInfo sent on Account
// Driven User Enrollment requests[1] that only describes the base product attempting enrollment.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/implementing-the-simple-authentication-user-enrollment-flow#Attempt-the-first-enrollment
type MDMAppleAccountDrivenUserEnrollDeviceInfo struct {
Version string `plist:"VERSION"`
Product string `plist:"PRODUCT"`
Language string `plist:"LANGUAGE,omitempty"`
// The following keys are not described in the documentation above but have been observed in practice
OSVersion string `plist:"OS_VERSION,omitempty"`
SoftwareUpdateDeviceID string `plist:"SOFTWARE_UPDATE_DEVICE_ID,omitempty"`
SupplementalBuildVersion string `plist:"SUPPLEMENTAL_BUILD_VERSION,omitempty"`
}
// MDMAppleSoftwareUpdateRequiredCode is the [code][1] specified by Apple to indicate that the device
// needs to perform a software update before enrollment and setup can proceed.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired
const MDMAppleSoftwareUpdateRequiredCode = "com.apple.softwareupdate.required"
// MDMAppleSoftwareUpdateRequiredDetails is the [details][1] specified by Apple for the
// required software update.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired/details
type MDMAppleSoftwareUpdateRequiredDetails struct {
OSVersion string `json:"OSVersion"`
BuildVersion string `json:"BuildVersion"`
}
// MDMAppleSoftwareUpdateRequired is the [error response][1] specified by Apple to indicate that the device
// needs to perform a software update before enrollment and setup can proceed.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired
type MDMAppleSoftwareUpdateRequired struct {
Code string `json:"code"` // "com.apple.softwareupdate.required"
Details MDMAppleSoftwareUpdateRequiredDetails `json:"details"`
}
func NewMDMAppleSoftwareUpdateRequired(asset MDMAppleSoftwareUpdateAsset) *MDMAppleSoftwareUpdateRequired {
return &MDMAppleSoftwareUpdateRequired{
Code: MDMAppleSoftwareUpdateRequiredCode,
Details: MDMAppleSoftwareUpdateRequiredDetails{OSVersion: asset.ProductVersion, BuildVersion: asset.Build},
}
}
type MDMAppleSoftwareUpdateAsset struct {
ProductVersion string `json:"ProductVersion"`
Build string `json:"Build"`
}
type MDMManagedCertificate struct {
ProfileUUID string `db:"profile_uuid"`
HostUUID string `db:"host_uuid"`
ChallengeRetrievedAt *time.Time `db:"challenge_retrieved_at"`
NotValidBefore *time.Time `db:"not_valid_before"`
NotValidAfter *time.Time `db:"not_valid_after"`
Type CAConfigAssetType `db:"type"`
CAName string `db:"ca_name"`
Serial *string `db:"serial"`
}
func (m MDMManagedCertificate) Equal(other MDMManagedCertificate) bool {
challengeEqual := m.ChallengeRetrievedAt == nil && other.ChallengeRetrievedAt == nil ||
m.ChallengeRetrievedAt != nil && other.ChallengeRetrievedAt != nil && m.ChallengeRetrievedAt.Equal(*other.ChallengeRetrievedAt)
validBeforeEqual := m.NotValidBefore == nil && other.NotValidBefore == nil ||
m.NotValidBefore != nil && other.NotValidBefore != nil && m.NotValidBefore.Equal(*other.NotValidBefore)
validAfterEqual := m.NotValidAfter == nil && other.NotValidAfter == nil ||
m.NotValidAfter != nil && other.NotValidAfter != nil && m.NotValidAfter.Equal(*other.NotValidAfter)
serialEqual := m.Serial == nil && other.Serial == nil ||
m.Serial != nil && other.Serial != nil && *m.Serial == *other.Serial
return m.ProfileUUID == other.ProfileUUID &&
m.HostUUID == other.HostUUID &&
challengeEqual &&
validBeforeEqual &&
validAfterEqual &&
m.Type == other.Type &&
m.CAName == other.CAName &&
serialEqual
}
// MDMAppleEnrolledDeviceInfo represents the information of a device enrolled
// in Apple MDM. Used by the MDM flow to re-create an iDevice that has been
// deleted from the hosts table but is still MDM-enrolled.
type MDMAppleEnrolledDeviceInfo struct {
ID string `db:"id"`
SerialNumber string `db:"serial_number"`
Authenticate string `db:"authenticate"`
Platform string `db:"platform"`
EnrollTeamID *uint `db:"enroll_team_id"`
}
type AppleMDMVPPInstaller interface {
// GetVPPTokenIfCanInstallVPPApps returns the host team's VPP token if the host can be a target for VPP apps
GetVPPTokenIfCanInstallVPPApps(ctx context.Context, appleDevice bool, host *Host) (string, error)
// InstallVPPAppPostValidation installs a VPP app, assuming that GetVPPTokenIfCanInstallVPPApps has passed and provided a VPP token
// Returns the command UUID of the installation.
InstallVPPAppPostValidation(ctx context.Context, host *Host, vppApp *VPPApp, token string, opts HostSoftwareInstallOptions) (string, error)
}
const (
DeviceLocationCmdName = "DeviceLocation"
EnableLostModeCmdName = "EnableLostMode"
DisableLostModeCmdName = "DisableLostMode"
SetRecoveryLockCmdName = "SetRecoveryLock"
)
type HostLocationData struct {
HostID uint `db:"host_id"`
Latitude float64 `db:"latitude"`
Longitude float64 `db:"longitude"`
}
// HostRecoveryLockPassword represents a recovery lock password for a host.
type HostRecoveryLockPassword struct {
Password string
UpdatedAt time.Time
AutoRotateAt *time.Time // When auto-rotation is scheduled (1 hour after password is viewed)
}
// HostRecoveryLockPasswordPayload contains the data needed to store a recovery lock password.
type HostRecoveryLockPasswordPayload struct {
HostUUID string
Password string
}
// HostRecoveryLockRotationStatus represents the current rotation state for a host's recovery lock.
type HostRecoveryLockRotationStatus struct {
HostUUID string // Host UUID
HasPassword bool // encrypted_password is not null and deleted=0
Status *string // current status (verified, failed, pending, NULL)
OperationType string // install or remove
HasPendingRotation bool // pending_encrypted_password is not null
PendingErrorMessage *string // error from failed rotation
}
// HostAutoRotationInfo contains the minimal host data needed for auto-rotation activity logging.
type HostAutoRotationInfo struct {
HostUUID string `db:"host_uuid"`
HostID uint `db:"host_id"`
DisplayName string `db:"display_name"`
}