fleet/server/mdm/apple/profile_processor_test.go
Magnus Jensen a8c9e261d7
speed up macOS profile delivery for initial enrollments (#41960)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #34433 

It speeds up the cron, meaning fleetd, bootstrap and now profiles should
be sent within 10 seconds of being known to fleet, compared to the
previous 1 minute.

It's heavily based on my last PR, so the structure and changes are close
to identical, with some small differences.
**I did not do the redis key part in this PR, as I think that should
come in it's own PR, to avoid overlooking logic bugs with that code, and
since this one is already quite sized since we're moving core pieces of
code around.**

# 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.


## 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

* **New Features**
* Faster macOS onboarding: device profiles are delivered and installed
as part of DEP enrollment, shortening initial setup.
* Improved profile handling: per-host profile preprocessing, secret
detection, and clearer failure marking.

* **Improvements**
  * Consolidated SCEP/NDES error messaging for clearer diagnostics.
  * Cron/work scheduling tuned to prioritize Apple MDM profile delivery.

* **Tests**
* Expanded MDM unit and integration tests, including
DeclarativeManagement handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-03-19 14:58:10 -05:00

999 lines
40 KiB
Go

package apple_mdm
import (
"context"
"errors"
"fmt"
"log/slog"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/fleetdm/fleet/v4/ee/server/service/digicert"
"github.com/fleetdm/fleet/v4/ee/server/service/scep"
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
"github.com/fleetdm/fleet/v4/server/mock"
digicert_mock "github.com/fleetdm/fleet/v4/server/mock/digicert"
scep_mock "github.com/fleetdm/fleet/v4/server/mock/scep"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPreprocessProfileContents(t *testing.T) {
ctx := context.Background()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
appCfg := &fleet.AppConfig{}
appCfg.ServerSettings.ServerURL = "https://test.example.com"
appCfg.MDM.EnabledAndConfigured = true
ds := new(mock.Store)
// No-op
svc := scep.NewSCEPConfigService(logger, nil)
digiCertService := digicert.NewService(digicert.WithLogger(logger))
err := preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, nil, nil, nil, nil, nil)
require.NoError(t, err)
hostUUID := "host-1"
cmdUUID := "cmd-1"
var targets map[string]*fleet.CmdTarget
populateTargets := func() {
targets = map[string]*fleet.CmdTarget{
"p1": {CmdUUID: cmdUUID, ProfileIdentifier: "com.add.profile", EnrollmentIDs: []string{hostUUID}},
}
}
hostProfilesToInstallMap := make(map[fleet.HostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload, 1)
hostProfilesToInstallMap[fleet.HostProfileUUID{HostUUID: hostUUID, ProfileUUID: "p1"}] = &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileUUID: "p1",
ProfileIdentifier: "com.add.profile",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
CommandUUID: cmdUUID,
Scope: fleet.PayloadScopeSystem,
}
userEnrollmentsToHostUUIDsMap := make(map[string]string)
populateTargets()
profileContents := map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + fleet.FleetVarNDESSCEPProxyURL),
}
var updatedPayload *fleet.MDMAppleBulkUpsertHostProfilePayload
ds.BulkUpsertMDMAppleHostProfilesFunc = func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
require.Len(t, payload, 1)
updatedPayload = payload[0]
for _, p := range payload {
require.NotNil(t, p.Status)
assert.Equal(t, fleet.MDMDeliveryFailed, *p.Status)
assert.Equal(t, cmdUUID, p.CommandUUID)
assert.Equal(t, hostUUID, p.HostUUID)
assert.Equal(t, fleet.MDMOperationTypeInstall, p.OperationType)
assert.Equal(t, fleet.PayloadScopeSystem, p.Scope)
}
return nil
}
// Can't use NDES SCEP proxy with free tier
ctx = license.NewContext(ctx, &fleet.LicenseInfo{Tier: fleet.TierFree})
err = preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, nil)
require.NoError(t, err)
require.NotNil(t, updatedPayload)
assert.Contains(t, updatedPayload.Detail, "Premium license")
assert.Empty(t, targets)
// Can't use NDES SCEP proxy without it being configured
ctx = license.NewContext(ctx, &fleet.LicenseInfo{Tier: fleet.TierPremium})
updatedPayload = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, &fleet.GroupedCertificateAuthorities{})
require.NoError(t, err)
require.NotNil(t, updatedPayload)
assert.Contains(t, updatedPayload.Detail, "not configured")
assert.NotNil(t, updatedPayload.VariablesUpdatedAt)
assert.Empty(t, targets)
// Unknown variable
profileContents = map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_BOZO"),
}
updatedPayload = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, nil)
require.NoError(t, err)
require.NotNil(t, updatedPayload)
assert.Contains(t, updatedPayload.Detail, "FLEET_VAR_BOZO")
assert.Empty(t, targets)
ndesPassword := "test-password"
ds.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context,
assetNames []fleet.MDMAssetName, _ sqlx.QueryerContext,
) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) {
return map[fleet.MDMAssetName]fleet.MDMConfigAsset{
fleet.MDMAssetNDESPassword: {Value: []byte(ndesPassword)},
}, nil
}
ds.BulkUpsertMDMAppleHostProfilesFunc = nil
var updatedProfile *fleet.HostMDMAppleProfile
ds.UpdateOrDeleteHostMDMAppleProfileFunc = func(ctx context.Context, profile *fleet.HostMDMAppleProfile) error {
updatedProfile = profile
require.NotNil(t, updatedProfile.Status)
assert.Equal(t, fleet.MDMDeliveryFailed, *updatedProfile.Status)
assert.Equal(t, cmdUUID, updatedProfile.CommandUUID)
assert.Equal(t, hostUUID, updatedProfile.HostUUID)
assert.Equal(t, fleet.MDMOperationTypeInstall, updatedProfile.OperationType)
return nil
}
ds.BulkUpsertMDMManagedCertificatesFunc = func(ctx context.Context, payload []*fleet.MDMManagedCertificate) error {
assert.Empty(t, payload)
return nil
}
adminUrl := "https://example.com"
username := "admin"
password := "test-password"
groupedCAs := &fleet.GroupedCertificateAuthorities{
NDESSCEP: &fleet.NDESSCEPProxyCA{
URL: "https://test-example.com",
AdminURL: adminUrl,
Username: username,
Password: password,
},
}
// Could not get NDES SCEP challenge
profileContents = map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + fleet.FleetVarNDESSCEPChallenge),
}
scepConfig := &scep_mock.SCEPConfigService{}
scepConfig.GetNDESSCEPChallengeFunc = func(ctx context.Context, proxy fleet.NDESSCEPProxyCA) (string, error) {
assert.Equal(t, ndesPassword, proxy.Password)
return "", scep.NewNDESInvalidError("NDES error")
}
updatedProfile = nil
populateTargets()
ds.BulkUpsertMDMAppleHostProfilesFunc = func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
assert.Empty(t, payload) // no profiles to update since FLEET VAR could not be populated
return nil
}
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotNil(t, updatedProfile)
assert.Contains(t, updatedProfile.Detail, "FLEET_VAR_"+fleet.FleetVarNDESSCEPChallenge)
assert.Contains(t, updatedProfile.Detail, "update credentials")
assert.NotNil(t, updatedProfile.VariablesUpdatedAt)
assert.Empty(t, targets)
// Password cache full
scepConfig.GetNDESSCEPChallengeFunc = func(ctx context.Context, proxy fleet.NDESSCEPProxyCA) (string, error) {
assert.Equal(t, ndesPassword, proxy.Password)
return "", scep.NewNDESPasswordCacheFullError("NDES error")
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotNil(t, updatedProfile)
assert.Contains(t, updatedProfile.Detail, "FLEET_VAR_"+fleet.FleetVarNDESSCEPChallenge)
assert.Contains(t, updatedProfile.Detail, "cached passwords")
assert.NotNil(t, updatedProfile.VariablesUpdatedAt)
assert.Empty(t, targets)
// Insufficient permissions
scepConfig.GetNDESSCEPChallengeFunc = func(ctx context.Context, proxy fleet.NDESSCEPProxyCA) (string, error) {
assert.Equal(t, ndesPassword, proxy.Password)
return "", scep.NewNDESInsufficientPermissionsError("NDES error")
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotNil(t, updatedProfile)
assert.Contains(t, updatedProfile.Detail, "FLEET_VAR_"+fleet.FleetVarNDESSCEPChallenge)
assert.Contains(t, updatedProfile.Detail, "does not have sufficient permissions")
assert.NotNil(t, updatedProfile.VariablesUpdatedAt)
assert.Empty(t, targets)
// Other NDES challenge error
scepConfig.GetNDESSCEPChallengeFunc = func(ctx context.Context, proxy fleet.NDESSCEPProxyCA) (string, error) {
assert.Equal(t, ndesPassword, proxy.Password)
return "", errors.New("NDES error")
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotNil(t, updatedProfile)
assert.Contains(t, updatedProfile.Detail, "FLEET_VAR_"+fleet.FleetVarNDESSCEPChallenge)
assert.NotContains(t, updatedProfile.Detail, "cached passwords")
assert.NotContains(t, updatedProfile.Detail, "update credentials")
assert.NotNil(t, updatedProfile.VariablesUpdatedAt)
assert.Empty(t, targets)
// NDES challenge
challenge := "ndes-challenge"
scepConfig.GetNDESSCEPChallengeFunc = func(ctx context.Context, proxy fleet.NDESSCEPProxyCA) (string, error) {
assert.Equal(t, ndesPassword, proxy.Password)
return challenge, nil
}
updatedProfile = nil
ds.BulkUpsertMDMAppleHostProfilesFunc = func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
for _, p := range payload {
assert.NotEqual(t, cmdUUID, p.CommandUUID)
}
return nil
}
populateTargets()
ds.BulkUpsertMDMManagedCertificatesFunc = func(ctx context.Context, payload []*fleet.MDMManagedCertificate) error {
require.Len(t, payload, 1)
assert.NotNil(t, payload[0].ChallengeRetrievedAt)
return nil
}
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
assert.Nil(t, updatedProfile)
require.NotEmpty(t, targets)
assert.Len(t, targets, 1)
for profUUID, target := range targets {
assert.NotEqual(t, profUUID, "p1") // new temporary UUID generated for specific host
assert.NotEqual(t, cmdUUID, target.CmdUUID)
assert.Equal(t, []string{hostUUID}, target.EnrollmentIDs)
assert.Equal(t, challenge, string(profileContents[profUUID]))
}
// NDES SCEP proxy URL
profileContents = map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + fleet.FleetVarNDESSCEPProxyURL),
}
expectedURL := "https://test.example.com" + SCEPProxyPath + url.QueryEscape(fmt.Sprintf("%s,%s,NDES", hostUUID, "p1"))
updatedProfile = nil
populateTargets()
ds.BulkUpsertMDMManagedCertificatesFunc = func(ctx context.Context, payload []*fleet.MDMManagedCertificate) error {
assert.Empty(t, payload)
return nil
}
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
assert.Nil(t, updatedProfile)
require.NotEmpty(t, targets)
assert.Len(t, targets, 1)
for profUUID, target := range targets {
assert.NotEqual(t, profUUID, "p1") // new temporary UUID generated for specific host
assert.NotEqual(t, cmdUUID, target.CmdUUID)
assert.Equal(t, []string{hostUUID}, target.EnrollmentIDs)
assert.Equal(t, expectedURL, string(profileContents[profUUID]))
}
// No IdP email found
ds.GetHostEmailsFunc = func(ctx context.Context, hostUUID string, source string) ([]string, error) {
return nil, nil
}
profileContents = map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + fleet.FleetVarHostEndUserEmailIDP),
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotNil(t, updatedProfile)
assert.Contains(t, updatedProfile.Detail, "FLEET_VAR_"+fleet.FleetVarHostEndUserEmailIDP)
assert.Contains(t, updatedProfile.Detail, "no IdP email")
assert.Empty(t, targets)
// IdP email found
email := "user@example.com"
ds.GetHostEmailsFunc = func(ctx context.Context, hostUUID string, source string) ([]string, error) {
return []string{email}, nil
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
assert.Nil(t, updatedProfile)
require.NotEmpty(t, targets)
assert.Len(t, targets, 1)
for profUUID, target := range targets {
assert.NotEqual(t, profUUID, "p1") // new temporary UUID generated for specific host
assert.NotEqual(t, cmdUUID, target.CmdUUID)
assert.Equal(t, []string{hostUUID}, target.EnrollmentIDs)
assert.Equal(t, email, string(profileContents[profUUID]))
}
// Hardware serial
ds.ListHostsLiteByUUIDsFunc = func(ctx context.Context, _ fleet.TeamFilter, uuids []string) ([]*fleet.Host, error) {
assert.Equal(t, []string{hostUUID}, uuids)
return []*fleet.Host{
{HardwareSerial: "serial1"},
}, nil
}
profileContents = map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + fleet.FleetVarHostHardwareSerial),
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
assert.Nil(t, updatedProfile)
require.NotEmpty(t, targets)
assert.Len(t, targets, 1)
for profUUID, target := range targets {
assert.NotEqual(t, profUUID, "p1") // new temporary UUID generated for specific host
assert.NotEqual(t, cmdUUID, target.CmdUUID)
assert.Equal(t, []string{hostUUID}, target.EnrollmentIDs)
assert.Equal(t, "serial1", string(profileContents[profUUID]))
}
// Hardware serial fail
ds.ListHostsLiteByUUIDsFunc = func(ctx context.Context, _ fleet.TeamFilter, uuids []string) ([]*fleet.Host, error) {
assert.Equal(t, []string{hostUUID}, uuids)
return nil, nil
}
updatedProfile = nil
populateTargets()
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotNil(t, updatedProfile)
assert.Contains(t, updatedProfile.Detail, "Unexpected number of hosts (0) for UUID")
assert.Empty(t, targets)
// multiple profiles, multiple hosts
populateTargets = func() {
targets = map[string]*fleet.CmdTarget{
"p1": {CmdUUID: cmdUUID, ProfileIdentifier: "com.add.profile", EnrollmentIDs: []string{hostUUID, "host-2"}}, // fails
"p2": {CmdUUID: cmdUUID, ProfileIdentifier: "com.add.profile2", EnrollmentIDs: []string{hostUUID, "host-3"}}, // works
"p3": {CmdUUID: cmdUUID, ProfileIdentifier: "com.add.profile3", EnrollmentIDs: []string{hostUUID, "host-4"}}, // no variables
}
}
populateTargets()
groupedCAs.NDESSCEP = nil
profileContents = map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + fleet.FleetVarNDESSCEPProxyURL),
"p2": []byte("$FLEET_VAR_" + fleet.FleetVarHostEndUserEmailIDP),
"p3": []byte("no variables"),
}
addProfileToInstall := func(hostUUID, profileUUID, profileIdentifier string) {
hostProfilesToInstallMap[fleet.HostProfileUUID{
HostUUID: hostUUID,
ProfileUUID: profileUUID,
}] = &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileUUID: profileUUID,
ProfileIdentifier: profileIdentifier,
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
CommandUUID: cmdUUID,
Scope: fleet.PayloadScopeSystem,
}
}
addProfileToInstall(hostUUID, "p1", "com.add.profile")
addProfileToInstall("host-2", "p1", "com.add.profile")
addProfileToInstall(hostUUID, "p2", "com.add.profile2")
addProfileToInstall("host-3", "p2", "com.add.profile2")
addProfileToInstall(hostUUID, "p3", "com.add.profile3")
addProfileToInstall("host-4", "p3", "com.add.profile3")
expectedHostsToFail := []string{hostUUID, "host-2", "host-3"}
ds.UpdateOrDeleteHostMDMAppleProfileFunc = func(ctx context.Context, profile *fleet.HostMDMAppleProfile) error {
updatedProfile = profile
require.NotNil(t, updatedProfile.Status)
assert.Equal(t, fleet.MDMDeliveryFailed, *updatedProfile.Status)
assert.NotEqual(t, cmdUUID, updatedProfile.CommandUUID)
assert.Contains(t, expectedHostsToFail, updatedProfile.HostUUID)
assert.Equal(t, fleet.MDMOperationTypeInstall, updatedProfile.OperationType)
return nil
}
ds.BulkUpsertMDMAppleHostProfilesFunc = func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
for _, p := range payload {
require.NotNil(t, p.Status)
if fleet.MDMDeliveryFailed == *p.Status {
assert.Equal(t, cmdUUID, p.CommandUUID)
} else {
assert.NotEqual(t, cmdUUID, p.CommandUUID)
}
assert.Equal(t, fleet.MDMOperationTypeInstall, p.OperationType)
}
return nil
}
err = preprocessProfileContents(ctx, appCfg, ds, scepConfig, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, groupedCAs)
require.NoError(t, err)
require.NotEmpty(t, targets)
assert.Len(t, targets, 3)
assert.Nil(t, targets["p1"]) // error
assert.Nil(t, targets["p2"]) // renamed
assert.NotNil(t, targets["p3"]) // normal, no variables
for profUUID, target := range targets {
assert.Contains(t, [][]string{{hostUUID}, {"host-3"}, {hostUUID, "host-4"}}, target.EnrollmentIDs)
if profUUID == "p3" {
assert.Equal(t, cmdUUID, target.CmdUUID)
} else {
assert.NotEqual(t, cmdUUID, target.CmdUUID)
}
assert.Contains(t, []string{email, "no variables"}, string(profileContents[profUUID]))
}
}
// TestPreprocessProfileContentsDigiCertUPNMultiHost is a regression test for
// https://github.com/fleetdm/fleet/issues/39324. When the same DigiCert CA is
// used for multiple hosts in a single batch, the CertificateUserPrincipalNames
// slice was shared via a shallow copy. In-place variable substitution for Host 1
// corrupted the cached CA entry, so Host 2 and later hosts received Host 1's
// substituted UPN instead of their own.
func TestPreprocessProfileContentsDigiCertUPNIsUniqueForMultipleHosts(t *testing.T) {
ctx := context.Background()
ctx = license.NewContext(ctx, &fleet.LicenseInfo{Tier: fleet.TierPremium})
logger := slog.New(slog.DiscardHandler)
appCfg := &fleet.AppConfig{}
appCfg.ServerSettings.ServerURL = "https://test.example.com"
appCfg.MDM.EnabledAndConfigured = true
ds := new(mock.Store)
svc := scep.NewSCEPConfigService(logger, nil)
const caName = "myCA"
const host1UUID = "host-uuid-1"
const host2UUID = "host-uuid-2"
const host1Serial = "SERIAL-AAA"
const host2Serial = "SERIAL-BBB"
const cmdUUID = "cmd-uuid-1"
// Track which UPNs were sent to GetCertificate per host.
upnByHostUUID := make(map[string]string)
mockDigiCert := &digicert_mock.Service{}
mockDigiCert.GetCertificateFunc = func(ctx context.Context, config fleet.DigiCertCA) (*fleet.DigiCertCertificate, error) {
// The UPN in config should have been substituted with each host's own
// hardware serial. Record it so we can assert correctness later.
require.Len(t, config.CertificateUserPrincipalNames, 1)
upn := config.CertificateUserPrincipalNames[0]
// Determine which host this call is for by checking which serial is in the UPN.
switch {
case strings.Contains(upn, host1Serial):
upnByHostUUID[host1UUID] = upn
case strings.Contains(upn, host2Serial):
upnByHostUUID[host2UUID] = upn
default:
t.Errorf("GetCertificate called with unexpected UPN %q", upn)
}
now := time.Now()
return &fleet.DigiCertCertificate{
PfxData: []byte("fake-pfx"),
Password: "fake-password",
NotValidBefore: now,
NotValidAfter: now.Add(365 * 24 * time.Hour),
SerialNumber: upn, // reuse upn as serial for easy tracing
}, nil
}
// Both hosts share the same profile UUID but are separate enrollment IDs.
targets := map[string]*fleet.CmdTarget{
"p1": {
CmdUUID: cmdUUID,
ProfileIdentifier: "com.apple.security.pkcs12",
EnrollmentIDs: []string{host1UUID, host2UUID},
},
}
pending := fleet.MDMDeliveryPending
hostProfilesToInstallMap := map[fleet.HostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload{
{HostUUID: host1UUID, ProfileUUID: "p1"}: {
ProfileUUID: "p1",
ProfileIdentifier: "com.apple.security.pkcs12",
HostUUID: host1UUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &pending,
CommandUUID: cmdUUID,
Scope: fleet.PayloadScopeSystem,
},
{HostUUID: host2UUID, ProfileUUID: "p1"}: {
ProfileUUID: "p1",
ProfileIdentifier: "com.apple.security.pkcs12",
HostUUID: host2UUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &pending,
CommandUUID: cmdUUID,
Scope: fleet.PayloadScopeSystem,
},
}
// Profile contains both DigiCert fleet variables.
profileContents := map[string]mobileconfig.Mobileconfig{
"p1": []byte("$FLEET_VAR_" + string(fleet.FleetVarDigiCertPasswordPrefix) + caName + " $FLEET_VAR_" + string(fleet.FleetVarDigiCertDataPrefix) + caName),
}
// DigiCert CA whose UPN contains the hardware serial variable.
groupedCAs := &fleet.GroupedCertificateAuthorities{
DigiCert: []fleet.DigiCertCA{
{
Name: caName,
URL: "https://digicert.example.com",
APIToken: "api_token",
ProfileID: "profile_id",
CertificateCommonName: "common_name",
CertificateUserPrincipalNames: []string{"$FLEET_VAR_" + string(fleet.FleetVarHostHardwareSerial) + "@example.com"},
CertificateSeatID: "seat_id",
},
},
}
// Mock datastore: return each host's own hardware serial when queried.
hostsByUUID := map[string]*fleet.Host{
host1UUID: {ID: 1, UUID: host1UUID, HardwareSerial: host1Serial, Platform: "darwin"},
host2UUID: {ID: 2, UUID: host2UUID, HardwareSerial: host2Serial, Platform: "darwin"},
}
ds.ListHostsLiteByUUIDsFunc = func(ctx context.Context, _ fleet.TeamFilter, uuids []string) ([]*fleet.Host, error) {
var hosts []*fleet.Host
for _, uuid := range uuids {
if h, ok := hostsByUUID[uuid]; ok {
hosts = append(hosts, h)
}
}
return hosts, nil
}
ds.BulkUpsertMDMAppleHostProfilesFunc = func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
return nil
}
ds.BulkUpsertMDMManagedCertificatesFunc = func(ctx context.Context, payload []*fleet.MDMManagedCertificate) error {
return nil
}
err := preprocessProfileContents(ctx, appCfg, ds, svc, mockDigiCert, logger, targets, profileContents, hostProfilesToInstallMap, make(map[string]string), groupedCAs)
require.NoError(t, err)
// Both hosts must have received GetCertificate calls with their own serial.
require.True(t, mockDigiCert.GetCertificateFuncInvoked, "GetCertificate was never called")
require.Contains(t, upnByHostUUID, host1UUID, "GetCertificate was not called for host 1")
require.Contains(t, upnByHostUUID, host2UUID, "GetCertificate was not called for host 2")
assert.Equal(t, host1Serial+"@example.com", upnByHostUUID[host1UUID], "host 1 UPN should contain its own serial")
assert.Equal(t, host2Serial+"@example.com", upnByHostUUID[host2UUID], "host 2 UPN should contain its own serial")
}
func TestPreprocessProfileContentsEndUserIDP(t *testing.T) {
ctx := context.Background()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
appCfg := &fleet.AppConfig{}
appCfg.ServerSettings.ServerURL = "https://test.example.com"
appCfg.MDM.EnabledAndConfigured = true
ds := new(mock.Store)
svc := scep.NewSCEPConfigService(logger, nil)
digiCertService := digicert.NewService(digicert.WithLogger(logger))
hostUUID := "host-1"
cmdUUID := "cmd-1"
var targets map[string]*fleet.CmdTarget
// this is a func to re-create it each time because calling the preprocess function modifies this
populateTargets := func() {
targets = map[string]*fleet.CmdTarget{
"p1": {CmdUUID: cmdUUID, ProfileIdentifier: "com.add.profile", EnrollmentIDs: []string{hostUUID}},
}
}
hostProfilesToInstallMap := map[fleet.HostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload{
{HostUUID: hostUUID, ProfileUUID: "p1"}: {
ProfileUUID: "p1",
ProfileIdentifier: "com.add.profile",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
CommandUUID: cmdUUID,
},
}
userEnrollmentsToHostUUIDsMap := make(map[string]string)
var updatedPayload *fleet.MDMAppleBulkUpsertHostProfilePayload
var expectedStatus fleet.MDMDeliveryStatus
ds.BulkUpsertMDMAppleHostProfilesFunc = func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
require.Len(t, payload, 1)
updatedPayload = payload[0]
require.NotNil(t, updatedPayload.Status)
assert.Equal(t, expectedStatus, *updatedPayload.Status)
// cmdUUID was replaced by a new unique command on success
assert.NotEqual(t, cmdUUID, updatedPayload.CommandUUID)
assert.Equal(t, hostUUID, updatedPayload.HostUUID)
assert.Equal(t, fleet.MDMOperationTypeInstall, updatedPayload.OperationType)
return nil
}
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, idents []string) ([]uint, error) {
require.Len(t, idents, 1)
require.Equal(t, hostUUID, idents[0])
return []uint{1}, nil
}
var updatedProfile *fleet.HostMDMAppleProfile
ds.UpdateOrDeleteHostMDMAppleProfileFunc = func(ctx context.Context, profile *fleet.HostMDMAppleProfile) error {
updatedProfile = profile
require.NotNil(t, profile.Status)
assert.Equal(t, expectedStatus, *profile.Status)
return nil
}
ds.GetAllCertificateAuthoritiesFunc = func(ctx context.Context, includeSecrets bool) ([]*fleet.CertificateAuthority, error) {
return []*fleet.CertificateAuthority{}, nil
}
ds.ListHostDeviceMappingFunc = func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) {
return nil, nil
}
cases := []struct {
desc string
profileContent string
expectedStatus fleet.MDMDeliveryStatus
setup func()
assert func(output string)
}{
{
desc: "username only scim",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsername),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "user1@example.com", output)
},
},
{
desc: "username local part only scim",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsernameLocalPart),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "user1", output)
},
},
{
desc: "groups only scim",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPGroups),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
Groups: []fleet.ScimUserGroup{{DisplayName: "a"}, {DisplayName: "b"}},
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "a,b", output)
},
},
{
desc: "multiple times username only scim",
profileContent: strings.Repeat("${FLEET_VAR_"+string(fleet.FleetVarHostEndUserIDPUsername)+"}", 3),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "user1@example.comuser1@example.comuser1@example.com", output)
},
},
{
desc: "all 3 vars with scim",
profileContent: "${FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsername) + "}${FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsernameLocalPart) + "}${FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPGroups) + "}",
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
Groups: []fleet.ScimUserGroup{{DisplayName: "a"}, {DisplayName: "b"}},
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "user1@example.comuser1a,b", output)
},
},
{
desc: "username no scim, with idp",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsername),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "idp@example.com",
Groups: []fleet.ScimUserGroup{{DisplayName: "a"}, {DisplayName: "b"}},
}, nil
}
ds.ListHostDeviceMappingFunc = func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) {
return []*fleet.HostDeviceMapping{
{
Email: "other@example.com", Source: fleet.DeviceMappingGoogleChromeProfiles,
},
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "idp@example.com", output)
},
},
{
desc: "username scim and idp",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsername),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
ds.ListHostDeviceMappingFunc = func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) {
return []*fleet.HostDeviceMapping{
{
Email: "other@example.com", Source: fleet.DeviceMappingGoogleChromeProfiles,
},
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "user1@example.com", output)
},
},
{
desc: "username, no idp user",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsername),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{}, nil
}
ds.ListHostDeviceMappingFunc = func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) {
return []*fleet.HostDeviceMapping{
{
Email: "other@example.com", Source: fleet.DeviceMappingGoogleChromeProfiles,
},
}, nil
}
},
assert: func(output string) {
assert.Len(t, targets, 0) // target is not present
assert.Contains(t, updatedProfile.Detail, "There is no IdP username for this host. Fleet couldn't populate $FLEET_VAR_HOST_END_USER_IDP_USERNAME.")
},
},
{
desc: "username local part, no idp user",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPUsernameLocalPart),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{}, nil
}
ds.ListHostDeviceMappingFunc = func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) {
return []*fleet.HostDeviceMapping{
{
Email: "other@example.com", Source: fleet.DeviceMappingGoogleChromeProfiles,
},
}, nil
}
},
assert: func(output string) {
assert.Len(t, targets, 0) // target is not present
assert.Contains(t, updatedProfile.Detail, "There is no IdP username for this host. Fleet couldn't populate $FLEET_VAR_HOST_END_USER_IDP_USERNAME_LOCAL_PART.")
},
},
{
desc: "groups, no idp user",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPGroups),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{}, nil
}
},
assert: func(output string) {
assert.Len(t, targets, 0) // target is not present
assert.Contains(t, updatedProfile.Detail, "There are no IdP groups for this host. Fleet couldn't populate $FLEET_VAR_HOST_END_USER_IDP_GROUPS.")
},
},
{
desc: "department, no idp user",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPDepartment),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{}, nil
}
},
assert: func(output string) {
assert.Len(t, targets, 0) // target is not present
assert.Contains(t, updatedProfile.Detail, "There is no IdP department for this host. Fleet couldn't populate $FLEET_VAR_HOST_END_USER_IDP_DEPARTMENT.")
},
},
{
desc: "groups with user groups, user has no groups",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPGroups),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
},
assert: func(output string) {
assert.Len(t, targets, 0) // target is not present
assert.Contains(t, updatedProfile.Detail, "There are no IdP groups for this host. Fleet couldn't populate $FLEET_VAR_HOST_END_USER_IDP_GROUPS.")
},
},
{
desc: "profile with department, user has department",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPDepartment),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
Department: ptr.String("Engineering"),
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "Engineering", output)
},
},
{
desc: "profile with department, user has no department",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPDepartment),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
},
assert: func(output string) {
assert.Len(t, targets, 0) // target is not present
assert.Contains(t, updatedProfile.Detail, "There is no IdP department for this host. Fleet couldn't populate $FLEET_VAR_HOST_END_USER_IDP_DEPARTMENT.")
},
},
{
desc: "profile with full name, user has full name",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPFullname),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
GivenName: ptr.String("First"),
FamilyName: ptr.String("Last"),
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "First Last", output)
},
},
{
desc: "profile with full name, user only has given name",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPFullname),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
GivenName: ptr.String("First"),
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "First", output)
},
},
{
desc: "profile with full name, user only has family name",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPFullname),
expectedStatus: fleet.MDMDeliveryPending,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
FamilyName: ptr.String("Last"),
}, nil
}
},
assert: func(output string) {
assert.Empty(t, updatedPayload.Detail) // no error detail
assert.Len(t, targets, 1) // target is still present
require.Equal(t, "Last", output)
},
},
{
desc: "profile with full name, user has no full name value",
profileContent: "$FLEET_VAR_" + string(fleet.FleetVarHostEndUserIDPFullname),
expectedStatus: fleet.MDMDeliveryFailed,
setup: func() {
ds.ScimUserByHostIDFunc = func(ctx context.Context, hostID uint) (*fleet.ScimUser, error) {
require.EqualValues(t, 1, hostID)
return &fleet.ScimUser{
UserName: "user1@example.com",
}, nil
}
},
assert: func(output string) {
assert.Contains(t, updatedProfile.Detail, fmt.Sprintf("There is no IdP full name for this host. Fleet couldn't populate $FLEET_VAR_%s.", fleet.FleetVarHostEndUserIDPFullname))
assert.Len(t, targets, 0)
},
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
c.setup()
profileContents := map[string]mobileconfig.Mobileconfig{
"p1": []byte(c.profileContent),
}
populateTargets()
expectedStatus = c.expectedStatus
updatedPayload = nil
updatedProfile = nil
err := preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, hostProfilesToInstallMap, userEnrollmentsToHostUUIDsMap, nil)
require.NoError(t, err)
var output string
if expectedStatus == fleet.MDMDeliveryFailed {
require.Nil(t, updatedPayload)
require.NotNil(t, updatedProfile)
} else {
require.NotNil(t, updatedPayload)
require.Nil(t, updatedProfile)
output = string(profileContents[updatedPayload.CommandUUID])
}
c.assert(output)
})
}
}