mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 06:48:54 +00:00
This PR addresses the concern of potentially being able to release a device before any profile is sent, and the check thinking there is no pending. It addresses both the release worker, but also the orbit setup experience endpoint, even though that is less likely. _Checked the query against my host on dogfood where it took 0.1 seconds, with the single host._ fixes: #31143 _I also ended up putting my main test in a new file `integration_mdm_release_worker_test.go` and decided not to do fancy setup, as there is only one test so no recurring things, and based on our retro talk also moved the setup experience related tests inside of `integration_mdm_dep_test.go` into their separate file `integration_mdm_setup_experience_test.go`_ # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) ## Testing - [x] Added/updated automated tests - [ ] QA'd all new/changed functionality manually (No, since this one is hard to reproduce, but instead wrote an integration test before doing the change to verify the behaviour.)
565 lines
19 KiB
Go
565 lines
19 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/fleetdbase"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/apple/appmanifest"
|
|
kitlog "github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Name of the Apple MDM job as registered in the worker. Note that although it
|
|
// is a single job, it can process a number of different-but-related tasks,
|
|
// identified by the Task field in the job's payload.
|
|
const appleMDMJobName = "apple_mdm"
|
|
|
|
type AppleMDMTask string
|
|
|
|
// List of supported tasks.
|
|
const (
|
|
AppleMDMPostDEPEnrollmentTask AppleMDMTask = "post_dep_enrollment"
|
|
AppleMDMPostManualEnrollmentTask AppleMDMTask = "post_manual_enrollment"
|
|
// PostDEPReleaseDevice is not enqueued anymore for macOS but remains for
|
|
// backward compatibility (processing existing jobs after a fleet upgrade)
|
|
// and for ios/ipados. Macs are now released via the swift dialog UI of the
|
|
// setup experience flow.
|
|
AppleMDMPostDEPReleaseDeviceTask AppleMDMTask = "post_dep_release_device"
|
|
)
|
|
|
|
// AppleMDM is the job processor for the apple_mdm job.
|
|
type AppleMDM struct {
|
|
Datastore fleet.Datastore
|
|
Log kitlog.Logger
|
|
Commander *apple_mdm.MDMAppleCommander
|
|
BootstrapPackageStore fleet.MDMBootstrapPackageStore
|
|
}
|
|
|
|
// Name returns the name of the job.
|
|
func (a *AppleMDM) Name() string {
|
|
return appleMDMJobName
|
|
}
|
|
|
|
// appleMDMArgs is the payload for the Apple MDM job.
|
|
type appleMDMArgs struct {
|
|
Task AppleMDMTask `json:"task"`
|
|
HostUUID string `json:"host_uuid"`
|
|
TeamID *uint `json:"team_id,omitempty"`
|
|
// EnrollReference is the UUID of the MDM IdP account used to enroll the
|
|
// device. It is used to set the username and full name of the user
|
|
// associated with the device.
|
|
//
|
|
// FIXME: Rename this to IdPAccountUUID or something similar.
|
|
EnrollReference string `json:"enroll_reference,omitempty"`
|
|
EnrollmentCommands []string `json:"enrollment_commands,omitempty"`
|
|
Platform string `json:"platform,omitempty"`
|
|
UseWorkerDeviceRelease bool `json:"use_worker_device_release,omitempty"`
|
|
ReleaseDeviceAttempt int `json:"release_device_attempt,omitempty"` // number of attempts to release the device
|
|
ReleaseDeviceStartedAt *time.Time `json:"release_device_started_at,omitempty"` // time when the release device task first started
|
|
}
|
|
|
|
// Run executes the apple_mdm job.
|
|
func (a *AppleMDM) Run(ctx context.Context, argsJSON json.RawMessage) error {
|
|
appCfg, err := a.Datastore.AppConfig(ctx)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "retrieving app config")
|
|
}
|
|
if !appCfg.MDM.EnabledAndConfigured || a.Commander == nil {
|
|
return nil
|
|
}
|
|
|
|
var args appleMDMArgs
|
|
if err := json.Unmarshal(argsJSON, &args); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "unmarshal args")
|
|
}
|
|
|
|
switch args.Task {
|
|
case AppleMDMPostDEPEnrollmentTask:
|
|
err := a.runPostDEPEnrollment(ctx, args)
|
|
return ctxerr.Wrap(ctx, err, "running post Apple DEP enrollment task")
|
|
|
|
case AppleMDMPostManualEnrollmentTask:
|
|
err := a.runPostManualEnrollment(ctx, args)
|
|
return ctxerr.Wrap(ctx, err, "running post Apple manual enrollment task")
|
|
|
|
case AppleMDMPostDEPReleaseDeviceTask:
|
|
err := a.runPostDEPReleaseDevice(ctx, args)
|
|
return ctxerr.Wrap(ctx, err, "running post Apple DEP release device task")
|
|
|
|
default:
|
|
return ctxerr.Errorf(ctx, "unknown task: %v", args.Task)
|
|
}
|
|
}
|
|
|
|
func isMacOS(platform string) bool {
|
|
// For backwards compatibility, we assume empty platform in job arguments is macOS.
|
|
return platform == "" ||
|
|
platform == "darwin"
|
|
}
|
|
|
|
func (a *AppleMDM) runPostManualEnrollment(ctx context.Context, args appleMDMArgs) error {
|
|
if isMacOS(args.Platform) {
|
|
if _, err := a.installFleetd(ctx, args.HostUUID); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "installing post-enrollment packages")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs) error {
|
|
var (
|
|
awaitCmdUUIDs []string
|
|
appCfg *fleet.AppConfig
|
|
team *fleet.Team
|
|
err error
|
|
)
|
|
|
|
if isMacOS(args.Platform) {
|
|
var manualAgentInstall bool
|
|
if args.TeamID == nil {
|
|
if appCfg, err = a.getAppConfig(ctx, appCfg); err != nil {
|
|
return err
|
|
}
|
|
manualAgentInstall = appCfg.MDM.MacOSSetup.ManualAgentInstall.Value
|
|
} else {
|
|
if team, err = a.getTeamConfig(ctx, team, *args.TeamID); err != nil {
|
|
return err
|
|
}
|
|
manualAgentInstall = team.Config.MDM.MacOSSetup.ManualAgentInstall.Value
|
|
}
|
|
|
|
if !manualAgentInstall {
|
|
fleetdCmdUUID, err := a.installFleetd(ctx, args.HostUUID)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "installing post-enrollment packages")
|
|
}
|
|
awaitCmdUUIDs = append(awaitCmdUUIDs, fleetdCmdUUID)
|
|
}
|
|
|
|
bootstrapCmdUUID, err := a.installBootstrapPackage(ctx, args.HostUUID, args.TeamID)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "installing post-enrollment packages")
|
|
}
|
|
if bootstrapCmdUUID != "" {
|
|
awaitCmdUUIDs = append(awaitCmdUUIDs, bootstrapCmdUUID)
|
|
}
|
|
}
|
|
|
|
if ref := args.EnrollReference; ref != "" {
|
|
a.Log.Log("info", "got an enroll_reference", "host_uuid", args.HostUUID, "ref", ref)
|
|
if appCfg, err = a.getAppConfig(ctx, appCfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
acct, err := a.Datastore.GetMDMIdPAccountByUUID(ctx, ref)
|
|
if err != nil {
|
|
return ctxerr.Wrapf(ctx, err, "getting idp account details for enroll reference %s", ref)
|
|
}
|
|
|
|
ssoEnabled := appCfg.MDM.MacOSSetup.EnableEndUserAuthentication
|
|
if args.TeamID != nil {
|
|
if team, err = a.getTeamConfig(ctx, team, *args.TeamID); err != nil {
|
|
return err
|
|
}
|
|
ssoEnabled = team.Config.MDM.MacOSSetup.EnableEndUserAuthentication
|
|
}
|
|
|
|
if ssoEnabled {
|
|
fullName, err := a.getIdPDisplayName(ctx, acct, args)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "getting idp account display name")
|
|
}
|
|
a.Log.Log("info", "setting username and fullname", "host_uuid", args.HostUUID)
|
|
cmdUUID := uuid.New().String()
|
|
if err := a.Commander.AccountConfiguration(
|
|
ctx,
|
|
[]string{args.HostUUID},
|
|
cmdUUID,
|
|
fullName,
|
|
acct.Username,
|
|
); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "sending AccountConfiguration command")
|
|
}
|
|
awaitCmdUUIDs = append(awaitCmdUUIDs, cmdUUID)
|
|
}
|
|
}
|
|
|
|
// proceed to release the device if it is not a macos, as those are released
|
|
// via the setup experience flow, or if we were told to use the worker based
|
|
// release.
|
|
if !isMacOS(args.Platform) || args.UseWorkerDeviceRelease {
|
|
var manualRelease bool
|
|
if args.TeamID == nil {
|
|
if appCfg, err = a.getAppConfig(ctx, appCfg); err != nil {
|
|
return err
|
|
}
|
|
manualRelease = appCfg.MDM.MacOSSetup.EnableReleaseDeviceManually.Value
|
|
} else {
|
|
if team, err = a.getTeamConfig(ctx, team, *args.TeamID); err != nil {
|
|
return err
|
|
}
|
|
manualRelease = team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value
|
|
}
|
|
|
|
if !manualRelease {
|
|
// send all command uuids for the commands sent here during post-DEP
|
|
// enrollment and enqueue a job to look for the status of those commands to
|
|
// be final and same for MDM profiles of that host; it means the DEP
|
|
// enrollment process is done and the device can be released.
|
|
if err := QueueAppleMDMJob(ctx, a.Datastore, a.Log, AppleMDMPostDEPReleaseDeviceTask,
|
|
args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, false, awaitCmdUUIDs...); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "queue Apple Post-DEP release device job")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getTeamConfig gets team config from DB if not provided.
|
|
func (a *AppleMDM) getTeamConfig(ctx context.Context, team *fleet.Team, teamID uint) (*fleet.Team, error) {
|
|
if team == nil {
|
|
var err error
|
|
team, err = a.Datastore.Team(ctx, teamID)
|
|
if err != nil {
|
|
return nil, ctxerr.Wrap(ctx, err, "fetch team to send AccountConfiguration")
|
|
}
|
|
}
|
|
return team, nil
|
|
}
|
|
|
|
// getAppConfig gets app config from DB if not provided.
|
|
func (a *AppleMDM) getAppConfig(ctx context.Context, appConfig *fleet.AppConfig) (*fleet.AppConfig, error) {
|
|
if appConfig == nil {
|
|
var err error
|
|
appConfig, err = a.Datastore.AppConfig(ctx)
|
|
if err != nil {
|
|
return nil, ctxerr.Wrap(ctx, err, "getting app config")
|
|
}
|
|
}
|
|
return appConfig, nil
|
|
}
|
|
|
|
func (a *AppleMDM) getIdPDisplayName(ctx context.Context, acct *fleet.MDMIdPAccount, args appleMDMArgs) (string, error) {
|
|
if acct.Fullname != "" && !strings.Contains(acct.Fullname, "@") {
|
|
return acct.Fullname, nil
|
|
}
|
|
|
|
// If full name is empty or appears to be an email, see if it exists via SCIM integration
|
|
scimUser, err := a.Datastore.ScimUserByUserNameOrEmail(ctx, acct.Username, acct.Email)
|
|
switch {
|
|
case err != nil && !fleet.IsNotFound(err):
|
|
return "", ctxerr.Wrap(ctx, err, "getting scim user details for enroll reference %s and host_uuid %s", acct.UUID, args.HostUUID)
|
|
case scimUser == nil:
|
|
return acct.Fullname, nil
|
|
}
|
|
if scimUser.DisplayName() == "" {
|
|
return acct.Fullname, nil
|
|
}
|
|
return scimUser.DisplayName(), nil
|
|
}
|
|
|
|
// This job is used only for iDevices or for macos devices that don't use any
|
|
// setup experience items (software installs, script exec) - see
|
|
// appleMDMArgs.UseWorkerDeviceRelease. Otherwise releasing devices is now done
|
|
// via the orbit endpoint /setup_experience/status that is polled by a swift
|
|
// dialog UI window during the setup process, and automatically releases the
|
|
// device once all pending setup tasks are done.
|
|
func (a *AppleMDM) runPostDEPReleaseDevice(ctx context.Context, args appleMDMArgs) error {
|
|
// Edge cases:
|
|
// - if the device goes offline for a long time, should we go ahead and
|
|
// release after a while?
|
|
// - if some commands/profiles failed (a final state), should we go ahead
|
|
// and release?
|
|
// - if the device keeps moving team, or profiles keep being added/removed
|
|
// from its team, it's possible that its profiles will never settle and
|
|
// always have pending statuses. Same as going offline, should we release
|
|
// after a while?
|
|
//
|
|
// We opted "yes" to all those, and we want to release after a few minutes,
|
|
// not hours, so we'll allow only a couple retries.
|
|
|
|
const (
|
|
maxWaitTime = 15 * time.Minute
|
|
minAttempts = 10
|
|
maxAttempts = 30
|
|
nextAttemptMinDelay = 30 * time.Second
|
|
)
|
|
|
|
args.ReleaseDeviceAttempt++
|
|
if args.ReleaseDeviceStartedAt == nil {
|
|
now := time.Now().UTC()
|
|
args.ReleaseDeviceStartedAt = &now
|
|
}
|
|
|
|
level.Debug(a.Log).Log(
|
|
"task", "runPostDEPReleaseDevice",
|
|
"msg", fmt.Sprintf("awaiting commands %v and profiles to settle for host %s", args.EnrollmentCommands, args.HostUUID),
|
|
"attempt", args.ReleaseDeviceAttempt,
|
|
"started_at", args.ReleaseDeviceStartedAt.Format(time.RFC3339),
|
|
)
|
|
|
|
// if we've reached the minimum number of attempts and the maximum time to
|
|
// wait, we release the device even if some commands or profiles are still
|
|
// pending. We also release in case it reached the maximum number of
|
|
// attempts, to prevent an issue with clock skew where the wait delay does
|
|
// not appear to be reached.
|
|
if (args.ReleaseDeviceAttempt >= minAttempts && time.Since(*args.ReleaseDeviceStartedAt) >= maxWaitTime) ||
|
|
(args.ReleaseDeviceAttempt >= maxAttempts) {
|
|
a.Log.Log("info", "releasing device after too many attempts or too long wait", "host_uuid", args.HostUUID, "attempts", args.ReleaseDeviceAttempt)
|
|
if err := a.Commander.DeviceConfigured(ctx, args.HostUUID, uuid.NewString()); err != nil {
|
|
return ctxerr.Wrapf(ctx, err, "failed to enqueue DeviceConfigured command after %d attempts", args.ReleaseDeviceAttempt)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
reenqueueTask := func() error {
|
|
// re-enqueue the same job, but now
|
|
// ReleaseDeviceAttempt/ReleaseDeviceStartedAt have been incremented/set,
|
|
// and run it not before a delay so it doesn't run again until the next
|
|
// worker cycle.
|
|
_, err := QueueJobWithDelay(ctx, a.Datastore, appleMDMJobName, args, nextAttemptMinDelay)
|
|
return err
|
|
}
|
|
|
|
for _, cmdUUID := range args.EnrollmentCommands {
|
|
if cmdUUID == "" {
|
|
continue
|
|
}
|
|
|
|
res, err := a.Datastore.GetMDMAppleCommandResults(ctx, cmdUUID)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "failed to get MDM command results")
|
|
}
|
|
|
|
var completed bool
|
|
for _, r := range res {
|
|
// succeeded or failed, it is done (final state)
|
|
if r.Status == fleet.MDMAppleStatusAcknowledged || r.Status == fleet.MDMAppleStatusError ||
|
|
r.Status == fleet.MDMAppleStatusCommandFormatError {
|
|
completed = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !completed {
|
|
// DEP enrollment commands are not done being delivered to that device,
|
|
// cannot release it now.
|
|
if err := reenqueueTask(); err != nil {
|
|
return fmt.Errorf("failed to re-enqueue task: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
level.Debug(a.Log).Log(
|
|
"task", "runPostDEPReleaseDevice",
|
|
"msg", fmt.Sprintf("command %s has completed", cmdUUID),
|
|
)
|
|
}
|
|
|
|
// all DEP-enrollment commands are done, check the host's profiles
|
|
profs, err := a.Datastore.GetHostMDMAppleProfiles(ctx, args.HostUUID)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "failed to get host MDM profiles")
|
|
}
|
|
for _, prof := range profs {
|
|
// NOTE: DDM profiles (declarations) are ignored because while a device is
|
|
// awaiting to be released, it cannot process a DDM session (at least
|
|
// that's what we noticed during testing).
|
|
if strings.HasPrefix(prof.ProfileUUID, fleet.MDMAppleDeclarationUUIDPrefix) {
|
|
continue
|
|
}
|
|
|
|
// NOTE: user-scoped profiles are ignored because they are not sent by Fleet
|
|
// until after the device is released - there is no user-channel available
|
|
// on the host until after the release, and after the user actually created
|
|
// the user account.
|
|
if prof.Scope == fleet.PayloadScopeUser {
|
|
continue
|
|
}
|
|
|
|
// if it has any pending profiles, then its profiles are not done being
|
|
// delivered (installed or removed).
|
|
if prof.Status == nil || *prof.Status == fleet.MDMDeliveryPending {
|
|
if err := reenqueueTask(); err != nil {
|
|
return fmt.Errorf("failed to re-enqueue task: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
level.Debug(a.Log).Log(
|
|
"task", "runPostDEPReleaseDevice",
|
|
"msg", fmt.Sprintf("profile %s has been deployed", prof.Identifier),
|
|
)
|
|
}
|
|
|
|
profilesMissingInstallation, err := a.Datastore.ListMDMAppleProfilesToInstall(ctx, args.HostUUID) // Get profiles that are missing to be installed on this host
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "failed to list profiles missing installation")
|
|
}
|
|
profilesMissingInstallation = fleet.FilterOutUserScopedProfiles(profilesMissingInstallation)
|
|
if args.Platform != "darwin" {
|
|
profilesMissingInstallation = fleet.FilterMacOSOnlyProfilesFromIOSIPadOS(profilesMissingInstallation)
|
|
}
|
|
|
|
if len(profilesMissingInstallation) > 0 {
|
|
level.Info(a.Log).Log("msg", "re-enqueuing due to profiles missing installation", "host_uuid", args.HostUUID)
|
|
// requeue the task if some profiles are still missing.
|
|
if err := reenqueueTask(); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "failed to re-enqueue task")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// release the device
|
|
a.Log.Log("info", "releasing device, all DEP enrollment commands and profiles have completed", "host_uuid", args.HostUUID)
|
|
if err := a.Commander.DeviceConfigured(ctx, args.HostUUID, uuid.NewString()); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "failed to enqueue DeviceConfigured command")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *AppleMDM) installFleetd(ctx context.Context, hostUUID string) (string, error) {
|
|
manifestURL := fleetdbase.GetPKGManifestURL()
|
|
cmdUUID := uuid.New().String()
|
|
if err := a.Commander.InstallEnterpriseApplication(ctx, []string{hostUUID}, cmdUUID, manifestURL); err != nil {
|
|
return "", err
|
|
}
|
|
a.Log.Log("info", "sent command to install fleetd", "host_uuid", hostUUID)
|
|
return cmdUUID, nil
|
|
}
|
|
|
|
func (a *AppleMDM) installBootstrapPackage(ctx context.Context, hostUUID string, teamID *uint) (string, error) {
|
|
// GetMDMAppleBootstrapPackageMeta expects team id 0 for no team
|
|
var tmID uint
|
|
if teamID != nil {
|
|
tmID = *teamID
|
|
}
|
|
meta, err := a.Datastore.GetMDMAppleBootstrapPackageMeta(ctx, tmID)
|
|
if err != nil {
|
|
var nfe fleet.NotFoundError
|
|
if errors.As(err, &nfe) {
|
|
a.Log.Log("info", "unable to find a bootstrap package for DEP enrolled device, skipping installation", "host_uuid", hostUUID)
|
|
return "", nil
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
// Get CloudFront CDN signed URL if configured
|
|
url := a.getSignedURL(ctx, meta)
|
|
|
|
if url == "" {
|
|
appCfg, err := a.Datastore.AppConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
url, err = meta.URL(appCfg.MDMUrl())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
manifest := appmanifest.NewFromSha(meta.Sha256, url)
|
|
cmdUUID := uuid.New().String()
|
|
err = a.Commander.InstallEnterpriseApplicationWithEmbeddedManifest(ctx, []string{hostUUID}, cmdUUID, manifest)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = a.Datastore.RecordHostBootstrapPackage(ctx, cmdUUID, hostUUID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
a.Log.Log("info", "sent command to install bootstrap package", "host_uuid", hostUUID)
|
|
return cmdUUID, nil
|
|
}
|
|
|
|
func (a *AppleMDM) getSignedURL(ctx context.Context, meta *fleet.MDMAppleBootstrapPackage) string {
|
|
var url string
|
|
if a.BootstrapPackageStore != nil {
|
|
pkgID := hex.EncodeToString(meta.Sha256)
|
|
signedURL, err := a.BootstrapPackageStore.Sign(ctx, pkgID)
|
|
switch {
|
|
case errors.Is(err, fleet.ErrNotConfigured):
|
|
// no CDN configured, fall back to the MDM URL
|
|
case err != nil:
|
|
// log the error but continue with the MDM URL
|
|
level.Error(a.Log).Log("msg", "failed to sign bootstrap package URL", "err", err)
|
|
default:
|
|
exists, err := a.BootstrapPackageStore.Exists(ctx, pkgID)
|
|
switch {
|
|
case err != nil:
|
|
// log the error but continue with the MDM URL
|
|
level.Error(a.Log).Log("msg", "failed to check if bootstrap package exists", "err", err)
|
|
case !exists:
|
|
// log the error but continue with the MDM URL
|
|
level.Error(a.Log).Log("msg", "bootstrap package does not exist in package store", "pkg_id", pkgID)
|
|
default:
|
|
url = signedURL
|
|
}
|
|
}
|
|
}
|
|
return url
|
|
}
|
|
|
|
// QueueAppleMDMJob queues a apple_mdm job for one of the supported tasks, to
|
|
// be processed asynchronously via the worker.
|
|
func QueueAppleMDMJob(
|
|
ctx context.Context,
|
|
ds fleet.Datastore,
|
|
logger kitlog.Logger,
|
|
task AppleMDMTask,
|
|
hostUUID string,
|
|
platform string,
|
|
teamID *uint,
|
|
enrollReference string,
|
|
useWorkerDeviceRelease bool,
|
|
enrollmentCommandUUIDs ...string,
|
|
) error {
|
|
attrs := []interface{}{
|
|
"enabled", "true",
|
|
appleMDMJobName, task,
|
|
"host_uuid", hostUUID,
|
|
"platform", platform,
|
|
"with_enroll_reference", enrollReference != "",
|
|
}
|
|
if teamID != nil {
|
|
attrs = append(attrs, "team_id", *teamID)
|
|
}
|
|
if len(enrollmentCommandUUIDs) > 0 {
|
|
attrs = append(attrs, "enrollment_commands", fmt.Sprintf("%v", enrollmentCommandUUIDs))
|
|
}
|
|
level.Info(logger).Log(attrs...)
|
|
|
|
args := &appleMDMArgs{
|
|
Task: task,
|
|
HostUUID: hostUUID,
|
|
TeamID: teamID,
|
|
EnrollReference: enrollReference,
|
|
EnrollmentCommands: enrollmentCommandUUIDs,
|
|
Platform: platform,
|
|
UseWorkerDeviceRelease: useWorkerDeviceRelease,
|
|
}
|
|
|
|
// the release device task is always added with a delay
|
|
var delay time.Duration
|
|
if task == AppleMDMPostDEPReleaseDeviceTask {
|
|
delay = 30 * time.Second
|
|
}
|
|
job, err := QueueJobWithDelay(ctx, ds, appleMDMJobName, args, delay)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "queueing job")
|
|
}
|
|
level.Debug(logger).Log("job_id", job.ID)
|
|
return nil
|
|
}
|