diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go index fa587164b0..5078779719 100644 --- a/cmd/fleet/cron.go +++ b/cmd/fleet/cron.go @@ -987,7 +987,6 @@ func newCleanupsAndAggregationSchedule( }, ), schedule.WithJob("renew_host_mdm_managed_certificates", func(ctx context.Context) error { - // TODO(MHJ): Move this datastore method to shared space, for when windows renewal is being worked on. return ds.RenewMDMManagedCertificates(ctx) }), schedule.WithJob("query_results_cleanup", func(ctx context.Context) error { diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index fd56131f31..eb058a62f6 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -830,77 +830,6 @@ func (ds *Datastore) GetAppleHostMDMCertificateProfile(ctx context.Context, host return &profile, nil } -// RenewMDMManagedCertificates marks managed certificate profiles for resend when renewal is required -func (ds *Datastore) RenewMDMManagedCertificates(ctx context.Context) error { - // This will trigger a resend next time profiles are checked - updateQuery := `UPDATE host_mdm_apple_profiles SET status = NULL WHERE status IS NOT NULL AND operation_type = ? AND (` - hostProfileClause := `` - values := []interface{}{fleet.MDMOperationTypeInstall} - - totalHostCertsToRenew := 0 - hostCertTypesToRenew := fleet.ListCATypesWithRenewalSupport() - for _, hostCertType := range hostCertTypesToRenew { - hostCertsToRenew := []struct { - HostUUID string `db:"host_uuid"` - ProfileUUID string `db:"profile_uuid"` - NotValidAfter time.Time `db:"not_valid_after"` - ValidityPeriod int `db:"validity_period"` - }{} - // Fetch all MDM Managed certificates of the given type that aren't already queued for - // resend(hmap.status=null) and which - // * Have a validity period > 30 days and are expiring in the next 30 days - // * Have a validity period <= 30 days and are within half the validity period of expiration - // nb: we SELECT not_valid_after and validity_period here so we can use them in the HAVING clause, but - // we don't actually need them for the update logic. - err := sqlx.SelectContext(ctx, ds.reader(ctx), &hostCertsToRenew, ` - SELECT - hmmc.host_uuid, - hmmc.profile_uuid, - hmmc.not_valid_after, - DATEDIFF(hmmc.not_valid_after, hmmc.not_valid_before) AS validity_period - FROM - host_mdm_managed_certificates hmmc - INNER JOIN - host_mdm_apple_profiles hmap - ON hmmc.host_uuid = hmap.host_uuid AND hmmc.profile_uuid = hmap.profile_uuid - WHERE - hmmc.type = ? AND hmap.status IS NOT NULL AND hmap.operation_type = ? - HAVING - validity_period IS NOT NULL AND - ((validity_period > 30 AND not_valid_after < DATE_ADD(NOW(), INTERVAL 30 DAY)) OR - (validity_period <= 30 AND not_valid_after < DATE_ADD(NOW(), INTERVAL validity_period/2 DAY))) - LIMIT 1000`, hostCertType, fleet.MDMOperationTypeInstall) - if err != nil { - return ctxerr.Wrap(ctx, err, "retrieving mdm managed certificates to renew") - } - if len(hostCertsToRenew) == 0 { - level.Debug(ds.logger).Log("msg", "No "+hostCertType+" certificates to renew") - continue - } - totalHostCertsToRenew += len(hostCertsToRenew) - - for _, hostCertToRenew := range hostCertsToRenew { - hostProfileClause += `(host_uuid = ? AND profile_uuid = ?) OR ` - values = append(values, hostCertToRenew.HostUUID, hostCertToRenew.ProfileUUID) - } - } - if totalHostCertsToRenew == 0 { - return nil - } - - hostProfileClause = strings.TrimSuffix(hostProfileClause, " OR ") - - level.Debug(ds.logger).Log("msg", "Renewing MDM managed digicert/SCEP certificates", "len(hostCertsToRenew)", totalHostCertsToRenew) - err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { - _, err := tx.ExecContext(ctx, updateQuery+hostProfileClause+")", values...) - if err != nil { - return ctxerr.Wrap(ctx, err, "updating mdm managed certificates to renew") - } - return nil - }) - return err -} - // ResendHostCertificateProfile marks the given profile UUID to be resent to the host with the given UUID. It // also deactivates prior nano commands and resets the retry counter for the profile UUID and host UUID. // diff --git a/server/datastore/mysql/mdm.go b/server/datastore/mysql/mdm.go index e0b0faffff..66f6e85495 100644 --- a/server/datastore/mysql/mdm.go +++ b/server/datastore/mysql/mdm.go @@ -2731,6 +2731,92 @@ func (ds *Datastore) ListHostMDMManagedCertificates(ctx context.Context, hostUUI return hostCertsToRenew, ctxerr.Wrap(ctx, err, "get mdm managed certificates for host") } +// RenewMDMManagedCertificates marks managed certificate profiles for resend when renewal is required +func (ds *Datastore) RenewMDMManagedCertificates(ctx context.Context) error { + totalHostCertsToRenew := 0 + hostCertTypesToRenew := fleet.ListCATypesWithRenewalSupport() + // Map is used to take advantage of Go map iteration order randomization so that + // if a customer is issuing certs across multiple platforms we will not bias renewals + // toward a specific platform + hostProfileTables := map[string]string{ + "apple": "host_mdm_apple_profiles", + "windows": "host_mdm_windows_profiles", + } + for _, hostCertType := range hostCertTypesToRenew { + // Limit to 1000 renewals per CA type per run across all platforms + limit := 1000 + for hostPlatform, table := range hostProfileTables { + if limit == 0 { + level.Debug(ds.logger).Log("msg", "Skipping check of %s certificates on %s hosts to renew - limit exceeded by prior platform", hostCertType, hostPlatform) + continue + } + // This will trigger a resend next time profiles are checked + updateQuery := `UPDATE ` + table + ` SET status = NULL WHERE status IS NOT NULL AND operation_type = ? AND (` + hostProfileClause := `` + values := []any{fleet.MDMOperationTypeInstall} + hostCertsToRenew := []struct { + HostUUID string `db:"host_uuid"` + ProfileUUID string `db:"profile_uuid"` + NotValidAfter time.Time `db:"not_valid_after"` + ValidityPeriod int `db:"validity_period"` + }{} + // Fetch all MDM Managed certificates of the given type that aren't already queued for + // resend(hmap.status=null) and which + // * Have a validity period > 30 days and are expiring in the next 30 days + // * Have a validity period <= 30 days and are within half the validity period of expiration + // nb: we SELECT not_valid_after and validity_period here so we can use them in the HAVING clause, but + // we don't actually need them for the update logic. + err := sqlx.SelectContext(ctx, ds.reader(ctx), &hostCertsToRenew, ` + SELECT + hmmc.host_uuid, + hmmc.profile_uuid, + hmmc.not_valid_after, + DATEDIFF(hmmc.not_valid_after, hmmc.not_valid_before) AS validity_period + FROM + host_mdm_managed_certificates hmmc + INNER JOIN + `+table+` hp + ON hmmc.host_uuid = hp.host_uuid AND hmmc.profile_uuid = hp.profile_uuid + WHERE + hmmc.type = ? AND hp.status IS NOT NULL AND hp.operation_type = ? + HAVING + validity_period IS NOT NULL AND + ((validity_period > 30 AND not_valid_after < DATE_ADD(NOW(), INTERVAL 30 DAY)) OR + (validity_period <= 30 AND not_valid_after < DATE_ADD(NOW(), INTERVAL validity_period/2 DAY))) + LIMIT ?`, hostCertType, fleet.MDMOperationTypeInstall, limit) + if err != nil { + return ctxerr.Wrap(ctx, err, "retrieving mdm managed certificates to renew") + } + if len(hostCertsToRenew) == 0 { + level.Debug(ds.logger).Log("msg", "No %s certificates on %s hosts to renew", hostCertType, hostPlatform) + continue + } + limit -= len(hostCertsToRenew) + totalHostCertsToRenew += len(hostCertsToRenew) + + for _, hostCertToRenew := range hostCertsToRenew { + hostProfileClause += `(host_uuid = ? AND profile_uuid = ?) OR ` + values = append(values, hostCertToRenew.HostUUID, hostCertToRenew.ProfileUUID) + } + hostProfileClause = strings.TrimSuffix(hostProfileClause, " OR ") + + level.Info(ds.logger).Log("msg", "Renewing MDM managed certificates", "len", len(hostCertsToRenew), "type", hostCertType, "platform", hostPlatform) + err = ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + _, err := tx.ExecContext(ctx, updateQuery+hostProfileClause+")", values...) + if err != nil { + return ctxerr.Wrap(ctx, err, "updating mdm managed certificates to renew") + } + return nil + }) + if err != nil { + return ctxerr.Wrap(ctx, err, "renewing mdm managed certificates") + } + } + } + + return nil +} + // GetHostMDMIdentifiers searches for a host by identifier (hostname, uuid, or hardware_serial). // // NOTE: We're not using existing methods like ds.whereFilterHostsByIdentifier, diff --git a/server/datastore/mysql/microsoft_mdm_test.go b/server/datastore/mysql/microsoft_mdm_test.go index 966e5b68f8..700b0d9998 100644 --- a/server/datastore/mysql/microsoft_mdm_test.go +++ b/server/datastore/mysql/microsoft_mdm_test.go @@ -3199,14 +3199,12 @@ func testWindowsMDMManagedSCEPCertificates(t *testing.T, ds *Datastore) { assert.Equal(t, caName, profile.CAName) // Renew should not do anything yet - /* - TODO: See comment below - err = ds.RenewMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryPending, *profile.Status) */ + err = ds.RenewMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryPending, *profile.Status) // Cleanup should do nothing err = ds.CleanUpMDMManagedCertificates(ctx) @@ -3215,249 +3213,246 @@ func testWindowsMDMManagedSCEPCertificates(t *testing.T, ds *Datastore) { require.NoError(t, err) require.NotNil(t, profile) - /* - TODO: Uncomment when adding renewal logic for Custom SCEP on Windows. + serial := "8ABADCAFEF684D6348F5EC95AEFF468F237A9D75" - serial := "8ABADCAFEF684D6348F5EC95AEFF468F237A9D75" - - t.Run("Non renewal scenario 1 - validity window > 30 days but not yet time to renew", func(t *testing.T) { - // Set not_valid_before to 1 day in the past and not_valid_after to 31 days in the future so - // teh validity window is 32 days of which there are 31 left which should not trigger renewal - notValidAfter := time.Now().Add(31 * 24 * time.Hour).UTC().Round(time.Microsecond) - notValidBefore := time.Now().Add(-1 * 24 * time.Hour).UTC().Round(time.Microsecond) - err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ - { - HostUUID: host.UUID, - ProfileUUID: initialCP.ProfileUUID, - ChallengeRetrievedAt: challengeRetrievedAt, - NotValidBefore: ¬ValidBefore, - NotValidAfter: ¬ValidAfter, - Type: caType, - CAName: caName, - Serial: &serial, - }, - }) - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - _, err := q.ExecContext(ctx, ` + t.Run("Non renewal scenario 1 - validity window > 30 days but not yet time to renew", func(t *testing.T) { + // Set not_valid_before to 1 day in the past and not_valid_after to 31 days in the future so + // the validity window is 32 days of which there are 31 left which should not trigger renewal + notValidAfter := time.Now().Add(31 * 24 * time.Hour).UTC().Round(time.Microsecond) + notValidBefore := time.Now().Add(-1 * 24 * time.Hour).UTC().Round(time.Microsecond) + err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ + { + HostUUID: host.UUID, + ProfileUUID: initialCP.ProfileUUID, + ChallengeRetrievedAt: challengeRetrievedAt, + NotValidBefore: ¬ValidBefore, + NotValidAfter: ¬ValidAfter, + Type: caType, + CAName: caName, + Serial: &serial, + }, + }) + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, ` UPDATE host_mdm_windows_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? `, fleet.MDMDeliveryVerified, host.UUID, initialCP.ProfileUUID) - if err != nil { - return err - } - return nil - }) + if err != nil { + return err + } + return nil + }) - // Verify the policy is not currently marked for resend and that the upsert executed correctly - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) + // Verify the policy is not currently marked for resend and that the upsert executed correctly + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) - assert.Equal(t, host.UUID, profile.HostUUID) - assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) - assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) - assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) - assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) - assert.Equal(t, caType, profile.Type) - require.NotNil(t, profile.Serial) - assert.Equal(t, serial, *profile.Serial) - assert.Equal(t, caName, profile.CAName) + assert.Equal(t, host.UUID, profile.HostUUID) + assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) + assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) + assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) + assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) + assert.Equal(t, caType, profile.Type) + require.NotNil(t, profile.Serial) + assert.Equal(t, serial, *profile.Serial) + assert.Equal(t, caName, profile.CAName) - // Renew should not change the MDM delivery status - err = ds.RenewMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) + // Renew should not change the MDM delivery status + err = ds.RenewMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) - // Cleanup should do nothing - err = ds.CleanUpMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile) - }) + // Cleanup should do nothing + err = ds.CleanUpMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile) + }) - t.Run("Non renewal scenario 2 - validity window < 30 days but not yet time to renew", func(t *testing.T) { - // Set not_valid_before to 13 days in the past and not_valid_after to 15 days in the future so - // the validity window is 28 days of which there are 15 left which should not trigger renewal - notValidAfter := time.Now().Add(15 * 24 * time.Hour).UTC().Round(time.Microsecond) - notValidBefore := time.Now().Add(-13 * 24 * time.Hour).UTC().Round(time.Microsecond) - err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ - { - HostUUID: host.UUID, - ProfileUUID: initialCP.ProfileUUID, - ChallengeRetrievedAt: challengeRetrievedAt, - NotValidBefore: ¬ValidBefore, - NotValidAfter: ¬ValidAfter, - Type: caType, - CAName: caName, - Serial: &serial, - }, - }) - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - _, err := q.ExecContext(ctx, ` - UPDATE host_mdm_apple_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? + t.Run("Non renewal scenario 2 - validity window < 30 days but not yet time to renew", func(t *testing.T) { + // Set not_valid_before to 13 days in the past and not_valid_after to 15 days in the future so + // the validity window is 28 days of which there are 15 left which should not trigger renewal + notValidAfter := time.Now().Add(15 * 24 * time.Hour).UTC().Round(time.Microsecond) + notValidBefore := time.Now().Add(-13 * 24 * time.Hour).UTC().Round(time.Microsecond) + err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ + { + HostUUID: host.UUID, + ProfileUUID: initialCP.ProfileUUID, + ChallengeRetrievedAt: challengeRetrievedAt, + NotValidBefore: ¬ValidBefore, + NotValidAfter: ¬ValidAfter, + Type: caType, + CAName: caName, + Serial: &serial, + }, + }) + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, ` + UPDATE host_mdm_windows_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? `, fleet.MDMDeliveryVerified, host.UUID, initialCP.ProfileUUID) - if err != nil { - return err - } - return nil - }) + if err != nil { + return err + } + return nil + }) - // Verify the policy is not currently marked for resend and that the upsert executed correctly - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) + // Verify the policy is not currently marked for resend and that the upsert executed correctly + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) - assert.Equal(t, host.UUID, profile.HostUUID) - assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) - assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) - assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) - assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) - assert.Equal(t, caType, profile.Type) - require.NotNil(t, profile.Serial) - assert.Equal(t, serial, *profile.Serial) - assert.Equal(t, caName, profile.CAName) + assert.Equal(t, host.UUID, profile.HostUUID) + assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) + assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) + assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) + assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) + assert.Equal(t, caType, profile.Type) + require.NotNil(t, profile.Serial) + assert.Equal(t, serial, *profile.Serial) + assert.Equal(t, caName, profile.CAName) - // Renew should not change the MDM delivery status - err = ds.RenewMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) + // Renew should not change the MDM delivery status + err = ds.RenewMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) - // Cleanup should do nothing - err = ds.CleanUpMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile) - }) + // Cleanup should do nothing + err = ds.CleanUpMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile) + }) - t.Run("Renew scenario 1 - validity window > 30 days", func(t *testing.T) { - // Set not_valid_before to 31 days in the past the validity window becomes 60 days, of which there are - // 29 left which should trigger the first renewal scenario(window > 30 days, renew when < 30 - // days left) - notValidAfter := time.Now().Add(29 * 24 * time.Hour).UTC().Round(time.Microsecond) - notValidBefore := time.Now().Add(-31 * 24 * time.Hour).UTC().Round(time.Microsecond) - err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ - { - HostUUID: host.UUID, - ProfileUUID: initialCP.ProfileUUID, - ChallengeRetrievedAt: challengeRetrievedAt, - NotValidBefore: ¬ValidBefore, - NotValidAfter: ¬ValidAfter, - Type: caType, - CAName: caName, - Serial: &serial, - }, - }) - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - _, err := q.ExecContext(ctx, ` - UPDATE host_mdm_apple_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? + t.Run("Renew scenario 1 - validity window > 30 days", func(t *testing.T) { + // Set not_valid_before to 31 days in the past the validity window becomes 60 days, of which there are + // 29 left which should trigger the first renewal scenario(window > 30 days, renew when < 30 + // days left) + notValidAfter := time.Now().Add(29 * 24 * time.Hour).UTC().Round(time.Microsecond) + notValidBefore := time.Now().Add(-31 * 24 * time.Hour).UTC().Round(time.Microsecond) + err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ + { + HostUUID: host.UUID, + ProfileUUID: initialCP.ProfileUUID, + ChallengeRetrievedAt: challengeRetrievedAt, + NotValidBefore: ¬ValidBefore, + NotValidAfter: ¬ValidAfter, + Type: caType, + CAName: caName, + Serial: &serial, + }, + }) + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, ` + UPDATE host_mdm_windows_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? `, fleet.MDMDeliveryVerified, host.UUID, initialCP.ProfileUUID) - if err != nil { - return err - } - return nil - }) + if err != nil { + return err + } + return nil + }) - // Verify the policy is not currently marked for resend and that the upsert executed correctly - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) + // Verify the policy is not currently marked for resend and that the upsert executed correctly + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) - assert.Equal(t, host.UUID, profile.HostUUID) - assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) - assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) - assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) - assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) - assert.Equal(t, caType, profile.Type) - require.NotNil(t, profile.Serial) - assert.Equal(t, serial, *profile.Serial) - assert.Equal(t, caName, profile.CAName) + assert.Equal(t, host.UUID, profile.HostUUID) + assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) + assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) + assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) + assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) + assert.Equal(t, caType, profile.Type) + require.NotNil(t, profile.Serial) + assert.Equal(t, serial, *profile.Serial) + assert.Equal(t, caName, profile.CAName) - // Renew should set the MDM delivery status to "null" so the profile gets resent and the certificate renewed - err = ds.RenewMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.Nil(t, profile.Status) + // Renew should set the MDM delivery status to "null" so the profile gets resent and the certificate renewed + err = ds.RenewMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.Nil(t, profile.Status) - // Cleanup should do nothing - err = ds.CleanUpMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile) - }) + // Cleanup should do nothing + err = ds.CleanUpMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile) + }) - t.Run("Renew scenario 2 - validity window < 30 days", func(t *testing.T) { - // Set not_valid_before to 15 days in the past and not_valid_after to 14 days in the future so the - // validity window becomes 29 days, of which there are 14 left which should trigger the second - // renewal scenario(window < 30 days, renew when there is half that time left) - notValidBefore := time.Now().Add(-15 * 24 * time.Hour).UTC().Round(time.Microsecond) - notValidAfter := time.Now().Add(14 * 24 * time.Hour).UTC().Round(time.Microsecond) - err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ - { - HostUUID: host.UUID, - ProfileUUID: initialCP.ProfileUUID, - ChallengeRetrievedAt: challengeRetrievedAt, - NotValidBefore: ¬ValidBefore, - NotValidAfter: ¬ValidAfter, - Type: caType, - CAName: caName, - Serial: &serial, - }, - }) - require.NoError(t, err) + t.Run("Renew scenario 2 - validity window < 30 days", func(t *testing.T) { + // Set not_valid_before to 15 days in the past and not_valid_after to 14 days in the future so the + // validity window becomes 29 days, of which there are 14 left which should trigger the second + // renewal scenario(window < 30 days, renew when there is half that time left) + notValidBefore := time.Now().Add(-15 * 24 * time.Hour).UTC().Round(time.Microsecond) + notValidAfter := time.Now().Add(14 * 24 * time.Hour).UTC().Round(time.Microsecond) + err = ds.BulkUpsertMDMManagedCertificates(ctx, []*fleet.MDMManagedCertificate{ + { + HostUUID: host.UUID, + ProfileUUID: initialCP.ProfileUUID, + ChallengeRetrievedAt: challengeRetrievedAt, + NotValidBefore: ¬ValidBefore, + NotValidAfter: ¬ValidAfter, + Type: caType, + CAName: caName, + Serial: &serial, + }, + }) + require.NoError(t, err) - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - _, err := q.ExecContext(ctx, ` - UPDATE host_mdm_apple_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, ` + UPDATE host_mdm_windows_profiles SET status = ? WHERE host_uuid = ? AND profile_uuid = ? `, fleet.MDMDeliveryVerified, host.UUID, initialCP.ProfileUUID) - if err != nil { - return err - } - return nil - }) - require.NoError(t, err) + if err != nil { + return err + } + return nil + }) + require.NoError(t, err) - // Verify the policy is not currently marked for resend and that the upsert executed correctly - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile.Status) - assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) + // Verify the policy is not currently marked for resend and that the upsert executed correctly + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile.Status) + assert.Equal(t, fleet.MDMDeliveryVerified, *profile.Status) - assert.Equal(t, host.UUID, profile.HostUUID) - assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) - assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) - assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) - assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) - assert.Equal(t, caType, profile.Type) - require.NotNil(t, profile.Serial) - assert.Equal(t, serial, *profile.Serial) - assert.Equal(t, caName, profile.CAName) + assert.Equal(t, host.UUID, profile.HostUUID) + assert.Equal(t, initialCP.ProfileUUID, profile.ProfileUUID) + assert.Equal(t, challengeRetrievedAt, profile.ChallengeRetrievedAt) + assert.Equal(t, ¬ValidBefore, profile.NotValidBefore) + assert.Equal(t, ¬ValidAfter, profile.NotValidAfter) + assert.Equal(t, caType, profile.Type) + require.NotNil(t, profile.Serial) + assert.Equal(t, serial, *profile.Serial) + assert.Equal(t, caName, profile.CAName) - // Renew should set the MDM delivery status to "null" so the profile gets resent and the certificate renewed - err = ds.RenewMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.Nil(t, profile.Status) + // Renew should set the MDM delivery status to "null" so the profile gets resent and the certificate renewed + err = ds.RenewMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.Nil(t, profile.Status) - // Cleanup should do nothing - err = ds.CleanUpMDMManagedCertificates(ctx) - require.NoError(t, err) - profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) - require.NoError(t, err) - require.NotNil(t, profile) - }) */ + // Cleanup should do nothing + err = ds.CleanUpMDMManagedCertificates(ctx) + require.NoError(t, err) + profile, err = ds.GetWindowsHostMDMCertificateProfile(ctx, host.UUID, initialCP.ProfileUUID, caName) + require.NoError(t, err) + require.NotNil(t, profile) + }) }) } } diff --git a/server/mdm/microsoft/profile_verifier.go b/server/mdm/microsoft/profile_verifier.go index bfebb060ad..f57f2d296f 100644 --- a/server/mdm/microsoft/profile_verifier.go +++ b/server/mdm/microsoft/profile_verifier.go @@ -494,6 +494,8 @@ func preprocessWindowsProfileContents(deps ProfilePreprocessDependencies, params switch { case fleetVar == string(fleet.FleetVarSCEPWindowsCertificateID): result = profiles.ReplaceFleetVariableInXML(fleet.FleetVarSCEPWindowsCertificateIDRegexp, result, params.ProfileUUID) + case fleetVar == string(fleet.FleetVarSCEPRenewalID): + result = profiles.ReplaceFleetVariableInXML(fleet.FleetVarSCEPRenewalIDRegexp, result, "fleet-"+params.ProfileUUID) case strings.HasPrefix(fleetVar, string(fleet.FleetVarCustomSCEPChallengePrefix)): caName := strings.TrimPrefix(fleetVar, string(fleet.FleetVarCustomSCEPChallengePrefix)) err := profiles.IsCustomSCEPConfigured(deps.Context, deps.CustomSCEPCAs, caName, fleetVar, func(errMsg string) error {