2023-05-15 18:06:09 +00:00
|
|
|
package worker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"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"
|
|
|
|
kitlog "github.com/go-kit/kit/log"
|
|
|
|
"github.com/go-kit/kit/log/level"
|
|
|
|
"github.com/micromdm/nanodep/godep"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Name of the macos setup assistant job as registered in the worker. Note that
|
|
|
|
// although it is a single job, it processes a number of different-but-related
|
|
|
|
// tasks, identified by the Task field in the job's payload.
|
|
|
|
const macosSetupAssistantJobName = "macos_setup_assistant" //nolint: gosec // somehow it detects this as credentials
|
|
|
|
|
|
|
|
type MacosSetupAssistantTask string
|
|
|
|
|
|
|
|
// List of supported tasks.
|
|
|
|
const (
|
|
|
|
MacosSetupAssistantProfileChanged MacosSetupAssistantTask = "profile_changed"
|
|
|
|
MacosSetupAssistantProfileDeleted MacosSetupAssistantTask = "profile_deleted"
|
|
|
|
MacosSetupAssistantTeamDeleted MacosSetupAssistantTask = "team_deleted"
|
|
|
|
MacosSetupAssistantHostsTransferred MacosSetupAssistantTask = "hosts_transferred"
|
|
|
|
MacosSetupAssistantUpdateAllProfiles MacosSetupAssistantTask = "update_all_profiles"
|
2023-05-17 13:06:14 +00:00
|
|
|
MacosSetupAssistantUpdateProfile MacosSetupAssistantTask = "update_profile"
|
2023-05-15 18:06:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// MacosSetupAssistant is the job processor for the macos_setup_assistant job.
|
|
|
|
type MacosSetupAssistant struct {
|
|
|
|
Datastore fleet.Datastore
|
|
|
|
Log kitlog.Logger
|
|
|
|
DEPService *apple_mdm.DEPService
|
|
|
|
DEPClient *godep.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of the job.
|
|
|
|
func (m *MacosSetupAssistant) Name() string {
|
|
|
|
return macosSetupAssistantJobName
|
|
|
|
}
|
|
|
|
|
|
|
|
// macosSetupAssistantArgs is the payload for the macos setup assistant job.
|
|
|
|
type macosSetupAssistantArgs struct {
|
|
|
|
Task MacosSetupAssistantTask `json:"task"`
|
|
|
|
TeamID *uint `json:"team_id,omitempty"`
|
|
|
|
// Note that only DEP-enrolled hosts in Fleet MDM should be provided.
|
|
|
|
HostSerialNumbers []string `json:"host_serial_numbers,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run executes the macos_setup_assistant job.
|
|
|
|
func (m *MacosSetupAssistant) Run(ctx context.Context, argsJSON json.RawMessage) error {
|
|
|
|
// if DEPService is nil, then mdm is not enabled, so just return without
|
|
|
|
// error so we clean up any pending macos setup assistant jobs.
|
|
|
|
if m.DEPService == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var args macosSetupAssistantArgs
|
|
|
|
if err := json.Unmarshal(argsJSON, &args); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "unmarshal args")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch args.Task {
|
|
|
|
case MacosSetupAssistantProfileChanged:
|
|
|
|
return m.runProfileChanged(ctx, args)
|
|
|
|
case MacosSetupAssistantProfileDeleted:
|
|
|
|
return m.runProfileDeleted(ctx, args)
|
|
|
|
case MacosSetupAssistantTeamDeleted:
|
|
|
|
return m.runTeamDeleted(ctx, args)
|
|
|
|
case MacosSetupAssistantHostsTransferred:
|
|
|
|
return m.runHostsTransferred(ctx, args)
|
|
|
|
case MacosSetupAssistantUpdateAllProfiles:
|
|
|
|
return m.runUpdateAllProfiles(ctx, args)
|
2023-05-17 13:06:14 +00:00
|
|
|
case MacosSetupAssistantUpdateProfile:
|
|
|
|
return m.runUpdateProfile(ctx, args)
|
2023-05-15 18:06:09 +00:00
|
|
|
default:
|
|
|
|
return ctxerr.Errorf(ctx, "unknown task: %v", args.Task)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MacosSetupAssistant) runProfileChanged(ctx context.Context, args macosSetupAssistantArgs) error {
|
2023-05-17 13:06:14 +00:00
|
|
|
team, err := m.getTeamNoTeam(ctx, args.TeamID)
|
|
|
|
if err != nil {
|
|
|
|
if fleet.IsNotFound(err) {
|
|
|
|
// team doesn't exist anymore, nothing to do (another job was enqueued to
|
|
|
|
// take care of team deletion)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ctxerr.Wrap(ctx, err, "get team")
|
|
|
|
}
|
|
|
|
|
2023-05-15 18:06:09 +00:00
|
|
|
// re-generate and register the profile with Apple. Since the profile has been
|
|
|
|
// updated, then its profile UUID will have been cleared, so this single call
|
|
|
|
// will do both tasks.
|
2023-05-17 13:06:14 +00:00
|
|
|
profUUID, _, err := m.DEPService.EnsureCustomSetupAssistantIfExists(ctx, team)
|
2023-05-15 18:06:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "ensure custom setup assistant")
|
|
|
|
}
|
|
|
|
if profUUID == "" {
|
|
|
|
// the custom setup assistant profile may have been deleted since the job
|
|
|
|
// was enqueued, if so another job will take care of assigning the default
|
|
|
|
// profile to the hosts, nothing to do.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the team's mdm-enrolled hosts, assign the profile to all of that
|
|
|
|
// team's hosts serials.
|
|
|
|
serials, err := m.Datastore.ListMDMAppleDEPSerialsInTeam(ctx, args.TeamID)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "list mdm dep serials in team")
|
|
|
|
}
|
|
|
|
if len(serials) > 0 {
|
|
|
|
if _, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, serials...); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "assign profile")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MacosSetupAssistant) runProfileDeleted(ctx context.Context, args macosSetupAssistantArgs) error {
|
2023-05-17 13:06:14 +00:00
|
|
|
team, err := m.getTeamNoTeam(ctx, args.TeamID)
|
|
|
|
if err != nil {
|
|
|
|
if fleet.IsNotFound(err) {
|
|
|
|
// team doesn't exist anymore, nothing to do (another job was enqueued to
|
|
|
|
// take care of team deletion)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ctxerr.Wrap(ctx, err, "get team")
|
|
|
|
}
|
|
|
|
|
2023-05-15 18:06:09 +00:00
|
|
|
// get the team's setup assistant, to make sure it is still absent. If it is
|
|
|
|
// not, then it was re-created before this job ran, so nothing to do (another
|
|
|
|
// job will take care of assigning the profile to the hosts).
|
2023-05-17 13:06:14 +00:00
|
|
|
customProfUUID, _, err := m.DEPService.EnsureCustomSetupAssistantIfExists(ctx, team)
|
2023-05-15 18:06:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "ensure custom setup assistant")
|
|
|
|
}
|
|
|
|
if customProfUUID != "" {
|
|
|
|
// a custom setup assistant was re-created, so nothing to do.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// a custom setup assistant profile was deleted, so we get the profile uuid
|
|
|
|
// of the default profile and assign it to all of the team's hosts. No need
|
|
|
|
// to force a re-generate of the default profile, if it is already registered
|
|
|
|
// with Apple this is fine and we use that profile uuid.
|
2023-05-17 13:06:14 +00:00
|
|
|
profUUID, _, err := m.DEPService.EnsureDefaultSetupAssistant(ctx, team)
|
2023-05-15 18:06:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "ensure default setup assistant")
|
|
|
|
}
|
|
|
|
if profUUID == "" {
|
|
|
|
// this should not happen, return an error
|
|
|
|
return ctxerr.Errorf(ctx, "default setup assistant profile uuid is empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the team's mdm-enrolled hosts, assign the profile to all of that
|
|
|
|
// team's hosts serials.
|
|
|
|
serials, err := m.Datastore.ListMDMAppleDEPSerialsInTeam(ctx, args.TeamID)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "list mdm dep serials in team")
|
|
|
|
}
|
|
|
|
if len(serials) > 0 {
|
|
|
|
if _, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, serials...); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "assign profile")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MacosSetupAssistant) runTeamDeleted(ctx context.Context, args macosSetupAssistantArgs) error {
|
|
|
|
// team deletion is semantically equivalent to moving hosts to "no team"
|
|
|
|
args.TeamID = nil // should already be this way, but just to make sure
|
|
|
|
return m.runHostsTransferred(ctx, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MacosSetupAssistant) runHostsTransferred(ctx context.Context, args macosSetupAssistantArgs) error {
|
2023-05-17 13:06:14 +00:00
|
|
|
team, err := m.getTeamNoTeam(ctx, args.TeamID)
|
|
|
|
if err != nil {
|
|
|
|
if fleet.IsNotFound(err) {
|
|
|
|
// team doesn't exist anymore, nothing to do (another job was enqueued to
|
|
|
|
// take care of team deletion)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ctxerr.Wrap(ctx, err, "get team")
|
|
|
|
}
|
|
|
|
|
2023-05-15 18:06:09 +00:00
|
|
|
// get the new team's setup assistant if it exists.
|
2023-05-17 13:06:14 +00:00
|
|
|
profUUID, _, err := m.DEPService.EnsureCustomSetupAssistantIfExists(ctx, team)
|
2023-05-15 18:06:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "ensure custom setup assistant")
|
|
|
|
}
|
|
|
|
if profUUID == "" {
|
|
|
|
// get the default setup assistant.
|
2023-05-17 13:06:14 +00:00
|
|
|
defProfUUID, _, err := m.DEPService.EnsureDefaultSetupAssistant(ctx, team)
|
2023-05-15 18:06:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "ensure default setup assistant")
|
|
|
|
}
|
|
|
|
profUUID = defProfUUID
|
|
|
|
if profUUID == "" {
|
|
|
|
// this should not happen, return an error
|
|
|
|
return ctxerr.Errorf(ctx, "default setup assistant profile uuid is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, args.HostSerialNumbers...)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "assign profile")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MacosSetupAssistant) runUpdateAllProfiles(ctx context.Context, args macosSetupAssistantArgs) error {
|
2023-05-17 13:06:14 +00:00
|
|
|
// for all teams and no-team, run the UpdateProfile task
|
2023-05-15 18:06:09 +00:00
|
|
|
teams, err := m.Datastore.TeamsSummary(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "get all teams")
|
|
|
|
}
|
|
|
|
|
|
|
|
processTeam := func(team *fleet.TeamSummary) error {
|
|
|
|
var teamID *uint
|
|
|
|
if team != nil {
|
|
|
|
teamID = &team.ID
|
|
|
|
}
|
|
|
|
|
2023-05-17 13:06:14 +00:00
|
|
|
if err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantUpdateProfile, teamID); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "queue macos setup assistant update profile job")
|
2023-05-15 18:06:09 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tm := range teams {
|
|
|
|
if err := processTeam(tm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// and finally for no-team
|
|
|
|
if err := processTeam(nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-17 13:06:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MacosSetupAssistant) runUpdateProfile(ctx context.Context, args macosSetupAssistantArgs) error {
|
|
|
|
// clear the profile uuid for the default setup assistant for that team/no-team
|
|
|
|
if err := m.Datastore.SetMDMAppleDefaultSetupAssistantProfileUUID(ctx, args.TeamID, ""); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "clear default setup assistant profile uuid")
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear the profile uuid for the custom setup assistant
|
|
|
|
if err := m.Datastore.SetMDMAppleSetupAssistantProfileUUID(ctx, args.TeamID, ""); err != nil {
|
|
|
|
if fleet.IsNotFound(err) {
|
|
|
|
// no setup assistant for that team, enqueue a profile deleted task so
|
|
|
|
// the default profile is assigned to the hosts.
|
|
|
|
if err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantProfileDeleted, args.TeamID); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "queue macos setup assistant profile deleted job")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ctxerr.Wrap(ctx, err, "clear custom setup assistant profile uuid")
|
|
|
|
}
|
2023-05-15 18:06:09 +00:00
|
|
|
|
2023-05-17 13:06:14 +00:00
|
|
|
// no error means that the setup assistant existed for that team, enqueue a profile
|
|
|
|
// changed task so the custom profile is assigned to the hosts.
|
|
|
|
if err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantProfileChanged, args.TeamID); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "queue macos setup assistant profile changed job")
|
|
|
|
}
|
2023-05-15 18:06:09 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-05-17 13:06:14 +00:00
|
|
|
func (m *MacosSetupAssistant) getTeamNoTeam(ctx context.Context, tmID *uint) (*fleet.Team, error) {
|
|
|
|
var team *fleet.Team
|
|
|
|
if tmID != nil {
|
|
|
|
tm, err := m.Datastore.Team(ctx, *tmID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
team = tm
|
|
|
|
}
|
|
|
|
return team, nil
|
|
|
|
}
|
|
|
|
|
2023-05-15 18:06:09 +00:00
|
|
|
// QueueMacosSetupAssistantJob queues a macos_setup_assistant job for one of
|
|
|
|
// the supported tasks, to be processed asynchronously via the worker.
|
|
|
|
func QueueMacosSetupAssistantJob(
|
|
|
|
ctx context.Context,
|
|
|
|
ds fleet.Datastore,
|
|
|
|
logger kitlog.Logger,
|
|
|
|
task MacosSetupAssistantTask,
|
|
|
|
teamID *uint,
|
|
|
|
serialNumbers ...string,
|
|
|
|
) error {
|
|
|
|
attrs := []interface{}{
|
|
|
|
"enabled", "true",
|
|
|
|
macosSetupAssistantJobName, task,
|
|
|
|
"hosts_count", len(serialNumbers),
|
|
|
|
}
|
|
|
|
if teamID != nil {
|
|
|
|
attrs = append(attrs, "team_id", *teamID)
|
|
|
|
}
|
|
|
|
level.Info(logger).Log(attrs...)
|
|
|
|
|
|
|
|
args := &macosSetupAssistantArgs{
|
|
|
|
Task: task,
|
|
|
|
TeamID: teamID,
|
|
|
|
HostSerialNumbers: serialNumbers,
|
|
|
|
}
|
|
|
|
job, err := QueueJob(ctx, ds, macosSetupAssistantJobName, args)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "queueing job")
|
|
|
|
}
|
|
|
|
level.Debug(logger).Log("job_id", job.ID)
|
|
|
|
return nil
|
|
|
|
}
|