mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #40540 go-kit/log is no longer a direct dependency; moved kitlog adapter required for some 3rd party libraries into its own package # Checklist for submitter - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - Present in previous PR ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Modernized logging across the codebase: switched from legacy logging wrappers to Go's standard slog, updated adapters, tests, tools, and server components. * Threaded the new slog logger through test utilities and tooling; adjusted a small number of logging-related function/constructor signatures to accept the new logger type (minor compatibility updates). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
403 lines
13 KiB
Go
403 lines
13 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
|
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMacosSetupAssistant(t *testing.T) {
|
|
ctx := context.Background()
|
|
ds := mysql.CreateMySQLDS(t)
|
|
// call TruncateTables immediately as some DB migrations may create jobs
|
|
mysql.TruncateTables(t, ds)
|
|
|
|
org1Name := "org1"
|
|
org2Name := "org2"
|
|
|
|
mysql.SetTestABMAssets(t, ds, "fleet")
|
|
tok := mysql.CreateAndSetABMToken(t, ds, org1Name)
|
|
tok2 := mysql.CreateAndSetABMToken(t, ds, org2Name)
|
|
|
|
// create a couple hosts for no team, team 1 and team 2 (none for team 3)
|
|
hosts := make([]*fleet.Host, 6)
|
|
for i := 0; i < len(hosts); i++ {
|
|
h, err := ds.NewHost(ctx, &fleet.Host{
|
|
Hostname: fmt.Sprintf("test-host%d-name", i),
|
|
OsqueryHostID: ptr.String(fmt.Sprintf("osquery-%d", i)),
|
|
NodeKey: ptr.String(fmt.Sprintf("nodekey-%d", i)),
|
|
UUID: fmt.Sprintf("test-uuid-%d", i),
|
|
Platform: "darwin",
|
|
HardwareSerial: fmt.Sprintf("serial-%d", i),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tokID := tok.ID
|
|
if i%2 == 0 {
|
|
tokID = tok2.ID
|
|
}
|
|
|
|
err = ds.UpsertMDMAppleHostDEPAssignments(ctx, []fleet.Host{*h}, tokID, make(map[uint]time.Time))
|
|
require.NoError(t, err)
|
|
hosts[i] = h
|
|
t.Logf("host [%d]: %s - %s - %d", i, h.UUID, h.HardwareSerial, tokID)
|
|
}
|
|
|
|
// create teams
|
|
tm1, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1"})
|
|
require.NoError(t, err)
|
|
tm2, err := ds.NewTeam(ctx, &fleet.Team{Name: "team2"})
|
|
require.NoError(t, err)
|
|
tm3, err := ds.NewTeam(ctx, &fleet.Team{Name: "team3"})
|
|
require.NoError(t, err)
|
|
|
|
// hosts[0, 1] are no-team, hosts[2, 3] are team1, hosts[4, 5] are team2
|
|
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&tm1.ID, []uint{hosts[2].ID, hosts[3].ID}))
|
|
require.NoError(t, err)
|
|
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&tm2.ID, []uint{hosts[4].ID, hosts[5].ID}))
|
|
require.NoError(t, err)
|
|
|
|
logger := slog.New(slog.DiscardHandler)
|
|
depStorage, err := ds.NewMDMAppleDEPStorage()
|
|
require.NoError(t, err)
|
|
macosJob := &MacosSetupAssistant{
|
|
Datastore: ds,
|
|
Log: logger,
|
|
DEPService: apple_mdm.NewDEPService(ds, depStorage, logger),
|
|
DEPClient: apple_mdm.NewDEPClient(depStorage, ds, logger),
|
|
}
|
|
|
|
const defaultProfileName = "Fleet default enrollment profile"
|
|
|
|
// track the profile assigned to each device
|
|
serialsToProfile := map[string]string{
|
|
"serial-0": "",
|
|
"serial-1": "",
|
|
"serial-2": "",
|
|
"serial-3": "",
|
|
"serial-4": "",
|
|
"serial-5": "",
|
|
}
|
|
|
|
// start the web server that will mock Apple DEP responses
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
encoder := json.NewEncoder(w)
|
|
switch r.URL.Path {
|
|
case "/session":
|
|
err := encoder.Encode(map[string]string{"auth_session_token": "auth123"})
|
|
require.NoError(t, err)
|
|
|
|
case "/profile":
|
|
var reqProf godep.Profile
|
|
b, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
err = json.Unmarshal(b, &reqProf)
|
|
require.NoError(t, err)
|
|
|
|
// use the profile name as profile uuid, and append "+sso" if it was
|
|
// registered with the sso url (end-user auth enabled).
|
|
profUUID := reqProf.ProfileName
|
|
if strings.HasSuffix(reqProf.ConfigurationWebURL, "/mdm/sso") {
|
|
profUUID += "+sso"
|
|
}
|
|
err = encoder.Encode(godep.ProfileResponse{ProfileUUID: profUUID})
|
|
require.NoError(t, err)
|
|
|
|
case "/profile/devices":
|
|
var reqProf godep.Profile
|
|
b, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
err = json.Unmarshal(b, &reqProf)
|
|
require.NoError(t, err)
|
|
|
|
for _, d := range reqProf.Devices {
|
|
serialsToProfile[d] = reqProf.ProfileUUID
|
|
}
|
|
_, _ = w.Write([]byte(`{}`))
|
|
|
|
default:
|
|
t.Errorf("unexpected request: %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
err = depStorage.StoreConfig(ctx, "fleet", &nanodep_client.Config{BaseURL: srv.URL})
|
|
require.NoError(t, err)
|
|
err = depStorage.StoreConfig(ctx, org1Name, &nanodep_client.Config{BaseURL: srv.URL})
|
|
require.NoError(t, err)
|
|
err = depStorage.StoreConfig(ctx, org2Name, &nanodep_client.Config{BaseURL: srv.URL})
|
|
require.NoError(t, err)
|
|
|
|
w := NewWorker(ds, logger)
|
|
w.Register(macosJob)
|
|
|
|
runCheckDone := func() {
|
|
err = w.ProcessJobs(ctx)
|
|
require.NoError(t, err)
|
|
// no remaining jobs to process
|
|
pending, err := ds.GetQueuedJobs(ctx, 10, time.Time{})
|
|
require.NoError(t, err)
|
|
require.Empty(t, pending)
|
|
}
|
|
|
|
// no jobs to process yet
|
|
runCheckDone()
|
|
|
|
// no default profile registered yet
|
|
_, err = ds.GetMDMAppleEnrollmentProfileByType(ctx, fleet.MDMAppleEnrollmentTypeAutomatic)
|
|
require.ErrorIs(t, err, sql.ErrNoRows)
|
|
|
|
start := time.Now().Truncate(time.Second)
|
|
|
|
// enqueue a regenerate all and process the jobs
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateAllProfiles, nil)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
// all devices are assigned the default profile
|
|
autoProf, err := ds.GetMDMAppleEnrollmentProfileByType(ctx, fleet.MDMAppleEnrollmentTypeAutomatic)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, autoProf.Token)
|
|
|
|
getTeamID := func(tmID *uint) string {
|
|
if tmID == nil {
|
|
return "null"
|
|
}
|
|
|
|
return fmt.Sprint(*tmID)
|
|
}
|
|
|
|
tmIDs := []*uint{nil, ptr.Uint(tm1.ID), ptr.Uint(tm2.ID)}
|
|
for _, tmID := range tmIDs {
|
|
for _, org := range []string{org1Name, org2Name} {
|
|
profUUID, modTime, err := ds.GetMDMAppleDefaultSetupAssistant(ctx, tmID, org)
|
|
require.NoError(t, err)
|
|
require.Equal(t, defaultProfileName, profUUID, "tmID", getTeamID(tmID))
|
|
require.False(t, modTime.Before(start))
|
|
}
|
|
}
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": defaultProfileName,
|
|
"serial-1": defaultProfileName,
|
|
"serial-2": defaultProfileName,
|
|
"serial-3": defaultProfileName,
|
|
"serial-4": defaultProfileName,
|
|
"serial-5": defaultProfileName,
|
|
}, serialsToProfile)
|
|
|
|
// create a custom setup assistant for team 1 and process the job
|
|
tm1Asst, err := ds.SetOrUpdateMDMAppleSetupAssistant(ctx, &fleet.MDMAppleSetupAssistant{
|
|
TeamID: &tm1.ID,
|
|
Name: "team1",
|
|
Profile: json.RawMessage(`{"profile_name": "team1"}`),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotZero(t, tm1Asst.ID)
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm1.ID)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
// default profile is unchanged
|
|
for _, tmID := range tmIDs {
|
|
for _, org := range []string{org1Name, org2Name} {
|
|
profUUID, modTime, err := ds.GetMDMAppleDefaultSetupAssistant(ctx, tmID, org)
|
|
require.NoError(t, err)
|
|
require.Equal(t, defaultProfileName, profUUID)
|
|
require.False(t, modTime.Before(start))
|
|
}
|
|
}
|
|
|
|
// team 1 setup assistant is registered for both tokens
|
|
tm1Asst, err = ds.GetMDMAppleSetupAssistant(ctx, &tm1.ID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tm1Asst)
|
|
for _, org := range []string{org1Name, org2Name} {
|
|
profUUID, modTime, err := ds.GetMDMAppleSetupAssistantProfileForABMToken(ctx, &tm1.ID, org)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "team1", profUUID)
|
|
require.False(t, modTime.Before(start))
|
|
}
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": defaultProfileName,
|
|
"serial-1": defaultProfileName,
|
|
"serial-2": "team1",
|
|
"serial-3": "team1",
|
|
"serial-4": defaultProfileName,
|
|
"serial-5": defaultProfileName,
|
|
}, serialsToProfile)
|
|
|
|
// enable end-user auth for team 2
|
|
tm2.Config.MDM.MacOSSetup.EnableEndUserAuthentication = true
|
|
tm2, err = ds.SaveTeam(ctx, tm2)
|
|
require.NoError(t, err)
|
|
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateProfile, &tm2.ID)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": defaultProfileName,
|
|
"serial-1": defaultProfileName,
|
|
"serial-2": "team1",
|
|
"serial-3": "team1",
|
|
"serial-4": defaultProfileName + "+sso",
|
|
"serial-5": defaultProfileName + "+sso",
|
|
}, serialsToProfile)
|
|
|
|
// create a custom setup assistant for teams 2 and 3, delete the one for team 1 and process the jobs
|
|
tm2Asst, err := ds.SetOrUpdateMDMAppleSetupAssistant(ctx, &fleet.MDMAppleSetupAssistant{
|
|
TeamID: &tm2.ID,
|
|
Name: "team2",
|
|
Profile: json.RawMessage(`{"profile_name": "team2"}`),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotZero(t, tm2Asst.ID)
|
|
tm3Asst, err := ds.SetOrUpdateMDMAppleSetupAssistant(ctx, &fleet.MDMAppleSetupAssistant{
|
|
TeamID: &tm3.ID,
|
|
Name: "team3",
|
|
Profile: json.RawMessage(`{"profile_name": "team3"}`),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotZero(t, tm3Asst.ID)
|
|
err = ds.DeleteMDMAppleSetupAssistant(ctx, &tm1.ID)
|
|
require.NoError(t, err)
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm2.ID)
|
|
require.NoError(t, err)
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm3.ID)
|
|
require.NoError(t, err)
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileDeleted, &tm1.ID)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": defaultProfileName,
|
|
"serial-1": defaultProfileName,
|
|
"serial-2": defaultProfileName,
|
|
"serial-3": defaultProfileName,
|
|
"serial-4": "team2+sso",
|
|
"serial-5": "team2+sso",
|
|
}, serialsToProfile)
|
|
|
|
// disable end-user auth for team 2
|
|
tm2.Config.MDM.MacOSSetup.EnableEndUserAuthentication = false
|
|
tm2, err = ds.SaveTeam(ctx, tm2)
|
|
require.NoError(t, err)
|
|
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateProfile, &tm2.ID)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": defaultProfileName,
|
|
"serial-1": defaultProfileName,
|
|
"serial-2": defaultProfileName,
|
|
"serial-3": defaultProfileName,
|
|
"serial-4": "team2",
|
|
"serial-5": "team2",
|
|
}, serialsToProfile)
|
|
|
|
// move hosts[2,4] to team 3, delete team 2
|
|
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&tm3.ID, []uint{hosts[2].ID, hosts[4].ID}))
|
|
require.NoError(t, err)
|
|
err = ds.DeleteTeam(ctx, tm2.ID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantHostsTransferred, &tm3.ID, "serial-2", "serial-4")
|
|
require.NoError(t, err)
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantTeamDeleted, nil, "serial-5") // hosts[5] was in team 2
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": defaultProfileName,
|
|
"serial-1": defaultProfileName,
|
|
"serial-2": "team3",
|
|
"serial-3": defaultProfileName,
|
|
"serial-4": "team3",
|
|
"serial-5": defaultProfileName,
|
|
}, serialsToProfile)
|
|
|
|
// create setup assistant for no-team
|
|
noTmAsst, err := ds.SetOrUpdateMDMAppleSetupAssistant(ctx, &fleet.MDMAppleSetupAssistant{
|
|
TeamID: nil,
|
|
Name: "no-team",
|
|
Profile: json.RawMessage(`{"profile_name": "no-team"}`),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotZero(t, noTmAsst.ID)
|
|
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, nil)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": "no-team",
|
|
"serial-1": "no-team",
|
|
"serial-2": "team3",
|
|
"serial-3": defaultProfileName,
|
|
"serial-4": "team3",
|
|
"serial-5": "no-team", // became a no-team host when team2 got deleted
|
|
}, serialsToProfile)
|
|
|
|
// check that profiles get re-generated (note that timestamps are not
|
|
// impacted as the content of the profiles did not change)
|
|
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateAllProfiles, nil)
|
|
require.NoError(t, err)
|
|
runCheckDone()
|
|
|
|
// team 2 got deleted, update the list of team IDs
|
|
tmIDs = []*uint{nil, ptr.Uint(tm1.ID), ptr.Uint(tm3.ID)}
|
|
for i, tmID := range tmIDs {
|
|
for _, org := range []string{org1Name, org2Name} {
|
|
// no team and team 3 have a custom setup assistant
|
|
switch i {
|
|
case 0: // no team
|
|
// custom profile defined for both orgs
|
|
_, _, err := ds.GetMDMAppleSetupAssistantProfileForABMToken(ctx, tmID, org)
|
|
require.NoError(t, err, "%v - %v", i, org)
|
|
case 1: // tm1
|
|
// team 1 uses the default setup assistant, and it is only defined for org1
|
|
_, _, err := ds.GetMDMAppleDefaultSetupAssistant(ctx, tmID, org)
|
|
if org == org1Name {
|
|
require.NoError(t, err, "%v - %v", i, org)
|
|
} else {
|
|
require.ErrorIs(t, err, sql.ErrNoRows, "%v - %v", i, org)
|
|
}
|
|
case 2: // tm3
|
|
_, _, err := ds.GetMDMAppleSetupAssistantProfileForABMToken(ctx, tmID, org)
|
|
// custom setup assistant only defined for org2
|
|
if org == org2Name {
|
|
require.NoError(t, err, "%v - %v", i, org)
|
|
} else {
|
|
require.ErrorIs(t, err, sql.ErrNoRows, "%v - %v", i, org)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
require.Equal(t, map[string]string{
|
|
"serial-0": "no-team",
|
|
"serial-1": "no-team",
|
|
"serial-2": "team3",
|
|
"serial-3": defaultProfileName,
|
|
"serial-4": "team3",
|
|
"serial-5": "no-team", // became a no-team host when team2 got deleted
|
|
}, serialsToProfile)
|
|
}
|