Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/42405-android-onc-after-cert
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- Android Wi-Fi configuration profiles (`openNetworkConfiguration` with `ClientCertKeyPairAlias`) are now withheld until the referenced certificate is installed or terminally failed on the device.
- The host OS settings detail column now shows the reason when an Android profile is pending due to a certificate dependency.
- `fleetctl gitops` now processes Android certificates before Android profiles
18 changes: 17 additions & 1 deletion server/datastore/mysql/host_certificate_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func (ds *Datastore) RequeueWithheldONCProfilesForHost(ctx context.Context, host
WHERE host_uuid = ? AND status = ? AND detail LIKE ?`,
hostUUID, fleet.MDMDeliveryPending, fleet.ONCProfileWithheldDetailPrefix+"%",
)
return ctxerr.Wrap(ctx, err, "requeue pending android profiles for host")
return ctxerr.Wrap(ctx, err, "requeue withheld ONC profiles for host")
}

// RetryHostCertificateTemplate resets a failed certificate to pending for automatic retry,
Expand Down Expand Up @@ -618,11 +618,27 @@ func (ds *Datastore) RevertStaleCertificateTemplates(
// For a given certificate template ID, it deletes any rows with status in (pending, failed)
// and operation_type=install, then updates rows with operation_type=install to pending remove.
// Rows already in remove state are left unchanged (idempotent).
//
// Also requeues any withheld ONC profiles for affected hosts within the same transaction,
// so the reconciler releases them on its next cycle.
func (ds *Datastore) SetHostCertificateTemplatesToPendingRemove(
ctx context.Context,
certificateTemplateID uint,
) error {
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
// Requeue withheld ONC profiles for all hosts with this cert template.
// Must run before deleting host_certificate_templates rows since the
// subquery needs them to find affected hosts.
if _, err := tx.ExecContext(ctx,
`UPDATE host_mdm_android_profiles SET status = NULL
WHERE host_uuid IN (
SELECT host_uuid FROM host_certificate_templates WHERE certificate_template_id = ?
) AND status = ? AND detail LIKE ?`,
certificateTemplateID, fleet.MDMDeliveryPending, fleet.ONCProfileWithheldDetailPrefix+"%",
); err != nil {
return ctxerr.Wrap(ctx, err, "requeue withheld ONC profiles for cert template removal")
}

// Delete rows with status in (pending, failed) and operation_type=install
// These certificates were never successfully installed on the device
deleteStmt := fmt.Sprintf(`
Expand Down
22 changes: 22 additions & 0 deletions server/datastore/mysql/host_certificate_templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1115,9 +1115,31 @@ func testSetHostCertificateTemplatesToPendingRemove(t *testing.T, ds *Datastore)
)
require.NoError(t, err)

// Insert a withheld ONC profile for host-delivered to verify it gets requeued
// when the cert template is removed.
withheldStatus := fleet.MDMDeliveryPending
err = ds.BulkUpsertMDMAndroidHostProfiles(ctx, []*fleet.MDMAndroidProfilePayload{{
HostUUID: "host-delivered",
ProfileUUID: "prof-onc-wifi",
ProfileName: "onc-wifi",
Status: &withheldStatus,
OperationType: fleet.MDMOperationTypeInstall,
Detail: fleet.ONCProfileWithheldDetailPrefix + ` "wifi-cert" to be installed on the host before applying this profile.`,
}})
require.NoError(t, err)

err = ds.SetHostCertificateTemplatesToPendingRemove(ctx, setup.template.ID)
require.NoError(t, err)

// Verify the withheld ONC profile was requeued (status set to NULL).
// We query raw SQL here because GetHostMDMAndroidProfiles coalesces NULL to 'pending'.
var oncStatus *string
err = ds.writer(ctx).GetContext(ctx, &oncStatus,
"SELECT status FROM host_mdm_android_profiles WHERE host_uuid = ? AND profile_uuid = ?",
"host-delivered", "prof-onc-wifi")
require.NoError(t, err)
require.Nil(t, oncStatus, "withheld ONC profile should have status reset to NULL")

// Verify the pending row was deleted
var count int
err = ds.writer(ctx).GetContext(ctx, &count,
Expand Down
16 changes: 10 additions & 6 deletions server/service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2453,12 +2453,11 @@ func (c *Client) DoGitOps(
}
}

err = c.doGitOpsPolicies(incoming, teamSoftwareInstallers, teamVPPApps, teamScripts, logFn, dryRun)
if err != nil {
return nil, err
}

// Apply Android certificates if present
// Apply Android certificates before policies so that certificate templates
// exist on the server before the profile reconciler's next cron cycle. This
// prevents a race where the cron fires after profiles are uploaded (by
// ApplyGroup above) but before cert templates exist, which would cause ONC
// profiles to be sent without waiting for the cert.
err = c.doGitOpsAndroidCertificates(incoming, logFn, dryRun)
if err != nil {
var gitOpsErr *gitOpsValidationError
Expand All @@ -2468,6 +2467,11 @@ func (c *Client) DoGitOps(
return nil, err
}

err = c.doGitOpsPolicies(incoming, teamSoftwareInstallers, teamVPPApps, teamScripts, logFn, dryRun)
if err != nil {
return nil, err
}

// apply icon changes from software installers and VPP apps
if len(teamSoftwareInstallers) > 0 || len(teamVPPApps) > 0 {
iconUpdates := fleet.IconChanges{}.WithUploadedHashes(iconSettings.UploadedHashes).WithSoftware(teamSoftwareInstallers, teamVPPApps)
Expand Down
Loading