Files
fleet/server/mdm/apple/profile_processor_test.go
a33481653d macos password sync feature branch (#47422)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #45524

# 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), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [ ] Confirmed that the fix is not expected to adversely impact load
test results
- [ ] Alerted the release DRI if additional load testing is needed

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

## New Fleet configuration settings

- [ ] Setting(s) is/are explicitly excluded from GitOps

If you didn't check the box above, follow this checklist for
GitOps-enabled settings:

- [x] Verified that the setting is exported via `fleetctl
generate-gitops`
- [x] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [x] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
- [x] Verified that any relevant UI is disabled when GitOps mode is
enabled


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Apple Platform SSO (PSSO) for macOS with device registration,
sign-in, and public discovery (JWKS + Apple app-site association)
protected by single-use nonces.
* Added Apple account provisioning (Platform SSO password sync)
configuration with masked client-secret handling and GitOps support.
* Added a host-scoped PSSO device registration token variable for Apple
MDM profile generation.
* **Bug Fixes**
* Fixed macOS packaging to correctly build, embed, and sign the Platform
SSO extension.
* Resetting device Apple MDM data now also clears stored PSSO enrollment
records.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Magnus Jensen <magnus@fleetdm.com>
2026-07-09 14:57:48 -04:00

1134 lines
46 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)
})
}
}
func TestPreprocessProfileContentsPSSORegistrationToken(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
svc := scep.NewSCEPConfigService(logger, nil)
digiCertService := digicert.NewService(digicert.WithLogger(logger))
const hostUUID = "host-1"
const cmdUUID = "cmd-1"
placeholder := "$" + fleet.HostSecretPrefix + fleet.HostSecretPSSODeviceRegistrationToken
newHostProfilesMap := func() map[fleet.HostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload {
m := make(map[fleet.HostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload, 1)
m[fleet.HostProfileUUID{HostUUID: hostUUID, ProfileUUID: "p1"}] = &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileUUID: "p1",
ProfileIdentifier: "com.fleetdm.platformsso",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
CommandUUID: cmdUUID,
Scope: fleet.PayloadScopeSystem,
}
return m
}
t.Run("only variable short-circuits the per-host split", func(t *testing.T) {
ds := new(mock.Store)
bulkUpsertCalled := false
ds.BulkUpsertMDMAppleHostProfilesFunc = func(_ context.Context, _ []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
bulkUpsertCalled = true
return nil
}
targets := map[string]*fleet.CmdTarget{
"p1": {CmdUUID: cmdUUID, ProfileIdentifier: "com.fleetdm.platformsso", EnrollmentIDs: []string{hostUUID}},
}
profileContents := map[string]mobileconfig.Mobileconfig{
"p1": []byte("<string>" + fleet.FleetVarPSSODeviceRegistrationToken.WithPrefix() + "</string>"),
}
err := preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, newHostProfilesMap(), make(map[string]string), &fleet.GroupedCertificateAuthorities{})
require.NoError(t, err)
// The original target is preserved (no per-host fan-out, no new temp UUIDs).
require.Len(t, targets, 1)
require.Contains(t, targets, "p1")
require.Equal(t, cmdUUID, targets["p1"].CmdUUID)
require.False(t, bulkUpsertCalled, "host profiles should not be re-keyed for the short-circuited profile")
// The variable resolved to the host-secret placeholder on the shared content.
require.Equal(t, "<string>"+placeholder+"</string>", string(profileContents["p1"]))
require.NotContains(t, string(profileContents["p1"]), "FLEET_VAR_")
})
t.Run("multiple hosts share placeholder without per-host fan-out", func(t *testing.T) {
const host2UUID = "host-2"
const host3UUID = "host-3"
ds := new(mock.Store)
bulkUpsertCalled := false
ds.BulkUpsertMDMAppleHostProfilesFunc = func(_ context.Context, _ []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
bulkUpsertCalled = true
return nil
}
enrollmentIDs := []string{hostUUID, host2UUID, host3UUID}
targets := map[string]*fleet.CmdTarget{
"p1": {CmdUUID: cmdUUID, ProfileIdentifier: "com.fleetdm.platformsso", EnrollmentIDs: enrollmentIDs},
}
profileContents := map[string]mobileconfig.Mobileconfig{
"p1": []byte("<string>" + fleet.FleetVarPSSODeviceRegistrationToken.WithPrefix() + "</string>"),
}
multiHostProfilesMap := make(map[fleet.HostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload)
for _, hUUID := range enrollmentIDs {
multiHostProfilesMap[fleet.HostProfileUUID{HostUUID: hUUID, ProfileUUID: "p1"}] = &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileUUID: "p1",
ProfileIdentifier: "com.fleetdm.platformsso",
HostUUID: hUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
CommandUUID: cmdUUID,
Scope: fleet.PayloadScopeSystem,
}
}
err := preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, multiHostProfilesMap, make(map[string]string), &fleet.GroupedCertificateAuthorities{})
require.NoError(t, err)
// Still one target — no per-host fan-out regardless of host count.
require.Len(t, targets, 1)
require.Contains(t, targets, "p1")
require.Equal(t, cmdUUID, targets["p1"].CmdUUID)
// All enrollment IDs remain in the single shared target.
require.ElementsMatch(t, enrollmentIDs, targets["p1"].EnrollmentIDs)
require.False(t, bulkUpsertCalled, "host profiles should not be re-keyed for the short-circuited profile")
// Single shared profile content with the placeholder — no per-host copies.
require.Equal(t, "<string>"+placeholder+"</string>", string(profileContents["p1"]))
require.NotContains(t, string(profileContents["p1"]), "FLEET_VAR_")
})
t.Run("mixed with a host-specific variable still fans out and keeps the placeholder", func(t *testing.T) {
ds := new(mock.Store)
ds.BulkUpsertMDMAppleHostProfilesFunc = func(_ context.Context, _ []*fleet.MDMAppleBulkUpsertHostProfilePayload) error {
return nil
}
targets := map[string]*fleet.CmdTarget{
"p1": {CmdUUID: cmdUUID, ProfileIdentifier: "com.fleetdm.platformsso", EnrollmentIDs: []string{hostUUID}},
}
profileContents := map[string]mobileconfig.Mobileconfig{
"p1": []byte("<string>" + fleet.FleetVarPSSODeviceRegistrationToken.WithPrefix() + "</string><string>" + fleet.FleetVarHostUUID.WithPrefix() + "</string>"),
}
err := preprocessProfileContents(ctx, appCfg, ds, svc, digiCertService, logger, targets, profileContents, newHostProfilesMap(), make(map[string]string), &fleet.GroupedCertificateAuthorities{})
require.NoError(t, err)
// The original target is replaced by a per-host target.
require.NotContains(t, targets, "p1")
require.Len(t, targets, 1)
var tempUUID string
for k := range targets {
tempUUID = k
}
hostContents := string(profileContents[tempUUID])
require.Contains(t, hostContents, placeholder)
require.Contains(t, hostContents, hostUUID)
require.NotContains(t, hostContents, "FLEET_VAR_")
})
}